chore: merge dev
to main
#181
15 changed files with 3792 additions and 66 deletions
|
@ -5,3 +5,5 @@
|
|||
!package-lock.json
|
||||
!LICENSE
|
||||
!tsconfig.json
|
||||
|
||||
src/tests
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
name: Lint and Format Check
|
||||
name: PR Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
@ -21,3 +21,6 @@ jobs:
|
|||
|
||||
- name: 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
|
||||
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)
|
||||
- [Soumettre ses modifications](#soumettre-ses-modifications)
|
||||
- [Gestion du dépôt](#gestion-du-dépôt)
|
||||
- [Tester son code](#tester-son-code)
|
||||
|
||||
## 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).
|
||||
- De préférences, suivre [ces conventions](https://www.conventionalcommits.org/fr/v1.0.0/)
|
||||
(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",
|
||||
"lint": "npx eslint src",
|
||||
"format-check": "npx prettier --check src",
|
||||
"format-write": "npx prettier --write src"
|
||||
"format-write": "npx prettier --write src",
|
||||
"test": "npx jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -25,15 +26,22 @@
|
|||
"discord-player": "^6.7.1",
|
||||
"discord.js": "^14.16.2",
|
||||
"mediaplex": "^0.0.9",
|
||||
"moment-timezone": "^0.5.45",
|
||||
"sqlite3": "^5.1.7",
|
||||
"typescript": "^5.6.2",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "~29.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "~8.6.0",
|
||||
"@typescript-eslint/parser": "~8.6.0",
|
||||
"dotenv": "~16.4.5",
|
||||
"jest": "~29.7.0",
|
||||
"prettier-eslint": "~16.3.0",
|
||||
"ts-jest": "~29.2.5",
|
||||
"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 { isImage, userWithNickname } from "../../utils/misc";
|
||||
import { showDate } from "../../utils/time";
|
||||
import { RegexC, RegExpFlags } from "../../utils/regex";
|
||||
|
||||
/** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-messageCreate */
|
||||
export default async (message: Message, client: Client) => {
|
||||
|
@ -24,7 +25,7 @@ export default async (message: Message, client: Client) => {
|
|||
/* Citation */
|
||||
const regex =
|
||||
/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
|
||||
if (!urls) {
|
||||
|
@ -42,7 +43,7 @@ export default async (message: Message, client: Client) => {
|
|||
}[] = [],
|
||||
match,
|
||||
) => {
|
||||
const [, guild_id, channel_id, message_id] = new RegExp(regex).exec(
|
||||
const [, guild_id, channel_id, message_id] = RegexC(regex).exec(
|
||||
match,
|
||||
) as RegExpExecArray;
|
||||
|
||||
|
@ -178,7 +179,7 @@ export default async (message: Message, client: Client) => {
|
|||
|
||||
// Delete source message if no content when removing links
|
||||
if (
|
||||
!message.content.replace(new RegExp(regex, "g"), "").trim() &&
|
||||
!message.content.replace(RegexC(regex, RegExpFlags.Global), "").trim() &&
|
||||
messages.length === urls.length &&
|
||||
!message.mentions.repliedUser &&
|
||||
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 { cleanCodeBlock } from "./misc";
|
||||
import { showDate, strToSeconds, timeDeltaToString } from "./time";
|
||||
import { RegexC, RegExpFlags } from "./regex";
|
||||
|
||||
/**
|
||||
* Option possible for reminders
|
||||
|
@ -45,14 +46,26 @@ export type dbReminder = {
|
|||
* @param time raw text from user
|
||||
* @returns An object with the time and the option
|
||||
*/
|
||||
const splitTime = (time: string) => {
|
||||
if (time?.endsWith("@")) {
|
||||
return { time: time.slice(0, -1), option: OptionReminder.Mention };
|
||||
} else if (time?.toLowerCase().endsWith("p")) {
|
||||
return { time: time.slice(0, -1), option: OptionReminder.DirectMessage };
|
||||
}
|
||||
export const splitTime = (time: string) => {
|
||||
const mapping = {
|
||||
[OptionReminder.DirectMessage]: "p",
|
||||
[OptionReminder.Mention]: "@",
|
||||
};
|
||||
|
||||
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
|
||||
* @param tz Lang
|
||||
|
@ -6,7 +9,10 @@
|
|||
* @returns String
|
||||
*/
|
||||
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, {
|
||||
timeZone: intlTimezone ? intlTimezone[0] : "Factory",
|
||||
dateStyle: "short",
|
||||
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]}`;
|
||||
};
|
||||
|
||||
enum TimeSecond {
|
||||
export enum TimeSecond {
|
||||
Year = 31536000,
|
||||
Week = 604800,
|
||||
Day = 86400,
|
||||
|
@ -25,6 +31,18 @@ enum TimeSecond {
|
|||
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
|
||||
* @param time time in human format
|
||||
|
@ -32,32 +50,44 @@ enum TimeSecond {
|
|||
*/
|
||||
export const strToSeconds = (time: string) => {
|
||||
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;
|
||||
}
|
||||
|
||||
const regex = new RegExp(
|
||||
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|(?<${
|
||||
TimeSecond[TimeSecond.Week]
|
||||
}>[0-9]+(?=[w]))|(?<${TimeSecond[TimeSecond.Day]}>[0-9]+(?=[d|j]))|(?<${
|
||||
TimeSecond[TimeSecond.Hour]
|
||||
}>[0-9]+(?=[h]))|(?<${TimeSecond[TimeSecond.Minute]}>[0-9]+(?=[m]))|(?<${
|
||||
TimeSecond[TimeSecond.Second]
|
||||
}>[0-9]+(?=[s]?))`,
|
||||
const noUnit = "unmarked";
|
||||
const regex = RegexC(
|
||||
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|` +
|
||||
`(?<${TimeSecond[TimeSecond.Week]}>[0-9]+(?=[w]))|` +
|
||||
`(?<${TimeSecond[TimeSecond.Day]}>[0-9]+(?=[d|j]))|` +
|
||||
`(?<${TimeSecond[TimeSecond.Hour]}>[0-9]+(?=[h]))|` +
|
||||
`(?<${TimeSecond[TimeSecond.Minute]}>[0-9]+(?=[m]))|` +
|
||||
`(?<${TimeSecond[TimeSecond.Second]}>[0-9]+(?=[s]))|` +
|
||||
`(?<${noUnit}>[0-9]+)`,
|
||||
RegExpFlags.Global | RegExpFlags.Insensitive,
|
||||
);
|
||||
|
||||
const data = Object.assign({}, regex.exec(time.toLowerCase())?.groups);
|
||||
|
||||
if (Object.keys(data).length === 0) {
|
||||
const data = Array.from(time.matchAll(regex));
|
||||
if (data.length === 0) {
|
||||
// Regex returned an invalid time
|
||||
return -1;
|
||||
}
|
||||
|
||||
let res = 0;
|
||||
Object.entries(data).forEach(([key, value]) => {
|
||||
let lastUnit = TimeSecond.Second;
|
||||
data.forEach((match) => {
|
||||
Object.entries(match.groups!).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
res += +value * TimeSecond[key as keyof typeof TimeSecond];
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue