chore: merge dev
to main
(#181)
Some checks failed
Publish latest version / build (push) Failing after 1m15s
Some checks failed
Publish latest version / build (push) Failing after 1m15s
- close #100 - find timezone based on locale - Add tests - Add tests to CI - Close #182 - Close #183 Reviewed-on: #181 Co-authored-by: Mylloon <kennel.anri@tutanota.com> Co-committed-by: Mylloon <kennel.anri@tutanota.com>
This commit is contained in:
parent
a08d0c0e9b
commit
2cc6c0bd74
15 changed files with 3792 additions and 66 deletions
|
@ -5,3 +5,5 @@
|
||||||
!package-lock.json
|
!package-lock.json
|
||||||
!LICENSE
|
!LICENSE
|
||||||
!tsconfig.json
|
!tsconfig.json
|
||||||
|
|
||||||
|
src/tests
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
name: Lint and Format Check
|
name: PR Check
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -21,3 +21,6 @@ jobs:
|
||||||
|
|
||||||
- name: Run format check
|
- name: Run format check
|
||||||
run: npm run format-check
|
run: npm run format-check
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm run test -- --ci
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -15,3 +15,6 @@ dist/
|
||||||
|
|
||||||
# Debug file
|
# Debug file
|
||||||
src/events/player/debug.ts
|
src/events/player/debug.ts
|
||||||
|
|
||||||
|
# Jest
|
||||||
|
coverage/
|
||||||
|
|
|
@ -25,6 +25,7 @@ une [Pull Request](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/pulls)
|
||||||
- [Modifier du code](#modifier-du-code)
|
- [Modifier du code](#modifier-du-code)
|
||||||
- [Soumettre ses modifications](#soumettre-ses-modifications)
|
- [Soumettre ses modifications](#soumettre-ses-modifications)
|
||||||
- [Gestion du dépôt](#gestion-du-dépôt)
|
- [Gestion du dépôt](#gestion-du-dépôt)
|
||||||
|
- [Tester son code](#tester-son-code)
|
||||||
|
|
||||||
## Recevoir de l'aide
|
## Recevoir de l'aide
|
||||||
|
|
||||||
|
@ -284,3 +285,20 @@ Pour commencer, vous pouvez jeter un œil aux
|
||||||
[le graphe](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/graph).
|
[le graphe](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/graph).
|
||||||
- De préférences, suivre [ces conventions](https://www.conventionalcommits.org/fr/v1.0.0/)
|
- De préférences, suivre [ces conventions](https://www.conventionalcommits.org/fr/v1.0.0/)
|
||||||
(cf. cette [partie précédente](#soumettre-ses-modifications)).
|
(cf. cette [partie précédente](#soumettre-ses-modifications)).
|
||||||
|
|
||||||
|
## Tester son code
|
||||||
|
|
||||||
|
Il est souhaité d'écrire des tests quand cela est possible.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { fnReturnsTrue } from "../src/utils/file";
|
||||||
|
|
||||||
|
describe("test name", () => {
|
||||||
|
{
|
||||||
|
const name = "to be tested";
|
||||||
|
test(name, () => {
|
||||||
|
expect(fnReturnsTrue() /* function to test */).toBe(true /* expected result */);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
3373
package-lock.json
generated
3373
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
@ -8,7 +8,8 @@
|
||||||
"debug": "npx tsnd --respawn ./src/index.ts",
|
"debug": "npx tsnd --respawn ./src/index.ts",
|
||||||
"lint": "npx eslint src",
|
"lint": "npx eslint src",
|
||||||
"format-check": "npx prettier --check src",
|
"format-check": "npx prettier --check src",
|
||||||
"format-write": "npx prettier --write src"
|
"format-write": "npx prettier --write src",
|
||||||
|
"test": "npx jest"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -25,15 +26,22 @@
|
||||||
"discord-player": "^6.7.1",
|
"discord-player": "^6.7.1",
|
||||||
"discord.js": "^14.16.2",
|
"discord.js": "^14.16.2",
|
||||||
"mediaplex": "^0.0.9",
|
"mediaplex": "^0.0.9",
|
||||||
|
"moment-timezone": "^0.5.45",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/jest": "~29.5.13",
|
||||||
"@typescript-eslint/eslint-plugin": "~8.6.0",
|
"@typescript-eslint/eslint-plugin": "~8.6.0",
|
||||||
"@typescript-eslint/parser": "~8.6.0",
|
"@typescript-eslint/parser": "~8.6.0",
|
||||||
"dotenv": "~16.4.5",
|
"dotenv": "~16.4.5",
|
||||||
|
"jest": "~29.7.0",
|
||||||
"prettier-eslint": "~16.3.0",
|
"prettier-eslint": "~16.3.0",
|
||||||
|
"ts-jest": "~29.2.5",
|
||||||
"ts-node-dev": "~2.0.0"
|
"ts-node-dev": "~2.0.0"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"preset": "ts-jest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Client, EmbedBuilder, Message, TextBasedChannel } from "discord.js";
|
||||||
import { getLocale } from "../../utils/locales";
|
import { getLocale } from "../../utils/locales";
|
||||||
import { isImage, userWithNickname } from "../../utils/misc";
|
import { isImage, userWithNickname } from "../../utils/misc";
|
||||||
import { showDate } from "../../utils/time";
|
import { showDate } from "../../utils/time";
|
||||||
|
import { RegexC, RegExpFlags } from "../../utils/regex";
|
||||||
|
|
||||||
/** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-messageCreate */
|
/** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-messageCreate */
|
||||||
export default async (message: Message, client: Client) => {
|
export default async (message: Message, client: Client) => {
|
||||||
|
@ -24,7 +25,7 @@ export default async (message: Message, client: Client) => {
|
||||||
/* Citation */
|
/* Citation */
|
||||||
const regex =
|
const regex =
|
||||||
/https:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19})/g;
|
/https:\/\/(?:canary\.|ptb\.)?discord(?:app)?\.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19})/g;
|
||||||
const urls = message.content.match(new RegExp(regex, "g"));
|
const urls = message.content.match(RegexC(regex, RegExpFlags.Global));
|
||||||
|
|
||||||
// Ignore message if there is no URLs
|
// Ignore message if there is no URLs
|
||||||
if (!urls) {
|
if (!urls) {
|
||||||
|
@ -42,7 +43,7 @@ export default async (message: Message, client: Client) => {
|
||||||
}[] = [],
|
}[] = [],
|
||||||
match,
|
match,
|
||||||
) => {
|
) => {
|
||||||
const [, guild_id, channel_id, message_id] = new RegExp(regex).exec(
|
const [, guild_id, channel_id, message_id] = RegexC(regex).exec(
|
||||||
match,
|
match,
|
||||||
) as RegExpExecArray;
|
) as RegExpExecArray;
|
||||||
|
|
||||||
|
@ -178,7 +179,7 @@ export default async (message: Message, client: Client) => {
|
||||||
|
|
||||||
// Delete source message if no content when removing links
|
// Delete source message if no content when removing links
|
||||||
if (
|
if (
|
||||||
!message.content.replace(new RegExp(regex, "g"), "").trim() &&
|
!message.content.replace(RegexC(regex, RegExpFlags.Global), "").trim() &&
|
||||||
messages.length === urls.length &&
|
messages.length === urls.length &&
|
||||||
!message.mentions.repliedUser &&
|
!message.mentions.repliedUser &&
|
||||||
message.channel.isSendable()
|
message.channel.isSendable()
|
||||||
|
|
28
src/tests/modules/string.test.ts
Normal file
28
src/tests/modules/string.test.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import "../../modules/string";
|
||||||
|
|
||||||
|
describe("Capitalize", () => {
|
||||||
|
{
|
||||||
|
const name = "test";
|
||||||
|
test(name, () => {
|
||||||
|
expect(name.capitalize()).toBe("Test");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "MACHIN";
|
||||||
|
test(name, () => {
|
||||||
|
expect(name.capitalize()).toBe("MACHIN");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "tRUC";
|
||||||
|
test(name, () => {
|
||||||
|
expect(name.capitalize()).toBe("TRUC");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "Super";
|
||||||
|
test(name, () => {
|
||||||
|
expect(name.capitalize()).toBe("Super");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
151
src/tests/utils/misc.test.ts
Normal file
151
src/tests/utils/misc.test.ts
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import {
|
||||||
|
cleanCodeBlock,
|
||||||
|
emojiPng,
|
||||||
|
isImage,
|
||||||
|
removeExtension,
|
||||||
|
splitFilenameExtensions,
|
||||||
|
} from "../../utils/misc";
|
||||||
|
|
||||||
|
describe("Filename splitter", () => {
|
||||||
|
{
|
||||||
|
const name = "test.js";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitFilenameExtensions(name)).toStrictEqual({ file: "test", ext: "js" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = ".env";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitFilenameExtensions(name)).toStrictEqual({ file: ".env", ext: undefined });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = ".env.test";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitFilenameExtensions(name)).toStrictEqual({
|
||||||
|
file: ".env",
|
||||||
|
ext: "test",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "file.test.js";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitFilenameExtensions(name)).toStrictEqual({ file: "file.test", ext: "js" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Extension remover", () => {
|
||||||
|
{
|
||||||
|
const name = "test.js";
|
||||||
|
test(name, () => {
|
||||||
|
expect(removeExtension(name)).toBe("test");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = ".env";
|
||||||
|
test(name, () => {
|
||||||
|
expect(removeExtension(name)).toBe(".env");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = ".env.test";
|
||||||
|
test(name, () => {
|
||||||
|
expect(removeExtension(name)).toBe(".env");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "file.test.js";
|
||||||
|
test(name, () => {
|
||||||
|
expect(removeExtension(name)).toBe("file.test");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Image checker", () => {
|
||||||
|
{
|
||||||
|
const name = "image.Png";
|
||||||
|
test(name, () => {
|
||||||
|
expect(isImage(name)).toBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "image.jpeg";
|
||||||
|
test(name, () => {
|
||||||
|
expect(isImage(name)).toBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "image.wav";
|
||||||
|
test(name, () => {
|
||||||
|
expect(isImage(name)).toBe(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "image.jpg";
|
||||||
|
test(name, () => {
|
||||||
|
expect(isImage(name)).toBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "image.webP";
|
||||||
|
test(name, () => {
|
||||||
|
expect(isImage(name)).toBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "image.GIF";
|
||||||
|
test(name, () => {
|
||||||
|
expect(isImage(name)).toBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Code block cleaner", () => {
|
||||||
|
{
|
||||||
|
const name = "salut";
|
||||||
|
test(name, () => {
|
||||||
|
expect(cleanCodeBlock(name)).toBe("`salut`");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "<@158260864623968257> ça va ?";
|
||||||
|
test(name, () => {
|
||||||
|
expect(cleanCodeBlock(name)).toBe("<@158260864623968257>` ça va ?`");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "t'as vu la vidéo ? https://youtu.be/dQw4w9WgXcQ";
|
||||||
|
test(name, () => {
|
||||||
|
expect(cleanCodeBlock(name)).toBe("`t'as vu la vidéo ? `https://youtu.be/dQw4w9WgXcQ");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "t'as vu la vidéo ? https://youtu.be/dQw4w9WgXcQ elle est cool en vrai tqt";
|
||||||
|
test(name, () => {
|
||||||
|
expect(cleanCodeBlock(name)).toBe(
|
||||||
|
"`t'as vu la vidéo ? `https://youtu.be/dQw4w9WgXcQ` elle est cool en vrai tqt`",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Emoji to link", () => {
|
||||||
|
{
|
||||||
|
const name = "☺️";
|
||||||
|
test(name, () => {
|
||||||
|
expect(emojiPng(name)).toBe(
|
||||||
|
"https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/263a.png",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "🍕";
|
||||||
|
test(name, () => {
|
||||||
|
expect(emojiPng(name)).toBe(
|
||||||
|
"https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/1f355.png",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
26
src/tests/utils/regex.test.ts
Normal file
26
src/tests/utils/regex.test.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { RegexC, RegExpFlags } from "../../utils/regex";
|
||||||
|
|
||||||
|
describe("Regex flags", () => {
|
||||||
|
test("One parameter", () => {
|
||||||
|
const regex = RegexC("", RegExpFlags.Global);
|
||||||
|
expect(regex.global).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("All parameters", () => {
|
||||||
|
const regex = RegexC(
|
||||||
|
"",
|
||||||
|
RegExpFlags.Global |
|
||||||
|
RegExpFlags.MultiLine |
|
||||||
|
RegExpFlags.Insensitive |
|
||||||
|
RegExpFlags.Sticky |
|
||||||
|
RegExpFlags.Unicode |
|
||||||
|
RegExpFlags.SingleLine,
|
||||||
|
);
|
||||||
|
expect(regex.global).toBeTruthy();
|
||||||
|
expect(regex.multiline).toBeTruthy();
|
||||||
|
expect(regex.ignoreCase).toBeTruthy();
|
||||||
|
expect(regex.sticky).toBeTruthy();
|
||||||
|
expect(regex.unicode).toBeTruthy();
|
||||||
|
expect(regex.dotAll).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
28
src/tests/utils/reminder.test.ts
Normal file
28
src/tests/utils/reminder.test.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { OptionReminder, splitTime } from "../../utils/reminder";
|
||||||
|
|
||||||
|
describe("Time splitter", () => {
|
||||||
|
{
|
||||||
|
const name = "";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitTime(name)).toStrictEqual({ option: OptionReminder.Nothing, time: "" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "2m@p";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitTime(name)).toStrictEqual({ option: OptionReminder.DirectMessage, time: "2m" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "41@";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitTime(name)).toStrictEqual({ option: OptionReminder.Mention, time: "41" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "0P";
|
||||||
|
test(name, () => {
|
||||||
|
expect(splitTime(name)).toStrictEqual({ option: OptionReminder.DirectMessage, time: "0" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
86
src/tests/utils/time.test.ts
Normal file
86
src/tests/utils/time.test.ts
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import { nextTimeUnit, showDate, strToSeconds, TimeSecond } from "../../utils/time";
|
||||||
|
|
||||||
|
describe("Date with correct timezone", () => {
|
||||||
|
const map = new Map([["u_time_at", "@"]]);
|
||||||
|
const date = new Date(1727434767686);
|
||||||
|
{
|
||||||
|
const name = "fr";
|
||||||
|
test(name, () => {
|
||||||
|
expect(showDate(name, map, date)).toBe("27/09/2024 @ 12:59:27");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "en-US";
|
||||||
|
test(name, () => {
|
||||||
|
expect(showDate(name, map, date)).toBe("9/27/24, @ 1:59:27");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "unknown";
|
||||||
|
// Depends on the system
|
||||||
|
// The important is that the date is in the correct timezone (UTC)
|
||||||
|
test(name, () => {
|
||||||
|
expect(["27/09/2024 @ 10:59:27", "9/27/24, @ 10:59:27"]).toContain(showDate(name, map, date));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "zh-CN";
|
||||||
|
test(name, () => {
|
||||||
|
expect(showDate(name, map, date)).toBe("2024/9/27 @ 18:59:27");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("String time to seconds", () => {
|
||||||
|
{
|
||||||
|
const name = "10m30";
|
||||||
|
test(name, () => {
|
||||||
|
expect(strToSeconds(name)).toBe(630);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "12h30";
|
||||||
|
test(name, () => {
|
||||||
|
expect(strToSeconds(name)).toBe(45000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "12s30";
|
||||||
|
test(name, () => {
|
||||||
|
expect(strToSeconds(name)).toBe(42);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = "1w30h20";
|
||||||
|
test(name, () => {
|
||||||
|
expect(strToSeconds(name)).toBe(714000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Next time unit", () => {
|
||||||
|
{
|
||||||
|
const name = TimeSecond.Minute;
|
||||||
|
test(name.toString(), () => {
|
||||||
|
expect(nextTimeUnit(name)).toBe(TimeSecond.Second);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = TimeSecond.Hour;
|
||||||
|
test(name.toString(), () => {
|
||||||
|
expect(nextTimeUnit(name)).toBe(TimeSecond.Minute);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = TimeSecond.Second;
|
||||||
|
test(name.toString(), () => {
|
||||||
|
expect(nextTimeUnit(name)).toBe(TimeSecond.Second);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const name = TimeSecond.Year;
|
||||||
|
test(name.toString(), () => {
|
||||||
|
expect(nextTimeUnit(name)).toBe(TimeSecond.Week);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
30
src/utils/regex.ts
Normal file
30
src/utils/regex.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
export enum RegExpFlags {
|
||||||
|
// Global
|
||||||
|
Global = 1 << 0,
|
||||||
|
// Multi Line
|
||||||
|
MultiLine = 1 << 1,
|
||||||
|
// Ignore Case
|
||||||
|
Insensitive = 1 << 2,
|
||||||
|
// Sticky
|
||||||
|
Sticky = 1 << 3,
|
||||||
|
// Unicode
|
||||||
|
Unicode = 1 << 4,
|
||||||
|
// Dot All
|
||||||
|
SingleLine = 1 << 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
const flagsToString = (flags: number) => {
|
||||||
|
let result = "";
|
||||||
|
|
||||||
|
if (flags & RegExpFlags.Global) result += "g";
|
||||||
|
if (flags & RegExpFlags.MultiLine) result += "m";
|
||||||
|
if (flags & RegExpFlags.Insensitive) result += "i";
|
||||||
|
if (flags & RegExpFlags.Sticky) result += "y";
|
||||||
|
if (flags & RegExpFlags.Unicode) result += "u";
|
||||||
|
if (flags & RegExpFlags.SingleLine) result += "s";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RegexC = (pattern: RegExp | string, flags: number = 0) =>
|
||||||
|
new RegExp(pattern, flagsToString(flags));
|
|
@ -2,6 +2,7 @@ import { Client, Colors, EmbedBuilder, User } from "discord.js";
|
||||||
import { getLocale } from "./locales";
|
import { getLocale } from "./locales";
|
||||||
import { cleanCodeBlock } from "./misc";
|
import { cleanCodeBlock } from "./misc";
|
||||||
import { showDate, strToSeconds, timeDeltaToString } from "./time";
|
import { showDate, strToSeconds, timeDeltaToString } from "./time";
|
||||||
|
import { RegexC, RegExpFlags } from "./regex";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Option possible for reminders
|
* Option possible for reminders
|
||||||
|
@ -45,14 +46,26 @@ export type dbReminder = {
|
||||||
* @param time raw text from user
|
* @param time raw text from user
|
||||||
* @returns An object with the time and the option
|
* @returns An object with the time and the option
|
||||||
*/
|
*/
|
||||||
const splitTime = (time: string) => {
|
export const splitTime = (time: string) => {
|
||||||
if (time?.endsWith("@")) {
|
const mapping = {
|
||||||
return { time: time.slice(0, -1), option: OptionReminder.Mention };
|
[OptionReminder.DirectMessage]: "p",
|
||||||
} else if (time?.toLowerCase().endsWith("p")) {
|
[OptionReminder.Mention]: "@",
|
||||||
return { time: time.slice(0, -1), option: OptionReminder.DirectMessage };
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return { time: time, option: OptionReminder.Nothing };
|
const trimmed = time.replaceAll(
|
||||||
|
RegexC(Object.values(mapping).join("|"), RegExpFlags.Global | RegExpFlags.Insensitive),
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Depending of the last character of the string
|
||||||
|
switch (time.toLowerCase().slice(-1)) {
|
||||||
|
case mapping[OptionReminder.Mention]:
|
||||||
|
return { time: trimmed, option: OptionReminder.Mention };
|
||||||
|
case mapping[OptionReminder.DirectMessage]:
|
||||||
|
return { time: trimmed, option: OptionReminder.DirectMessage };
|
||||||
|
default:
|
||||||
|
return { time: time, option: OptionReminder.Nothing };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
import { RegexC, RegExpFlags } from "./regex";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parsed string adapted with TZ (locales) and format for the specified lang
|
* Parsed string adapted with TZ (locales) and format for the specified lang
|
||||||
* @param tz Lang
|
* @param tz Lang
|
||||||
|
@ -6,7 +9,10 @@
|
||||||
* @returns String
|
* @returns String
|
||||||
*/
|
*/
|
||||||
export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) => {
|
export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) => {
|
||||||
|
const localeInfo = new Intl.Locale(tz);
|
||||||
|
const intlTimezone = moment.tz.zonesForCountry(localeInfo.region ?? localeInfo.baseName);
|
||||||
const formattedDate = new Intl.DateTimeFormat(tz, {
|
const formattedDate = new Intl.DateTimeFormat(tz, {
|
||||||
|
timeZone: intlTimezone ? intlTimezone[0] : "Factory",
|
||||||
dateStyle: "short",
|
dateStyle: "short",
|
||||||
timeStyle: "medium",
|
timeStyle: "medium",
|
||||||
})
|
})
|
||||||
|
@ -16,7 +22,7 @@ export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) =
|
||||||
return `${formattedDate[0]} ${locale.get("u_time_at")} ${formattedDate[1]}`;
|
return `${formattedDate[0]} ${locale.get("u_time_at")} ${formattedDate[1]}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum TimeSecond {
|
export enum TimeSecond {
|
||||||
Year = 31536000,
|
Year = 31536000,
|
||||||
Week = 604800,
|
Week = 604800,
|
||||||
Day = 86400,
|
Day = 86400,
|
||||||
|
@ -25,6 +31,18 @@ enum TimeSecond {
|
||||||
Second = 1,
|
Second = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get next time unit. For example the next unit after Hour is Minute
|
||||||
|
* @param currentUnit Current time unit
|
||||||
|
* @returns The next time unit
|
||||||
|
*/
|
||||||
|
export const nextTimeUnit = (currentUnit: number) => {
|
||||||
|
const units = Object.values(TimeSecond) as number[];
|
||||||
|
|
||||||
|
const index = units.indexOf(currentUnit);
|
||||||
|
return units[index + 1] || TimeSecond.Second;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a cooldown, for example 2min and transform it to seconds, here: 120s
|
* Take a cooldown, for example 2min and transform it to seconds, here: 120s
|
||||||
* @param time time in human format
|
* @param time time in human format
|
||||||
|
@ -32,32 +50,44 @@ enum TimeSecond {
|
||||||
*/
|
*/
|
||||||
export const strToSeconds = (time: string) => {
|
export const strToSeconds = (time: string) => {
|
||||||
if (time.length > 15) {
|
if (time.length > 15) {
|
||||||
// 15 is a magic value, weird to be this long
|
// 15 is a magic value as it's weird to have time this long
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const regex = new RegExp(
|
const noUnit = "unmarked";
|
||||||
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|(?<${
|
const regex = RegexC(
|
||||||
TimeSecond[TimeSecond.Week]
|
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|` +
|
||||||
}>[0-9]+(?=[w]))|(?<${TimeSecond[TimeSecond.Day]}>[0-9]+(?=[d|j]))|(?<${
|
`(?<${TimeSecond[TimeSecond.Week]}>[0-9]+(?=[w]))|` +
|
||||||
TimeSecond[TimeSecond.Hour]
|
`(?<${TimeSecond[TimeSecond.Day]}>[0-9]+(?=[d|j]))|` +
|
||||||
}>[0-9]+(?=[h]))|(?<${TimeSecond[TimeSecond.Minute]}>[0-9]+(?=[m]))|(?<${
|
`(?<${TimeSecond[TimeSecond.Hour]}>[0-9]+(?=[h]))|` +
|
||||||
TimeSecond[TimeSecond.Second]
|
`(?<${TimeSecond[TimeSecond.Minute]}>[0-9]+(?=[m]))|` +
|
||||||
}>[0-9]+(?=[s]?))`,
|
`(?<${TimeSecond[TimeSecond.Second]}>[0-9]+(?=[s]))|` +
|
||||||
|
`(?<${noUnit}>[0-9]+)`,
|
||||||
|
RegExpFlags.Global | RegExpFlags.Insensitive,
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = Object.assign({}, regex.exec(time.toLowerCase())?.groups);
|
const data = Array.from(time.matchAll(regex));
|
||||||
|
if (data.length === 0) {
|
||||||
if (Object.keys(data).length === 0) {
|
|
||||||
// Regex returned an invalid time
|
// Regex returned an invalid time
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = 0;
|
let res = 0;
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
let lastUnit = TimeSecond.Second;
|
||||||
if (value) {
|
data.forEach((match) => {
|
||||||
res += +value * TimeSecond[key as keyof typeof TimeSecond];
|
Object.entries(match.groups!).forEach(([key, value]) => {
|
||||||
}
|
if (value) {
|
||||||
|
let unit;
|
||||||
|
if (key === noUnit) {
|
||||||
|
unit = nextTimeUnit(lastUnit);
|
||||||
|
res += +value * unit;
|
||||||
|
} else {
|
||||||
|
unit = TimeSecond[key as keyof typeof TimeSecond];
|
||||||
|
res += +value * unit;
|
||||||
|
}
|
||||||
|
lastUnit = unit;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
Loading…
Reference in a new issue