Compare commits
2 commits
main
...
archive-re
Author | SHA1 | Date | |
---|---|---|---|
10fa611740 | |||
db7dd69de6 |
60 changed files with 611 additions and 5307 deletions
|
@ -1,2 +1 @@
|
||||||
dist
|
dist
|
||||||
tests
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
name: PR Check
|
name: Lint and Format Check
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
@ -14,13 +14,10 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm install
|
||||||
|
|
||||||
- name: Run lint
|
- name: Run lint
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
- 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
|
|
|
@ -32,6 +32,5 @@ jobs:
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -11,10 +11,7 @@ docker-compose.yml
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
# Databse
|
# Databse
|
||||||
*.sqlite3*
|
*.sqlite3
|
||||||
|
|
||||||
# Debug file
|
# Debug file
|
||||||
src/events/player/debug.ts
|
src/events/player/debug.ts
|
||||||
|
|
||||||
# Jest
|
|
||||||
coverage/
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ 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
|
||||||
|
|
||||||
|
@ -285,20 +284,3 @@ 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 */);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM node:22.9-bullseye-slim
|
FROM node:22.8-bullseye-slim
|
||||||
|
|
||||||
ENV DOCKERIZED=1
|
ENV DOCKERIZED=1
|
||||||
RUN mkdir /config && \
|
RUN mkdir /config && \
|
||||||
|
@ -14,7 +14,7 @@ COPY --chown=node:node . .
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
RUN npm ci --omit=dev && \
|
RUN npm ci --omit=dev && \
|
||||||
npm run compile && \
|
npx tsc && \
|
||||||
rm -r src/ tsconfig.json && \
|
rm -r src/ tsconfig.json && \
|
||||||
npm uninstall typescript @types/sqlite3 && \
|
npm uninstall typescript @types/sqlite3 && \
|
||||||
npm cache clean --force
|
npm cache clean --force
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# 🌱 Botanique [![status-badge](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/badges/workflows/publish.yml/badge.svg)](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/actions?workflow=publish.yml)
|
# 🌱 Botanique [![status-badge](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/badges/workflows/publish.yml/badge.svg)](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/actions?workflow=publish.yml)
|
||||||
|
|
||||||
[**Ajoute le bot à un serveur**](https://discord.com/api/oauth2/authorize?client_id=965598852407230494&permissions=8&scope=bot%20applications.commands)
|
[**Ajoute le bot à ton serveur**](https://discord.com/api/oauth2/authorize?client_id=965598852407230494&permissions=8&scope=bot%20applications.commands)
|
||||||
|
|
||||||
## Lancer le bot
|
## Lancer le bot
|
||||||
|
|
||||||
### Avec docker-compose (recommandé)
|
### Avec docker-compose (recommandé)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
botanique:
|
botanique:
|
||||||
image: git.mylloon.fr/confreriedukassoulait/botanique:latest
|
image: git.mylloon.fr/confreriedukassoulait/botanique:latest
|
||||||
|
|
3546
package-lock.json
generated
3546
package-lock.json
generated
File diff suppressed because it is too large
Load diff
27
package.json
27
package.json
|
@ -4,13 +4,11 @@
|
||||||
"description": "Bot discord",
|
"description": "Bot discord",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "rm -r dist 2> /dev/null; npx tsc && cp -r ./src/sql ./dist/sql",
|
"main": "rm -r dist 2> /dev/null; npx tsc && node ./dist/index.js",
|
||||||
"main": "npm run compile && node ./dist/index.js",
|
|
||||||
"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",
|
||||||
|
@ -21,26 +19,21 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discord-player/extractor": "^4.5.1",
|
"@discord-player/extractor": "^4.5.1",
|
||||||
"@discordjs/rest": "^2.4.0",
|
"@discordjs/rest": "^2.4.0",
|
||||||
|
"@types/sqlite3": "^3.1.11",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
"discord-player": "^6.7.1",
|
"discord-player": "^6.7.1",
|
||||||
"discord-player-youtubei": "^1.3.4",
|
"discord-player-youtubei": "^1.3.1",
|
||||||
"discord.js": "^14.16.3",
|
"discord.js": "^14.16.2",
|
||||||
"mediaplex": "^0.0.9",
|
"mediaplex": "^0.0.9",
|
||||||
"moment-timezone": "^0.5.46",
|
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.2",
|
||||||
"uuid": "^11.0.2"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "~29.5.14",
|
"@typescript-eslint/eslint-plugin": "~8.6.0",
|
||||||
"@typescript-eslint/eslint-plugin": "~8.12.2",
|
"@typescript-eslint/parser": "~8.6.0",
|
||||||
"@typescript-eslint/parser": "~8.12.2",
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ import { getLocale } from "../utils/locales";
|
||||||
|
|
||||||
export default async (client: Client) => {
|
export default async (client: Client) => {
|
||||||
// Dossier des buttons
|
// Dossier des buttons
|
||||||
const buttons_categories = (await readdir(__dirname, { withFileTypes: true }))
|
const buttons_categories = (await readdir(__dirname)).filter(
|
||||||
.filter((element) => element.isDirectory())
|
(element) => !element.endsWith(".js") && !element.endsWith(".ts"),
|
||||||
.map((element) => element.name);
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
// For each categorie
|
// For each categorie
|
||||||
|
|
|
@ -4,11 +4,12 @@ import {
|
||||||
ButtonStyle,
|
ButtonStyle,
|
||||||
Client,
|
Client,
|
||||||
MessageComponentInteraction,
|
MessageComponentInteraction,
|
||||||
|
User,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { getLocale } from "../../utils/locales";
|
import { getLocale } from "../../utils/locales";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
import { embedListReminders } from "../../utils/commands/reminder";
|
import { embedListReminders } from "../../utils/reminder";
|
||||||
import { collect } from "../loader";
|
import { collect } from "../loader";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -17,11 +18,11 @@ export default {
|
||||||
},
|
},
|
||||||
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
||||||
const loc = getLocale(client, interaction.locale);
|
const loc = getLocale(client, interaction.locale);
|
||||||
const embed_desc = interaction.message.embeds.at(0)?.description;
|
const embed_desc = interaction.message.embeds.at(0)?.description as string;
|
||||||
|
|
||||||
// Retrieve Pages
|
// Retrieve Pages
|
||||||
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc!)?.[0]);
|
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
|
||||||
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc!)?.[0]);
|
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
|
||||||
if (page + 1 > pageMax) {
|
if (page + 1 > pageMax) {
|
||||||
page = 1;
|
page = 1;
|
||||||
} else {
|
} else {
|
||||||
|
@ -29,8 +30,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve user
|
// Retrieve user
|
||||||
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc!)?.[0];
|
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
|
||||||
const user = client.users.cache.get(userId!)!;
|
const user = client.users.cache.get(userId) as User;
|
||||||
|
|
||||||
// Fetch list
|
// Fetch list
|
||||||
const list = await embedListReminders(
|
const list = await embedListReminders(
|
||||||
|
|
|
@ -4,11 +4,12 @@ import {
|
||||||
ButtonStyle,
|
ButtonStyle,
|
||||||
Client,
|
Client,
|
||||||
MessageComponentInteraction,
|
MessageComponentInteraction,
|
||||||
|
User,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { getLocale } from "../../utils/locales";
|
import { getLocale } from "../../utils/locales";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
import { embedListReminders } from "../../utils/commands/reminder";
|
import { embedListReminders } from "../../utils/reminder";
|
||||||
import { collect } from "../loader";
|
import { collect } from "../loader";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -17,20 +18,20 @@ export default {
|
||||||
},
|
},
|
||||||
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
||||||
const loc = getLocale(client, interaction.locale);
|
const loc = getLocale(client, interaction.locale);
|
||||||
const embed_desc = interaction.message.embeds.at(0)?.description;
|
const embed_desc = interaction.message.embeds.at(0)?.description as string;
|
||||||
|
|
||||||
// Retrieve Pages
|
// Retrieve Pages
|
||||||
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc!)?.[0]);
|
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
|
||||||
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc!)?.[0]);
|
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
|
||||||
if (page - 1 === 0) {
|
if (page - 1 == 0) {
|
||||||
page = pageMax;
|
page = pageMax;
|
||||||
} else {
|
} else {
|
||||||
page--;
|
page--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve user
|
// Retrieve user
|
||||||
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc!)?.[0];
|
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
|
||||||
const user = client.users.cache.get(userId!)!;
|
const user = client.users.cache.get(userId) as User;
|
||||||
|
|
||||||
// Fetch list
|
// Fetch list
|
||||||
const list = await embedListReminders(
|
const list = await embedListReminders(
|
||||||
|
|
|
@ -10,8 +10,8 @@ import {
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { getLocale } from "../../utils/locales";
|
import { getLocale } from "../../utils/locales";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
|
import { embedListQueue } from "../../utils/music";
|
||||||
import { collect } from "../loader";
|
import { collect } from "../loader";
|
||||||
import { embedListQueue } from "../../utils/commands/music";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: {
|
data: {
|
||||||
|
@ -19,11 +19,11 @@ export default {
|
||||||
},
|
},
|
||||||
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
||||||
const loc = getLocale(client, interaction.locale);
|
const loc = getLocale(client, interaction.locale);
|
||||||
const embed_desc = interaction.message.embeds.at(0)?.author?.name;
|
const embed_desc = interaction.message.embeds.at(0)?.author?.name as string;
|
||||||
|
|
||||||
// Retrieve Pages
|
// Retrieve Pages
|
||||||
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc!)?.[0]);
|
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
|
||||||
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc!)?.[0]);
|
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
|
||||||
if (page + 1 > pageMax) {
|
if (page + 1 > pageMax) {
|
||||||
page = 1;
|
page = 1;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,8 +10,8 @@ import {
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import { getLocale } from "../../utils/locales";
|
import { getLocale } from "../../utils/locales";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
|
import { embedListQueue } from "../../utils/music";
|
||||||
import { collect } from "../loader";
|
import { collect } from "../loader";
|
||||||
import { embedListQueue } from "../../utils/commands/music";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: {
|
data: {
|
||||||
|
@ -19,12 +19,12 @@ export default {
|
||||||
},
|
},
|
||||||
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
|
||||||
const loc = getLocale(client, interaction.locale);
|
const loc = getLocale(client, interaction.locale);
|
||||||
const embed_desc = interaction.message.embeds.at(0)!.author!.name;
|
const embed_desc = interaction.message.embeds.at(0)?.author?.name as string;
|
||||||
|
|
||||||
// Retrieve Pages
|
// Retrieve Pages
|
||||||
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
|
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
|
||||||
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
|
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
|
||||||
if (page - 1 === 0) {
|
if (page - 1 == 0) {
|
||||||
page = pageMax;
|
page = pageMax;
|
||||||
} else {
|
} else {
|
||||||
page--;
|
page--;
|
||||||
|
|
|
@ -8,9 +8,9 @@ import { removeExtension } from "../utils/misc";
|
||||||
export default async (client: Client) => {
|
export default async (client: Client) => {
|
||||||
const rest = new REST({ version: "10" }).setToken(client.token!);
|
const rest = new REST({ version: "10" }).setToken(client.token!);
|
||||||
|
|
||||||
const command_categories = (await readdir(__dirname, { withFileTypes: true }))
|
const command_categories = (await readdir(__dirname)).filter(
|
||||||
.filter((element) => element.isDirectory())
|
(element) => !element.endsWith(".js") && !element.endsWith(".ts"),
|
||||||
.map((element) => element.name);
|
);
|
||||||
|
|
||||||
const commands = (
|
const commands = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
|
@ -64,14 +64,14 @@ export default async (client: Client) => {
|
||||||
|
|
||||||
scopedCommands.forEach(
|
scopedCommands.forEach(
|
||||||
async (command, guild) =>
|
async (command, guild) =>
|
||||||
await rest.put(Routes.applicationGuildCommands(client.user!.id, guild), {
|
await rest.put(Routes.applicationGuildCommands(client.user?.id as string, guild), {
|
||||||
body: command,
|
body: command,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Send global commands to Discord
|
// Send global commands to Discord
|
||||||
const globalCommands = commands.filter((c) => c.scope().length === 0);
|
const globalCommands = commands.filter((c) => c.scope().length == 0);
|
||||||
return await rest.put(Routes.applicationCommands(client.user!.id), {
|
return await rest.put(Routes.applicationCommands(client.user?.id as string), {
|
||||||
body: globalCommands.map((c) => c.data.toJSON()),
|
body: globalCommands.map((c) => c.data.toJSON()),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,139 +0,0 @@
|
||||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
||||||
import {
|
|
||||||
ChannelType,
|
|
||||||
Client,
|
|
||||||
Colors,
|
|
||||||
CommandInteraction,
|
|
||||||
EmbedBuilder,
|
|
||||||
NonThreadGuildBasedChannel,
|
|
||||||
} from "discord.js";
|
|
||||||
import "../../modules/string";
|
|
||||||
import { getLocale, getLocalizations } from "../../utils/locales";
|
|
||||||
import { getFilename } from "../../utils/misc";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
scope: () => ["807244911350906920"],
|
|
||||||
|
|
||||||
data: (client: Client) => {
|
|
||||||
const filename = getFilename(__filename);
|
|
||||||
return (
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName(filename.toLowerCase())
|
|
||||||
.setDescription(client.locales.get(client.config.default_lang)!.get(`c_${filename}_desc`)!)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
|
||||||
|
|
||||||
// Command option
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName(
|
|
||||||
client.locales.get(client.config.default_lang)!.get(`c_${filename}_opt1_name`)!,
|
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
client.locales.get(client.config.default_lang)!.get(`c_${filename}_opt1_desc`)!,
|
|
||||||
)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`)),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
interaction: async (interaction: CommandInteraction, client: Client) => {
|
|
||||||
const loc = getLocale(client, interaction.locale);
|
|
||||||
const desiredCat = interaction.options.get(
|
|
||||||
client.locales
|
|
||||||
.get(client.config.default_lang)!
|
|
||||||
.get(`c_${getFilename(__filename)}_opt1_name`)!,
|
|
||||||
)?.value as string;
|
|
||||||
|
|
||||||
// If a category isn't specified
|
|
||||||
if (!desiredCat) {
|
|
||||||
// Sends a list of commands sorted into categories
|
|
||||||
return interaction.reply({
|
|
||||||
embeds: [
|
|
||||||
new EmbedBuilder()
|
|
||||||
.setColor(Colors.Blurple)
|
|
||||||
.setTitle(loc.get("c_archive1"))
|
|
||||||
.setDescription(loc.get("c_archive2")),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a category is specified
|
|
||||||
const cleanCat = ["L1", "L2", "L3", "M1", "M2"];
|
|
||||||
const channel = cleanCat.includes(desiredCat);
|
|
||||||
if (!channel) {
|
|
||||||
// Category doesn't exist or is not included
|
|
||||||
return interaction.reply({
|
|
||||||
content: `${loc.get("c_archive3")} \`${desiredCat}\``,
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const allChannel = interaction.guild?.channels.fetch();
|
|
||||||
allChannel?.then(async (channelGuild) => {
|
|
||||||
// Retrieve category to archive
|
|
||||||
const catToArchive = channelGuild
|
|
||||||
.filter((chan) => chan?.type === ChannelType.GuildCategory)
|
|
||||||
.filter((chan) => chan?.name === desiredCat);
|
|
||||||
|
|
||||||
// Create/Retrieve the archive category
|
|
||||||
const catArchivedName = "archive - " + desiredCat;
|
|
||||||
const catArchivedMap = channelGuild
|
|
||||||
.filter((chan) => chan?.type === ChannelType.GuildCategory)
|
|
||||||
.filter((chan) => chan?.name === catArchivedName);
|
|
||||||
|
|
||||||
let catArchived: NonThreadGuildBasedChannel | null | undefined;
|
|
||||||
if (catArchivedMap.size > 0) {
|
|
||||||
catArchived = catArchivedMap.at(0);
|
|
||||||
} else {
|
|
||||||
catArchived = await interaction.guild?.channels.create({
|
|
||||||
name: catArchivedName,
|
|
||||||
type: ChannelType.GuildCategory,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const allChannelDesired = channelGuild
|
|
||||||
.filter((chan) => chan?.type === 0)
|
|
||||||
.filter((chan) => chan?.parentId === catToArchive.map((cat) => cat?.id)[0]);
|
|
||||||
|
|
||||||
// If no channels in the source category
|
|
||||||
if (allChannelDesired.size === 0) {
|
|
||||||
return interaction.reply({
|
|
||||||
embeds: [
|
|
||||||
new EmbedBuilder()
|
|
||||||
.setColor(Colors.Blurple)
|
|
||||||
.setTitle(loc.get("c_archive6"))
|
|
||||||
.setDescription(loc.get("c_archive7")),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move channels to the archived categoryx
|
|
||||||
allChannelDesired.forEach((elem) => elem?.setParent(catArchived?.id as string));
|
|
||||||
|
|
||||||
return interaction.reply({
|
|
||||||
embeds: [
|
|
||||||
new EmbedBuilder()
|
|
||||||
.setColor(Colors.Blurple)
|
|
||||||
.setTitle(
|
|
||||||
loc.get("c_archive4") +
|
|
||||||
" `" +
|
|
||||||
catToArchive.map((cat) => cat?.name) +
|
|
||||||
"` " +
|
|
||||||
loc.get("c_archive5") +
|
|
||||||
" `" +
|
|
||||||
catArchivedName +
|
|
||||||
"`",
|
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
allChannelDesired
|
|
||||||
.map((cgD) => cgD?.name)
|
|
||||||
.toString()
|
|
||||||
.replaceAll(",", "\n"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,20 +1,9 @@
|
||||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
import {
|
import { Locale } from "discord-api-types/v9";
|
||||||
ApplicationCommandOptionType,
|
import { ChatInputCommandInteraction, Client, Colors, EmbedBuilder } from "discord.js";
|
||||||
ChatInputCommandInteraction,
|
|
||||||
Client,
|
|
||||||
Colors,
|
|
||||||
EmbedBuilder,
|
|
||||||
} from "discord.js";
|
|
||||||
import "../../modules/string";
|
import "../../modules/string";
|
||||||
import { getLocale, getLocalizations } from "../../utils/locales";
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
import {
|
|
||||||
goodDescription,
|
|
||||||
goodName,
|
|
||||||
NameNotLocalized,
|
|
||||||
SubnameNotLocalized,
|
|
||||||
} from "../../utils/commands/help";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
scope: () => [],
|
scope: () => [],
|
||||||
|
@ -59,35 +48,15 @@ export default {
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
// Load all the command per categories
|
// Load all the command per categories
|
||||||
|
// TODO: Check if the command exist in the context (guild)
|
||||||
|
// TODO: List subcommands too
|
||||||
client.commands.categories.forEach((commands_name, category) => {
|
client.commands.categories.forEach((commands_name, category) => {
|
||||||
// Check if the command exist in the context (guild)
|
const commands = commands_name.reduce((data, command_name) => {
|
||||||
commands_name = commands_name.filter((command) => {
|
return data + `\`/${command_name}\`, `;
|
||||||
const scope = client.commands.list.get(command)?.scope();
|
}, "");
|
||||||
return scope!.length === 0 || scope?.find((v) => v === interaction.guildId) !== undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add subcommands
|
|
||||||
const all_commands: string[] = [];
|
|
||||||
commands_name.forEach((command) => {
|
|
||||||
const data = client.commands.list.get(command)?.data;
|
|
||||||
const name = goodName(data!, interaction.locale);
|
|
||||||
all_commands.push(name);
|
|
||||||
|
|
||||||
data
|
|
||||||
?.toJSON()
|
|
||||||
.options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand)
|
|
||||||
.forEach((subcommand) =>
|
|
||||||
all_commands.push(name + " " + goodName(subcommand, interaction.locale)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const commands = all_commands.reduce(
|
|
||||||
(data, command_name) => data + `\`/${command_name}\`, `,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
|
|
||||||
fields.push({
|
fields.push({
|
||||||
name: category.capitalize() + ` (${all_commands.length})`,
|
name: category.capitalize() + ` (${commands_name.length})`,
|
||||||
value: commands.slice(0, -2),
|
value: commands.slice(0, -2),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -104,49 +73,29 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = `${loc.get("c_help3")} \`${desired_command}\``;
|
// If a command is specified
|
||||||
|
// TODO: Check if the command exist in the context (guild)
|
||||||
const [possible_command, possible_subcommand] = desired_command.split(" ");
|
const command = client.commands.list.get(desired_command);
|
||||||
|
|
||||||
const command = NameNotLocalized(client, possible_command);
|
|
||||||
if (!command) {
|
if (!command) {
|
||||||
return interaction.reply({ content: error, ephemeral: true });
|
// Command don't exist
|
||||||
|
return interaction.reply({
|
||||||
|
content: `${loc.get("c_help3")} \`${desired_command}\``,
|
||||||
|
ephemeral: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const scope = client.commands.list.get(command.name)?.scope();
|
|
||||||
if (scope!.length > 0 && scope?.find((id) => id === interaction.guildId) === undefined) {
|
|
||||||
// Command not available for the current guild
|
|
||||||
return interaction.reply({ content: error, ephemeral: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
let subcommand = undefined;
|
|
||||||
if (possible_subcommand) {
|
|
||||||
subcommand = SubnameNotLocalized(command, possible_subcommand);
|
|
||||||
} else {
|
|
||||||
subcommand = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!command || subcommand === undefined) {
|
|
||||||
// Sub/Command don't exist
|
|
||||||
return interaction.reply({ content: error, ephemeral: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loads the data according to the user's locals
|
|
||||||
const requestedName =
|
|
||||||
goodName(command, interaction.locale) +
|
|
||||||
(subcommand !== null ? " " + goodName(subcommand, interaction.locale) : "");
|
|
||||||
const requestedDesc = goodDescription(
|
|
||||||
subcommand !== null ? subcommand : command,
|
|
||||||
interaction.locale,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send information about the command
|
// Send information about the command
|
||||||
return interaction.reply({
|
return interaction.reply({
|
||||||
embeds: [
|
embeds: [
|
||||||
new EmbedBuilder()
|
new EmbedBuilder()
|
||||||
.setColor(Colors.Blurple)
|
.setColor(Colors.Blurple)
|
||||||
.setTitle("`/" + requestedName + "`")
|
.setTitle("`/" + command.data.name + "`")
|
||||||
.setDescription(requestedDesc),
|
.setDescription(
|
||||||
|
// Loads the description
|
||||||
|
// according to the user's locals
|
||||||
|
command.data.description_localizations?.[interaction.locale as Locale] ??
|
||||||
|
command.data.description,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
|
||||||
import { ChannelType, Client, Colors, CommandInteraction, EmbedBuilder } from "discord.js";
|
|
||||||
import "../../modules/string";
|
|
||||||
import { getLocale, getLocalizations } from "../../utils/locales";
|
|
||||||
import { getFilename } from "../../utils/misc";
|
|
||||||
|
|
||||||
export default {
|
|
||||||
scope: () => ["807244911350906920"],
|
|
||||||
|
|
||||||
data: (client: Client) => {
|
|
||||||
const filename = getFilename(__filename);
|
|
||||||
return (
|
|
||||||
new SlashCommandBuilder()
|
|
||||||
.setName(filename.toLowerCase())
|
|
||||||
.setDescription(client.locales.get(client.config.default_lang)!.get(`c_${filename}_desc`)!)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
|
||||||
|
|
||||||
// Command option
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName(
|
|
||||||
client.locales.get(client.config.default_lang)!.get(`c_${filename}_opt1_name`)!,
|
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
client.locales.get(client.config.default_lang)!.get(`c_${filename}_opt1_desc`)!,
|
|
||||||
)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`)),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
interaction: async (interaction: CommandInteraction, client: Client) => {
|
|
||||||
const loc = getLocale(client, interaction.locale);
|
|
||||||
const desired_cat = interaction.options.get(
|
|
||||||
client.locales
|
|
||||||
.get(client.config.default_lang)!
|
|
||||||
.get(`c_${getFilename(__filename)}_opt1_name`)!,
|
|
||||||
)?.value as string;
|
|
||||||
|
|
||||||
// If a category isn't specified
|
|
||||||
if (!desired_cat) {
|
|
||||||
// Sends a list of commands sorted into categories
|
|
||||||
return interaction.reply({
|
|
||||||
embeds: [
|
|
||||||
new EmbedBuilder()
|
|
||||||
.setColor(Colors.Blurple)
|
|
||||||
.setTitle(loc.get("c_prep1"))
|
|
||||||
.setDescription(loc.get("c_prep2")),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a category is specified
|
|
||||||
const allowedCategories = ["L1", "L2", "L3", "M1", "M2"];
|
|
||||||
const channel = allowedCategories.includes(desired_cat);
|
|
||||||
if (!channel) {
|
|
||||||
// Category doesn't exist or is not allowed
|
|
||||||
return interaction.reply({
|
|
||||||
content: `${loc.get("c_prep3")} \`${desired_cat}\``,
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send information about the command
|
|
||||||
const allChannel = interaction.guild?.channels.fetch();
|
|
||||||
allChannel?.then((channel_guild) => {
|
|
||||||
const cat_to_prep = channel_guild
|
|
||||||
.filter((chan) => chan?.type === ChannelType.GuildCategory)
|
|
||||||
.filter((chan) => chan?.name === desired_cat);
|
|
||||||
const cat_to_prep_id = cat_to_prep.map((cat) => cat?.id);
|
|
||||||
const cat_to_prep_name = cat_to_prep.map((cat) => cat?.name);
|
|
||||||
|
|
||||||
// console.log(cat_to_prep);
|
|
||||||
const all_channel_desired = channel_guild
|
|
||||||
.filter((chan) => chan?.type === 0)
|
|
||||||
.filter((chan) => chan?.parentId === cat_to_prep_id[0]);
|
|
||||||
const all_channel_desired_name = all_channel_desired.map((c_d) => c_d?.name);
|
|
||||||
|
|
||||||
let desc = "";
|
|
||||||
|
|
||||||
const general = "général";
|
|
||||||
if (all_channel_desired_name.filter((cdn) => cdn === general).length === 0) {
|
|
||||||
interaction.guild?.channels.create({
|
|
||||||
name: general,
|
|
||||||
type: 0,
|
|
||||||
parent: cat_to_prep_id[0],
|
|
||||||
});
|
|
||||||
desc = general + loc.get("c_prep5") + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = "informations";
|
|
||||||
if (all_channel_desired_name.filter((cdn) => cdn === info).length === 0) {
|
|
||||||
interaction.guild?.channels.create({
|
|
||||||
name: info,
|
|
||||||
type: 0,
|
|
||||||
parent: cat_to_prep_id[0],
|
|
||||||
});
|
|
||||||
|
|
||||||
desc += "`" + info + "` " + loc.get("c_prep5") + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (desc === "") {
|
|
||||||
desc = loc.get("c_prep6");
|
|
||||||
}
|
|
||||||
|
|
||||||
return interaction.reply({
|
|
||||||
embeds: [
|
|
||||||
new EmbedBuilder()
|
|
||||||
.setColor(Colors.Blurple)
|
|
||||||
.setTitle(loc.get("c_prep4") + cat_to_prep_name)
|
|
||||||
.setDescription(desc),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
embedListReminders,
|
embedListReminders,
|
||||||
getReminderInfo,
|
getReminderInfo,
|
||||||
newReminder,
|
newReminder,
|
||||||
} from "../../utils/commands/reminder";
|
} from "../../utils/reminder";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
scope: () => [],
|
scope: () => [],
|
||||||
|
@ -145,32 +145,25 @@ export default {
|
||||||
case loc_default?.get(`c_${filename}_sub1_name`)?.toLowerCase(): {
|
case loc_default?.get(`c_${filename}_sub1_name`)?.toLowerCase(): {
|
||||||
// If time is already renseigned
|
// If time is already renseigned
|
||||||
const time = interaction.options.getString(
|
const time = interaction.options.getString(
|
||||||
loc_default!.get(`c_${filename}_sub1_opt1_name`)!,
|
loc_default?.get(`c_${filename}_sub1_opt1_name`) as string,
|
||||||
);
|
);
|
||||||
if (time != null) {
|
if (time != null) {
|
||||||
// Use the cli because we already have enough data
|
// Use the cli because we already have enough data
|
||||||
return newReminder(client, time, {
|
return newReminder(client, time, {
|
||||||
locale: interaction.locale,
|
locale: interaction.locale,
|
||||||
message: interaction.options.getString(
|
message: interaction.options.getString(
|
||||||
loc_default!.get(`c_${filename}_sub1_opt2_name`)!,
|
loc_default?.get(`c_${filename}_sub1_opt2_name`) as string,
|
||||||
),
|
),
|
||||||
createdAt: interaction.createdAt.getTime(),
|
createdAt: interaction.createdAt.getTime(),
|
||||||
channelId: interaction.channelId,
|
channelId: interaction.channelId,
|
||||||
userId: interaction.user.id,
|
userId: interaction.user.id,
|
||||||
guildId: interaction.guildId,
|
guildId: interaction.guildId,
|
||||||
})
|
}).then((msg) =>
|
||||||
.then((msg) =>
|
|
||||||
interaction.reply({
|
interaction.reply({
|
||||||
content: msg as string,
|
content: msg as string,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
.catch((err) => {
|
|
||||||
interaction.reply({
|
|
||||||
content: err,
|
|
||||||
ephemeral: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// Show modal to user to get at least the time
|
// Show modal to user to get at least the time
|
||||||
const modal = new ModalBuilder()
|
const modal = new ModalBuilder()
|
||||||
|
@ -202,13 +195,17 @@ export default {
|
||||||
// List reminders
|
// List reminders
|
||||||
case loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase(): {
|
case loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase(): {
|
||||||
// Which user to show
|
// Which user to show
|
||||||
let user = interaction.options.getUser(loc_default!.get(`c_${filename}_sub2_opt1_name`)!);
|
let user = interaction.options.getUser(
|
||||||
if (user === null) {
|
loc_default?.get(`c_${filename}_sub2_opt1_name`) as string,
|
||||||
|
);
|
||||||
|
if (user == null) {
|
||||||
user = interaction.user;
|
user = interaction.user;
|
||||||
}
|
}
|
||||||
|
|
||||||
const page =
|
const page =
|
||||||
interaction.options.getInteger(loc_default!.get(`c_${filename}_sub2_opt2_name`)!) ?? 1;
|
interaction.options.getInteger(
|
||||||
|
loc_default?.get(`c_${filename}_sub2_opt2_name`) as string,
|
||||||
|
) ?? 1;
|
||||||
const list = await embedListReminders(
|
const list = await embedListReminders(
|
||||||
client,
|
client,
|
||||||
user,
|
user,
|
||||||
|
@ -249,7 +246,7 @@ export default {
|
||||||
// Delete a reminder
|
// Delete a reminder
|
||||||
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase(): {
|
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase(): {
|
||||||
const id = interaction.options.getInteger(
|
const id = interaction.options.getInteger(
|
||||||
loc_default!.get(`c_${filename}_sub3_opt1_name`)!,
|
loc_default?.get(`c_${filename}_sub3_opt1_name`) as string,
|
||||||
);
|
);
|
||||||
if (id === null) {
|
if (id === null) {
|
||||||
return interaction.reply({
|
return interaction.reply({
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
import { useMainPlayer, useQueue } from "discord-player";
|
import { Player, useMainPlayer, useQueue } from "discord-player";
|
||||||
import { ChatInputCommandInteraction, Client, EmbedBuilder, Message } from "discord.js";
|
import { ChatInputCommandInteraction, Client, EmbedBuilder } from "discord.js";
|
||||||
import { getLocale, getLocalizations } from "../../utils/locales";
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
import { discord_limit_message } from "../../utils/constants";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
scope: () => [],
|
scope: () => [],
|
||||||
|
@ -58,7 +57,7 @@ export default {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Synced start
|
// Synced
|
||||||
.addSubcommand((subcommand) =>
|
.addSubcommand((subcommand) =>
|
||||||
subcommand
|
subcommand
|
||||||
.setName(loc_default.get(`c_${filename}_sub3_name`)!.toLowerCase())
|
.setName(loc_default.get(`c_${filename}_sub3_name`)!.toLowerCase())
|
||||||
|
@ -66,15 +65,6 @@ export default {
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub3_name`, true))
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub3_name`, true))
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub3_desc`)),
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub3_desc`)),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Synced stop
|
|
||||||
.addSubcommand((subcommand) =>
|
|
||||||
subcommand
|
|
||||||
.setName(loc_default.get(`c_${filename}_sub4_name`)!.toLowerCase())
|
|
||||||
.setDescription(loc_default.get(`c_${filename}_sub4_desc`)!)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub4_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub4_desc`)),
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -84,16 +74,18 @@ export default {
|
||||||
|
|
||||||
const loc = getLocale(client, interaction.locale);
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
|
||||||
let request = interaction.options.getString(loc_default!.get(`c_${filename}_opt1_name`)!);
|
let request = interaction.options.getString(
|
||||||
|
loc_default?.get(`c_${filename}_opt1_name`) as string,
|
||||||
|
);
|
||||||
|
|
||||||
let data = null;
|
let data = null;
|
||||||
await interaction.deferReply();
|
await interaction.deferReply();
|
||||||
|
|
||||||
const player = useMainPlayer();
|
const player = useMainPlayer() as Player;
|
||||||
const queue = useQueue(interaction.guildId!);
|
const queue = useQueue(interaction.guildId!);
|
||||||
if (request) {
|
if (request) {
|
||||||
if (
|
if (
|
||||||
interaction.options.getSubcommand() ===
|
interaction.options.getSubcommand() ==
|
||||||
loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase()
|
loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase()
|
||||||
) {
|
) {
|
||||||
// Romanized
|
// Romanized
|
||||||
|
@ -119,7 +111,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
interaction.options.getSubcommand() ===
|
interaction.options.getSubcommand() ==
|
||||||
loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase()
|
loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase()
|
||||||
) {
|
) {
|
||||||
if (queue === null) {
|
if (queue === null) {
|
||||||
|
@ -133,78 +125,38 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load lyrics
|
// Load lyrics
|
||||||
if (queue.syncedLyricsMemory !== undefined) {
|
|
||||||
return await interaction.followUp(loc.get("c_lyrics9"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncedLyrics = queue.syncedLyrics(data[0]);
|
const syncedLyrics = queue.syncedLyrics(data[0]);
|
||||||
queue.syncedLyricsMemory = syncedLyrics;
|
|
||||||
|
|
||||||
let message: Message;
|
|
||||||
syncedLyrics?.onChange(async (lyrics) => {
|
syncedLyrics?.onChange(async (lyrics) => {
|
||||||
|
const content = `[${data[0].trackName}]: ${lyrics}`;
|
||||||
if (interaction.channel?.isSendable()) {
|
if (interaction.channel?.isSendable()) {
|
||||||
if (message) {
|
await interaction.channel?.send({
|
||||||
const payload = message.cleanContent + "\n" + lyrics;
|
content,
|
||||||
if (payload.length < discord_limit_message) {
|
});
|
||||||
message.edit(payload);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message = await interaction.channel?.send(
|
|
||||||
(message ? loc.get("c_lyrics6") + " " : "") +
|
|
||||||
`${data[0].artistName} : **${data[0].trackName}**\n\n` +
|
|
||||||
lyrics,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
await interaction.followUp(loc.get("c_lyrics5"));
|
await interaction.followUp({
|
||||||
|
content,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Live update
|
// Live update
|
||||||
syncedLyrics.subscribe();
|
syncedLyrics.subscribe();
|
||||||
|
|
||||||
syncedLyrics.onUnsubscribe(() => {
|
|
||||||
queue.syncedLyricsMemory = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
return await interaction.followUp({
|
return await interaction.followUp({
|
||||||
content: `🎤 | ${loc.get("c_lyrics4")}`,
|
content: `🎤 | ${loc.get("c_lyrics4")}`,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
interaction.options.getSubcommand() ===
|
|
||||||
loc_default?.get(`c_${filename}_sub4_name`)?.toLowerCase()
|
|
||||||
) {
|
|
||||||
if (queue === null) {
|
|
||||||
return await interaction.followUp(`❌ | ${loc.get("c_lyrics1")}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data === null || !data[0] || !data[0].syncedLyrics) {
|
|
||||||
return await interaction.followUp(
|
|
||||||
`❌ | ${loc.get("c_lyrics3")} \`${queue.currentTrack?.cleanTitle}\``,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load lyrics
|
|
||||||
if (queue.syncedLyricsMemory !== undefined && queue.syncedLyricsMemory.isSubscribed()) {
|
|
||||||
queue.syncedLyricsMemory.unsubscribe();
|
|
||||||
return await interaction.followUp(loc.get("c_lyrics7"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return await interaction.followUp(loc.get("c_lyrics8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data && data.length > 0 && data[0].plainLyrics !== null) {
|
if (data && data.length > 0 && data[0].plainLyrics !== null) {
|
||||||
const title = data[0];
|
const title = data[0];
|
||||||
const limit_desc = 4096;
|
const limit_desc = 4096;
|
||||||
const nb_embed = Math.ceil(title.plainLyrics.length / limit_desc);
|
const nb_embed = Math.ceil(title.plainLyrics.length / limit_desc);
|
||||||
|
|
||||||
// https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/issues/186
|
|
||||||
// TODO: If lyrics < 6000, only send one message with multiples embed
|
// TODO: If lyrics < 6000, only send one message with multiples embed
|
||||||
for (let i = 0, j = 0; i < nb_embed; i++, j += limit_desc) {
|
for (let i = 0, j = 0; i < nb_embed; i++, j += limit_desc) {
|
||||||
// + Better cut in lyrics
|
// TODO: Better cut in lyrics
|
||||||
const lyrics = title.plainLyrics.slice(j, j + limit_desc);
|
const lyrics = title.plainLyrics.slice(j, j + limit_desc);
|
||||||
|
|
||||||
let embed;
|
let embed;
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
import { SearchResult, useMainPlayer, useQueue } from "discord-player";
|
import { Player, SearchResult, useMainPlayer, useQueue } from "discord-player";
|
||||||
import {
|
import {
|
||||||
AutocompleteInteraction,
|
AutocompleteInteraction,
|
||||||
ChatInputCommandInteraction,
|
ChatInputCommandInteraction,
|
||||||
Client,
|
Client,
|
||||||
EmbedBuilder,
|
EmbedBuilder,
|
||||||
|
GuildResolvable,
|
||||||
|
VoiceBasedChannel,
|
||||||
} from "discord.js";
|
} from "discord.js";
|
||||||
import { getLocale, getLocalizations } from "../../utils/locales";
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
import { Metadata } from "../../utils/metadata";
|
import { Metadata } from "../../utils/metadata";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
import {
|
|
||||||
discord_limit_autocompletion_list_length,
|
|
||||||
discord_limit_autocompletion_value_length,
|
|
||||||
} from "../../utils/constants";
|
|
||||||
import { timeToString } from "../../utils/time";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
scope: () => [],
|
scope: () => [],
|
||||||
|
@ -32,14 +29,6 @@ export default {
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
|
||||||
|
|
||||||
// Normal
|
|
||||||
.addSubcommand((subcommand) =>
|
|
||||||
subcommand
|
|
||||||
.setName(loc_default.get(`c_${filename}_sub1_name`)!.toLowerCase())
|
|
||||||
.setDescription(loc_default.get(`c_${filename}_sub1_desc`)!)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub1_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub1_desc`))
|
|
||||||
|
|
||||||
// Command option
|
// Command option
|
||||||
.addStringOption((option) =>
|
.addStringOption((option) =>
|
||||||
option
|
option
|
||||||
|
@ -48,26 +37,6 @@ export default {
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`))
|
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`))
|
||||||
.setAutocomplete(true),
|
.setAutocomplete(true),
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
// Play now
|
|
||||||
.addSubcommand((subcommand) =>
|
|
||||||
subcommand
|
|
||||||
.setName(loc_default.get(`c_${filename}_sub2_name`)!.toLowerCase())
|
|
||||||
.setDescription(loc_default.get(`c_${filename}_sub2_desc`)!)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub2_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub2_desc`))
|
|
||||||
|
|
||||||
// Command option
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName(loc_default.get(`c_${filename}_opt1_name`)!.toLowerCase())
|
|
||||||
.setDescription(loc_default.get(`c_${filename}_opt1_desc`)!)
|
|
||||||
.setNameLocalizations(getLocalizations(client, `c_${filename}_opt1_name`, true))
|
|
||||||
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`))
|
|
||||||
.setAutocomplete(true),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -98,9 +67,11 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = interaction.options.getString(loc_default!.get(`c_${filename}_opt1_name`)!);
|
const query = interaction.options.getString(
|
||||||
|
loc_default?.get(`c_${filename}_opt1_name`) as string,
|
||||||
|
);
|
||||||
|
|
||||||
const player = useMainPlayer();
|
const player = useMainPlayer() as Player;
|
||||||
if (!query) {
|
if (!query) {
|
||||||
// Now playing
|
// Now playing
|
||||||
|
|
||||||
|
@ -126,8 +97,7 @@ export default {
|
||||||
return await interaction.reply({ embeds: [embed] });
|
return await interaction.reply({ embeds: [embed] });
|
||||||
}
|
}
|
||||||
|
|
||||||
const queue = player.nodes.create(interaction.guild!, {
|
const queue = player.nodes.create(interaction.guild as GuildResolvable, {
|
||||||
volume: 50,
|
|
||||||
defaultFFmpegFilters: ["silenceremove"],
|
defaultFFmpegFilters: ["silenceremove"],
|
||||||
metadata: {
|
metadata: {
|
||||||
channel: interaction.channel,
|
channel: interaction.channel,
|
||||||
|
@ -136,7 +106,7 @@ export default {
|
||||||
|
|
||||||
// Verify vc connection
|
// Verify vc connection
|
||||||
try {
|
try {
|
||||||
if (!queue.connection) await queue.connect(member.voice.channel!);
|
if (!queue.connection) await queue.connect(member.voice.channel as VoiceBasedChannel);
|
||||||
} catch {
|
} catch {
|
||||||
queue.delete();
|
queue.delete();
|
||||||
return await interaction.reply({
|
return await interaction.reply({
|
||||||
|
@ -164,15 +134,7 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
const track = result.tracks[0];
|
const track = result.tracks[0];
|
||||||
|
|
||||||
if (
|
|
||||||
interaction.options.getSubcommand() ===
|
|
||||||
loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase()
|
|
||||||
) {
|
|
||||||
queue.insertTrack(track, 0);
|
|
||||||
} else {
|
|
||||||
queue.addTrack(track);
|
queue.addTrack(track);
|
||||||
}
|
|
||||||
|
|
||||||
title = track.title;
|
title = track.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,29 +142,11 @@ export default {
|
||||||
queue.node.play();
|
queue.node.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
const positionEstimation = () => {
|
// TODO: When added to an existing queue (size of queue > 0):
|
||||||
const pos = queue.node.getTrackPosition(result.tracks[0]) + 1;
|
// - Add position in queue
|
||||||
|
// - Add estimated time until playing
|
||||||
if (pos === 0) {
|
|
||||||
return loc.get("c_play_sub2_name");
|
|
||||||
}
|
|
||||||
|
|
||||||
const estimation = timeToString(
|
|
||||||
[queue.currentTrack, ...queue.tracks.toArray()]
|
|
||||||
.filter((t) => t !== null)
|
|
||||||
.slice(0, pos)
|
|
||||||
.reduce((total, t) => {
|
|
||||||
if (total === 0) {
|
|
||||||
return queue.dispatcher ? t.durationMS - queue.dispatcher.streamTime : t.durationMS;
|
|
||||||
}
|
|
||||||
return total + t.durationMS;
|
|
||||||
}, 0),
|
|
||||||
);
|
|
||||||
return `${loc.get("c_play10")} ${pos} (${loc.get("c_play11")} ≈${estimation})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return await interaction.followUp({
|
return await interaction.followUp({
|
||||||
content: `⏱️ | \`${title}\` ${loc.get("c_play5")}, ${loc.get("c_play12")} ${positionEstimation()}.`,
|
content: `⏱️ | \`${title}\` ${loc.get("c_play5")}.`,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -211,13 +155,13 @@ export default {
|
||||||
const loc_default = interaction.client.locales.get(interaction.client.config.default_lang);
|
const loc_default = interaction.client.locales.get(interaction.client.config.default_lang);
|
||||||
const filename = getFilename(__filename);
|
const filename = getFilename(__filename);
|
||||||
|
|
||||||
const player = useMainPlayer();
|
const player = useMainPlayer() as Player;
|
||||||
const query = interaction.options.getString(loc_default!.get(`c_${filename}_opt1_name`)!, true);
|
const query = interaction.options.getString(
|
||||||
|
loc_default?.get(`c_${filename}_opt1_name`) as string,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
const limit_value_discord = discord_limit_autocompletion_value_length;
|
const limit_value_discord = 100;
|
||||||
const limit_element_discord = discord_limit_autocompletion_list_length;
|
|
||||||
|
|
||||||
const query_discord = query.slice(0, limit_value_discord);
|
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
/* Since Discord wanna receive a response within 3 secs and results is async
|
/* Since Discord wanna receive a response within 3 secs and results is async
|
||||||
|
@ -233,7 +177,7 @@ export default {
|
||||||
|
|
||||||
/* Create a race between a timeout and the search
|
/* Create a race between a timeout and the search
|
||||||
* At the end, Discord will always receive a response */
|
* At the end, Discord will always receive a response */
|
||||||
const tracks = await Promise.race([
|
let tracks = await Promise.race([
|
||||||
delay,
|
delay,
|
||||||
player.search(query, {
|
player.search(query, {
|
||||||
requestedBy: interaction.user,
|
requestedBy: interaction.user,
|
||||||
|
@ -250,18 +194,23 @@ export default {
|
||||||
|
|
||||||
// If tracks found
|
// If tracks found
|
||||||
if (tracks.length > 0) {
|
if (tracks.length > 0) {
|
||||||
const payload = tracks
|
if (tracks.length > 25) {
|
||||||
|
tracks = tracks
|
||||||
// Assure that URL is under the limit of Discord
|
// Assure that URL is under the limit of Discord
|
||||||
.filter((v) => v.url.length < limit_value_discord)
|
.filter((v) => v.url.length < limit_value_discord)
|
||||||
// Slice the list to respect the limit of Discord
|
// Slice the list if needed to the 25 first results
|
||||||
.slice(0, limit_element_discord - 1)
|
.slice(0, 25);
|
||||||
.map((t) => {
|
}
|
||||||
|
|
||||||
|
// Returns a list of songs with their title and author
|
||||||
|
return interaction.respond(
|
||||||
|
tracks.map((t) => {
|
||||||
let title = t.title;
|
let title = t.title;
|
||||||
let author = t.author;
|
let author = t.author;
|
||||||
let name = `${title} • ${author}`;
|
let name = `${title} • ${author}`;
|
||||||
|
|
||||||
// Slice returned data if needed to not exceed the length limit
|
// Slice returned data if needed to not exceed the length limit (100)
|
||||||
if (name.length > limit_value_discord) {
|
if (name.length > 100) {
|
||||||
const newTitle = title.substring(0, 40);
|
const newTitle = title.substring(0, 40);
|
||||||
if (title.length != newTitle.length) {
|
if (title.length != newTitle.length) {
|
||||||
title = `${newTitle}...`;
|
title = `${newTitle}...`;
|
||||||
|
@ -277,18 +226,12 @@ export default {
|
||||||
name,
|
name,
|
||||||
value: t.url,
|
value: t.url,
|
||||||
};
|
};
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
payload.unshift({
|
|
||||||
name: query_discord,
|
|
||||||
value: query_discord,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Returns a list of songs with their title and author
|
|
||||||
return interaction.respond(payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return interaction.respond([
|
||||||
return interaction.respond([{ name: loc.get("c_play9"), value: query_discord }]);
|
{ name: loc.get("c_play9"), value: query.slice(0, limit_value_discord) },
|
||||||
|
]);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { v4 as uuidv4 } from "uuid";
|
||||||
import { collect } from "../../buttons/loader";
|
import { collect } from "../../buttons/loader";
|
||||||
import { getLocale, getLocalizations } from "../../utils/locales";
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
import { embedListQueue } from "../../utils/commands/music";
|
import { embedListQueue } from "../../utils/music";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
scope: () => [],
|
scope: () => [],
|
||||||
|
@ -72,7 +72,6 @@ export default {
|
||||||
|
|
||||||
// Specified ID
|
// Specified ID
|
||||||
// TODO?: ID range -> as a string: 5-8 remove 5, 6, 7, 8
|
// TODO?: ID range -> as a string: 5-8 remove 5, 6, 7, 8
|
||||||
// https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/issues/185
|
|
||||||
.addNumberOption((option) =>
|
.addNumberOption((option) =>
|
||||||
option
|
option
|
||||||
.setName(loc_default.get(`c_${filename}_sub3_opt1_name`)!.toLowerCase())
|
.setName(loc_default.get(`c_${filename}_sub3_opt1_name`)!.toLowerCase())
|
||||||
|
@ -104,7 +103,9 @@ export default {
|
||||||
// Show the queue
|
// Show the queue
|
||||||
case loc_default?.get(`c_${filename}_sub1_name`)?.toLowerCase(): {
|
case loc_default?.get(`c_${filename}_sub1_name`)?.toLowerCase(): {
|
||||||
const page =
|
const page =
|
||||||
interaction.options.getNumber(loc_default!.get(`c_${filename}_sub1_opt1_name`)!) ?? 1;
|
interaction.options.getNumber(
|
||||||
|
loc_default?.get(`c_${filename}_sub1_opt1_name`) as string,
|
||||||
|
) ?? 1;
|
||||||
|
|
||||||
embedListQueue(client, embed, queue, page, interaction.locale);
|
embedListQueue(client, embed, queue, page, interaction.locale);
|
||||||
|
|
||||||
|
@ -151,8 +152,8 @@ export default {
|
||||||
// Remove <ID>
|
// Remove <ID>
|
||||||
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase(): {
|
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase(): {
|
||||||
const id = interaction.options.getNumber(
|
const id = interaction.options.getNumber(
|
||||||
loc_default!.get(`c_${filename}_sub3_opt1_name`)!,
|
loc_default?.get(`c_${filename}_sub3_opt1_name`) as string,
|
||||||
)!;
|
) as number;
|
||||||
|
|
||||||
const track = queue.removeTrack(id - 1);
|
const track = queue.removeTrack(id - 1);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||||
import { useMainPlayer } from "discord-player";
|
import { Player, useMainPlayer } from "discord-player";
|
||||||
import { ChatInputCommandInteraction, Client } from "discord.js";
|
import { ChatInputCommandInteraction, Client, GuildResolvable } from "discord.js";
|
||||||
import { getLocale, getLocalizations } from "../../utils/locales";
|
import { getLocale, getLocalizations } from "../../utils/locales";
|
||||||
|
import { Metadata } from "../../utils/metadata";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -24,11 +25,11 @@ export default {
|
||||||
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
|
||||||
const loc = getLocale(client, interaction.locale);
|
const loc = getLocale(client, interaction.locale);
|
||||||
|
|
||||||
const player = useMainPlayer();
|
const player = useMainPlayer() as Player;
|
||||||
const queue = player.nodes.create(interaction.guild!, {
|
const queue = player.nodes.create(interaction.guild as GuildResolvable, {
|
||||||
metadata: {
|
metadata: {
|
||||||
channel: interaction.channel,
|
channel: interaction.channel,
|
||||||
},
|
} as Metadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(queue.connection || queue.node.isPlaying())) {
|
if (!(queue.connection || queue.node.isPlaying())) {
|
||||||
|
|
|
@ -8,8 +8,7 @@ import {
|
||||||
sendReminder,
|
sendReminder,
|
||||||
setTimeoutReminder,
|
setTimeoutReminder,
|
||||||
updateReminder,
|
updateReminder,
|
||||||
} from "../../utils/commands/reminder";
|
} from "../../utils/reminder";
|
||||||
import { readSQL } from "../../utils/db";
|
|
||||||
|
|
||||||
export const once = true;
|
export const once = true;
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ export default async (client: Client) => {
|
||||||
// Restart all the timeout about reminders here
|
// Restart all the timeout about reminders here
|
||||||
new Promise((ok, ko) => {
|
new Promise((ok, ko) => {
|
||||||
// Fetch all reminders
|
// Fetch all reminders
|
||||||
client.db.all(readSQL("reminder/select"), [], (err, row) => {
|
client.db.all("SELECT * FROM reminder", [], (err, row) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
ko(err);
|
ko(err);
|
||||||
}
|
}
|
||||||
|
@ -42,18 +41,14 @@ export default async (client: Client) => {
|
||||||
} as infoReminder;
|
} as infoReminder;
|
||||||
|
|
||||||
if (element.expiration_date <= now) {
|
if (element.expiration_date <= now) {
|
||||||
sendReminder(client, info, element.option_id as OptionReminder)
|
|
||||||
.then(() =>
|
|
||||||
// Reminder expired
|
// Reminder expired
|
||||||
deleteReminder(client, element.creation_date, `${element.user_id}`).then((res) => {
|
deleteReminder(client, element.creation_date, `${element.user_id}`).then((res) => {
|
||||||
if (res != true) {
|
if (res != true) {
|
||||||
throw res;
|
throw res;
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
)
|
|
||||||
.catch((err) => {
|
|
||||||
throw err;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sendReminder(client, info, element.option_id as OptionReminder);
|
||||||
} else {
|
} else {
|
||||||
// Restart timeout
|
// Restart timeout
|
||||||
const timeoutId = setTimeoutReminder(
|
const timeoutId = setTimeoutReminder(
|
||||||
|
|
|
@ -1,37 +1,12 @@
|
||||||
import { PlayerEvents, useMainPlayer } from "discord-player";
|
import { Player, PlayerEvents, useMainPlayer } from "discord-player";
|
||||||
import { Client } from "discord.js";
|
import { Client } from "discord.js";
|
||||||
import { readdir } from "fs/promises";
|
import { readdir } from "fs/promises";
|
||||||
import { isDev, splitFilenameExtensions } from "../utils/misc";
|
|
||||||
|
|
||||||
/** Load all the events */
|
/** Load all the events */
|
||||||
export default async (client: Client) => {
|
export default async (client: Client) => {
|
||||||
const events_categories = (await readdir(__dirname, { withFileTypes: true }))
|
const events_categories = (await readdir(__dirname)).filter(
|
||||||
.filter((element) => element.isDirectory())
|
(element) => !element.endsWith(".js") && !element.endsWith(".ts"),
|
||||||
.map((element) => element.name);
|
);
|
||||||
|
|
||||||
const player = useMainPlayer();
|
|
||||||
|
|
||||||
if (isDev) {
|
|
||||||
player.on("debug", async (message) => {
|
|
||||||
console.log(`General player debug event: ${message}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
player.events.on("debug", async (_, message) => {
|
|
||||||
console.log(`Player debug event: ${message}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
player.events.on("error", (_, error) => {
|
|
||||||
// Emitted when the player queue encounters error
|
|
||||||
console.error(`General player error event: ${error.message}`);
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
player.events.on("playerError", (_, error) => {
|
|
||||||
// Emitted when the audio player errors while streaming audio track
|
|
||||||
console.error(`Player error event: ${error.message}`);
|
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
events_categories.forEach(async (event_category) => {
|
events_categories.forEach(async (event_category) => {
|
||||||
// Retrieve events
|
// Retrieve events
|
||||||
|
@ -45,12 +20,16 @@ export default async (client: Client) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove extension
|
// Remove extension
|
||||||
const { file: event_type, ext } = splitFilenameExtensions(event_file)!;
|
// TODO: use utils functions
|
||||||
|
const event_type_ext = event_file.split(".");
|
||||||
|
const ext = event_type_ext.pop();
|
||||||
if (!(ext === "js" || ext === "ts")) {
|
if (!(ext === "js" || ext === "ts")) {
|
||||||
throw `Unknown file in ${event_category}: ${event_file}`;
|
throw `Unknown file in ${event_category}: ${event_file}`;
|
||||||
}
|
}
|
||||||
|
const event_type = event_type_ext.join(".");
|
||||||
|
|
||||||
if (event_category === "player") {
|
if (event_category == "player") {
|
||||||
|
const player = useMainPlayer() as Player;
|
||||||
if (once) {
|
if (once) {
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
return player.events.once(event_type as keyof PlayerEvents, (...args: any[]) => {
|
return player.events.once(event_type as keyof PlayerEvents, (...args: any[]) => {
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { Client, EmbedBuilder, Message, TextBasedChannel } from "discord.js";
|
import { Client, EmbedBuilder, GuildMember, Message, TextBasedChannel } from "discord.js";
|
||||||
import { getLocale } from "../../utils/locales";
|
import { getLocale } from "../../utils/locales";
|
||||||
import { 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";
|
|
||||||
import { handleAttachments } from "../../utils/events/citation";
|
|
||||||
|
|
||||||
/** 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) => {
|
||||||
|
@ -26,7 +24,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(RegexC(regex, RegExpFlags.Global));
|
const urls = message.content.match(new RegExp(regex, "g"));
|
||||||
|
|
||||||
// Ignore message if there is no URLs
|
// Ignore message if there is no URLs
|
||||||
if (!urls) {
|
if (!urls) {
|
||||||
|
@ -44,7 +42,7 @@ export default async (message: Message, client: Client) => {
|
||||||
}[] = [],
|
}[] = [],
|
||||||
match,
|
match,
|
||||||
) => {
|
) => {
|
||||||
const [, guild_id, channel_id, message_id] = RegexC(regex).exec(
|
const [, guild_id, channel_id, message_id] = new RegExp(regex).exec(
|
||||||
match,
|
match,
|
||||||
) as RegExpExecArray;
|
) as RegExpExecArray;
|
||||||
|
|
||||||
|
@ -53,10 +51,10 @@ export default async (message: Message, client: Client) => {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const channel = message.guild.channels.cache.get(channel_id);
|
const channel = message.guild.channels.cache.get(channel_id) as TextBasedChannel;
|
||||||
|
|
||||||
// If channel doesn't exist in the guild and isn't text
|
// If channel doesn't exist in the guild and isn't text
|
||||||
if (!channel || !channel.isTextBased()) {
|
if (!channel) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,25 +65,12 @@ export default async (message: Message, client: Client) => {
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
.map(async ({ message_id, channel }) => {
|
.map(async ({ message_id, channel }) => {
|
||||||
let quoted_message = await channel.messages.fetch(message_id).catch(() => undefined);
|
const quoted_message = await channel.messages.fetch(message_id).catch(() => undefined);
|
||||||
|
|
||||||
// If it's a reference, we only check for reference once
|
|
||||||
const message_reference = quoted_message?.reference;
|
|
||||||
if (message_reference && message_reference.messageId) {
|
|
||||||
const channel_reference = client.channels.cache.get(message_reference.channelId);
|
|
||||||
if (!channel_reference?.isTextBased()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
quoted_message = await channel_reference.messages
|
|
||||||
.fetch(message_reference.messageId)
|
|
||||||
.catch(() => undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If message doesn't exist or empty
|
// If message doesn't exist or empty
|
||||||
if (
|
if (
|
||||||
!quoted_message ||
|
!quoted_message ||
|
||||||
(!quoted_message.content && quoted_message.attachments.size === 0)
|
(!quoted_message.content && quoted_message.attachments.size == 0)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -97,48 +82,60 @@ export default async (message: Message, client: Client) => {
|
||||||
// Remove undefined elements
|
// Remove undefined elements
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
const loc = getLocale(client);
|
const loc = getLocale(client, client.config.default_lang);
|
||||||
|
|
||||||
// Remove duplicates then map the quoted posts
|
// Remove duplicates then map the quoted posts
|
||||||
[...new Set(messages)]
|
[...new Set(messages)].map((quoted_post) => {
|
||||||
.filter((p) => p !== undefined)
|
|
||||||
.map((quoted_post) => {
|
|
||||||
const embed = new EmbedBuilder().setColor("#2f3136").setAuthor({
|
const embed = new EmbedBuilder().setColor("#2f3136").setAuthor({
|
||||||
name: "Citation",
|
name: "Citation",
|
||||||
iconURL: quoted_post.author.displayAvatarURL(),
|
iconURL: quoted_post?.author.displayAvatarURL(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle attachments
|
// Handle attachments
|
||||||
if (quoted_post.attachments.size !== 0) {
|
if (quoted_post?.attachments.size !== 0) {
|
||||||
handleAttachments(loc, embed, quoted_post.attachments);
|
if (
|
||||||
|
quoted_post?.attachments.size === 1 &&
|
||||||
|
isImage(quoted_post.attachments.first()?.name as string)
|
||||||
|
) {
|
||||||
|
// Only contains one image
|
||||||
|
embed.setImage(quoted_post.attachments.first()?.url as string);
|
||||||
|
} else {
|
||||||
|
// Contains more than one image and/or other files
|
||||||
|
let files = "";
|
||||||
|
quoted_post?.attachments.forEach((file) => (files += `[${file.name}](${file.url}), `));
|
||||||
|
embed.addFields({
|
||||||
|
// TODO: Don't pluralize when there is only one file.
|
||||||
|
// TODO: Locales
|
||||||
|
name: "Fichiers joints",
|
||||||
|
// TODO: Check if don't exceed char limit, if yes, split
|
||||||
|
// files into multiples field.
|
||||||
|
value: `${files.slice(0, -2)}.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Description as post content
|
// Description as post content
|
||||||
if (quoted_post.content) {
|
if (quoted_post?.content) {
|
||||||
// Only if content exists and length > 0
|
// Only if content exists and length > 0
|
||||||
embed.setDescription(quoted_post.content);
|
embed.setDescription(quoted_post?.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer
|
// Footer
|
||||||
let footer = `Posté le ${showDate(
|
let footer = `Posté le ${showDate(
|
||||||
message.guild?.preferredLocale ?? client.config.default_lang,
|
client.config.default_lang,
|
||||||
loc,
|
loc,
|
||||||
quoted_post.createdAt,
|
quoted_post?.createdAt as Date,
|
||||||
)}`;
|
|
||||||
if (quoted_post.editedAt) {
|
|
||||||
footer += ` et modifié le ${showDate(
|
|
||||||
message.guild?.preferredLocale ?? client.config.default_lang,
|
|
||||||
loc,
|
|
||||||
quoted_post.editedAt,
|
|
||||||
)}`;
|
)}`;
|
||||||
|
if (quoted_post?.editedAt) {
|
||||||
|
footer += ` et modifié le ${showDate(client.config.default_lang, loc, quoted_post.editedAt)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let author = "Auteur";
|
let author = "Auteur";
|
||||||
if (message.author === quoted_post.author) {
|
if (message.author == quoted_post?.author) {
|
||||||
author += " & Citateur";
|
author += " & Citateur";
|
||||||
} else {
|
} else {
|
||||||
footer += `\nCité par ${userWithNickname(message.member!) ?? "?"} le ${showDate(
|
footer += `\nCité par ${userWithNickname(message.member as GuildMember) ?? "?"} le ${showDate(
|
||||||
message.guild?.preferredLocale ?? client.config.default_lang,
|
client.config.default_lang,
|
||||||
loc,
|
loc,
|
||||||
message.createdAt,
|
message.createdAt,
|
||||||
)}`;
|
)}`;
|
||||||
|
@ -153,19 +150,19 @@ export default async (message: Message, client: Client) => {
|
||||||
embed.addFields(
|
embed.addFields(
|
||||||
{
|
{
|
||||||
name: author,
|
name: author,
|
||||||
value: `${quoted_post.author}`,
|
value: `${quoted_post?.author}`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Message",
|
name: "Message",
|
||||||
value: `${quoted_post.channel} - [Lien Message](${quoted_post.url})`,
|
value: `${quoted_post?.channel} - [Lien Message](${quoted_post?.url})`,
|
||||||
inline: true,
|
inline: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Delete source message if no content when removing links
|
// Delete source message if no content when removing links
|
||||||
if (
|
if (
|
||||||
!message.content.replace(RegexC(regex, RegExpFlags.Global), "").trim() &&
|
!message.content.replace(new RegExp(regex, "g"), "").trim() &&
|
||||||
messages.length === urls.length &&
|
messages.length === urls.length &&
|
||||||
!message.mentions.repliedUser &&
|
!message.mentions.repliedUser &&
|
||||||
message.channel.isSendable()
|
message.channel.isSendable()
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { isDev } from "./utils/misc";
|
const isDev = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
/** Load the app */
|
/** Load the app */
|
||||||
const start_app = () => {
|
const start_app = () => {
|
||||||
import("./load").then((l) => l.run().catch((error) => console.error(error)));
|
import("./load").then((l) => l.run(isDev).catch((error) => console.error(error)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Load .env if not in prod
|
// Load .env if not in prod
|
||||||
|
|
14
src/load.ts
14
src/load.ts
|
@ -4,10 +4,10 @@ import loadEvents from "./events/loader";
|
||||||
import loadModals from "./modals/loader";
|
import loadModals from "./modals/loader";
|
||||||
import loadClient, { quit } from "./utils/client";
|
import loadClient, { quit } from "./utils/client";
|
||||||
|
|
||||||
import { isDev, logStart } from "./utils/misc";
|
import { logStart } from "./utils/misc";
|
||||||
|
|
||||||
/** Run the bot */
|
/** Run the bot */
|
||||||
export const run = async () => {
|
export const run = async (isDev: boolean) => {
|
||||||
console.log("Starting Botanique...");
|
console.log("Starting Botanique...");
|
||||||
|
|
||||||
// Client Discord.JS
|
// Client Discord.JS
|
||||||
|
@ -61,11 +61,11 @@ export const run = async () => {
|
||||||
console.log(logStart(client_name, true));
|
console.log(logStart(client_name, true));
|
||||||
console.log(`Botanique "${client.user?.username}" v${client.config.version} started!`);
|
console.log(`Botanique "${client.user?.username}" v${client.config.version} started!`);
|
||||||
|
|
||||||
// Handle quit
|
// ^C
|
||||||
process.on("exit", () => quit(client));
|
process.on("SIGINT", () => quit(client));
|
||||||
process.on("SIGHUP", () => process.exit(128 + 1));
|
|
||||||
process.on("SIGINT", () => process.exit(128 + 2));
|
// Container force closed
|
||||||
process.on("SIGTERM", () => process.exit(128 + 15));
|
process.on("SIGTERM", () => quit(client));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
|
@ -17,29 +17,6 @@
|
||||||
"c_help2": "`/help <command>` to get more information about a command.",
|
"c_help2": "`/help <command>` to get more information about a command.",
|
||||||
"c_help3": "Can't find :",
|
"c_help3": "Can't find :",
|
||||||
|
|
||||||
"c_archive_name": "clean",
|
|
||||||
"c_archive_desc": "Clean category for the new year",
|
|
||||||
"c_archive_opt1_name": "category",
|
|
||||||
"c_archive_opt1_desc": "Name of the category to be cleaned",
|
|
||||||
"c_archive1": "List of categories subject to cleaning",
|
|
||||||
"c_archive2": "`L1`, `L2`, `L3`, `M1`, `M2`",
|
|
||||||
"c_archive3": "Unable to find/clean the channel:",
|
|
||||||
"c_archive4": "List of archived channels in the category",
|
|
||||||
"c_archive5": "to",
|
|
||||||
"c_archive6": "Cleaning",
|
|
||||||
"c_archive7": "Category already cleaned",
|
|
||||||
|
|
||||||
"c_prep_name": "Preparation",
|
|
||||||
"c_prep_desc": "Preparation of general channels for the new year",
|
|
||||||
"c_prep_opt1_name": "year",
|
|
||||||
"c_prep_opt1_desc": "Name of the year to be prepared",
|
|
||||||
"c_prep1": "List of categories submitted to the preparation",
|
|
||||||
"c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`",
|
|
||||||
"c_prep3": "Unable to find/clean the channel:",
|
|
||||||
"c_prep4": "Lists of prepared channels `",
|
|
||||||
"c_prep5": "created",
|
|
||||||
"c_prep6": "No preparation is required",
|
|
||||||
|
|
||||||
"u_time_at": "at",
|
"u_time_at": "at",
|
||||||
|
|
||||||
"c_reminder_name": "reminder",
|
"c_reminder_name": "reminder",
|
||||||
|
@ -77,14 +54,9 @@
|
||||||
"c_reminder15": "Message sent in DM because you have left",
|
"c_reminder15": "Message sent in DM because you have left",
|
||||||
"c_reminder16": "Message sent in DM because the Discord guild is no longer available.",
|
"c_reminder16": "Message sent in DM because the Discord guild is no longer available.",
|
||||||
"c_reminder17": "Message from",
|
"c_reminder17": "Message from",
|
||||||
"c_reminder18": "Invalid time, try again.",
|
|
||||||
|
|
||||||
"c_play_name": "play",
|
"c_play_name": "play",
|
||||||
"c_play_desc": "Plays a song/playlist, no query displays the now playing song",
|
"c_play_desc": "Plays a song/playlist, no query displays the now playing song",
|
||||||
"c_play_sub1_name": "add",
|
|
||||||
"c_play_sub1_desc": "Adds song/playlist to the queue",
|
|
||||||
"c_play_sub2_name": "now",
|
|
||||||
"c_play_sub2_desc": "Adds the song/playlist to the beginning of the queue",
|
|
||||||
"c_play_opt1_name": "query",
|
"c_play_opt1_name": "query",
|
||||||
"c_play_opt1_desc": "What you want to listen to",
|
"c_play_opt1_desc": "What you want to listen to",
|
||||||
"c_play1": "You're not on any vocal channels.",
|
"c_play1": "You're not on any vocal channels.",
|
||||||
|
@ -96,9 +68,6 @@
|
||||||
"c_play7": "Currently playing",
|
"c_play7": "Currently playing",
|
||||||
"c_play8": "Asked by",
|
"c_play8": "Asked by",
|
||||||
"c_play9": "No results were found",
|
"c_play9": "No results were found",
|
||||||
"c_play10": "in position",
|
|
||||||
"c_play11": "estimation",
|
|
||||||
"c_play12": "play",
|
|
||||||
|
|
||||||
"c_stop_name": "stop",
|
"c_stop_name": "stop",
|
||||||
"c_stop_desc": "Stop the music",
|
"c_stop_desc": "Stop the music",
|
||||||
|
@ -151,19 +120,12 @@
|
||||||
"c_lyrics_sub2_desc": "Search for romanized lyrics (e.g., hangul → Latin)",
|
"c_lyrics_sub2_desc": "Search for romanized lyrics (e.g., hangul → Latin)",
|
||||||
"c_lyrics_sub3_name": "synced",
|
"c_lyrics_sub3_name": "synced",
|
||||||
"c_lyrics_sub3_desc": "Synchronized lyrics search (updates in live)",
|
"c_lyrics_sub3_desc": "Synchronized lyrics search (updates in live)",
|
||||||
"c_lyrics_sub4_name": "stop-synced",
|
|
||||||
"c_lyrics_sub4_desc": "Stop Synchronized lyrics",
|
|
||||||
"c_lyrics_opt1_name": "song",
|
"c_lyrics_opt1_name": "song",
|
||||||
"c_lyrics_opt1_desc": "Wanted song",
|
"c_lyrics_opt1_desc": "Wanted song",
|
||||||
"c_lyrics1": "The bot is not playing anything at the moment, and no songs are specified.",
|
"c_lyrics1": "The bot is not playing anything at the moment, and no songs are specified.",
|
||||||
"c_lyrics2": "Unable to find the lyrics for",
|
"c_lyrics2": "Unable to find the lyrics for",
|
||||||
"c_lyrics3": "Unable to find synchronized lyrics for",
|
"c_lyrics3": "Unable to find synchronized lyrics for",
|
||||||
"c_lyrics4": "It's karaoke time!",
|
"c_lyrics4": "It's karaoke time!",
|
||||||
"c_lyrics5": "Unable to post the lyrics here.",
|
|
||||||
"c_lyrics6": "More of :",
|
|
||||||
"c_lyrics7": "Stop synchronized lyrics.",
|
|
||||||
"c_lyrics8": "No synchronized lyrics currently posted.",
|
|
||||||
"c_lyrics9": "Synchronized lyrics currently posted.",
|
|
||||||
|
|
||||||
"c_repeat_name": "repeat",
|
"c_repeat_name": "repeat",
|
||||||
"c_repeat_desc": "Command for the type of music repetition",
|
"c_repeat_desc": "Command for the type of music repetition",
|
||||||
|
@ -183,8 +145,5 @@
|
||||||
"c_repeat6": "enabled",
|
"c_repeat6": "enabled",
|
||||||
|
|
||||||
"e_trackstart1": "Asked by",
|
"e_trackstart1": "Asked by",
|
||||||
"e_trackstart2": "Duration :",
|
"e_trackstart2": "Duration :"
|
||||||
|
|
||||||
"e_attachement": "Attachement",
|
|
||||||
"e_attachements": "Attachements"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,29 +17,6 @@
|
||||||
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
|
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
|
||||||
"c_help3": "Impossible de trouver :",
|
"c_help3": "Impossible de trouver :",
|
||||||
|
|
||||||
"c_archive_name": "Nettoyer",
|
|
||||||
"c_archive_desc": "Nettoyage pour le passage à niveau",
|
|
||||||
"c_archive_opt1_name": "catégorie",
|
|
||||||
"c_archive_opt1_desc": "Nom de la catégorie à nettoyer",
|
|
||||||
"c_archive1": "Liste des catégories soumises au nettoyage",
|
|
||||||
"c_archive2": "`L1`, `L2`, `L3`, `M1`, `M2`",
|
|
||||||
"c_archive3": "Impossible de trouver/nettoyer le salon :",
|
|
||||||
"c_archive4": "Liste des salons archivés de la catégorie",
|
|
||||||
"c_archive5": "vers",
|
|
||||||
"c_archive6": "Nettoyage",
|
|
||||||
"c_archive7": "Catégorie déjà nettoyée",
|
|
||||||
|
|
||||||
"c_prep_name": "Préparation",
|
|
||||||
"c_prep_desc": "Préparation des salons généraux pour la nouvelle année",
|
|
||||||
"c_prep_opt1_name": "année",
|
|
||||||
"c_prep_opt1_desc": "Nom de l'année à préparer",
|
|
||||||
"c_prep1": "Liste des catégories soumises à la préparation",
|
|
||||||
"c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`",
|
|
||||||
"c_prep3": "Impossible de trouver/nettoyer le salon :",
|
|
||||||
"c_prep4": "Listes des salons préparés `",
|
|
||||||
"c_prep5": "créé",
|
|
||||||
"c_prep6": "Pas besoin de préparation",
|
|
||||||
|
|
||||||
"u_time_at": "à",
|
"u_time_at": "à",
|
||||||
|
|
||||||
"c_reminder_name": "rappel",
|
"c_reminder_name": "rappel",
|
||||||
|
@ -77,14 +54,9 @@
|
||||||
"c_reminder15": "Message envoyé en DM car vous avez quitté",
|
"c_reminder15": "Message envoyé en DM car vous avez quitté",
|
||||||
"c_reminder16": "Message envoyé en DM car le serveur Discord n'est plus disponible.",
|
"c_reminder16": "Message envoyé en DM car le serveur Discord n'est plus disponible.",
|
||||||
"c_reminder17": "Message d'il y a",
|
"c_reminder17": "Message d'il y a",
|
||||||
"c_reminder18": "Temps invalide, réessayez.",
|
|
||||||
|
|
||||||
"c_play_name": "play",
|
"c_play_name": "play",
|
||||||
"c_play_desc": "Joue une chanson/playlist, pas de requête affiche la chanson en cours actuellement",
|
"c_play_desc": "Joue une chanson/playlist, pas de requête affiche la chanson en cours actuellement",
|
||||||
"c_play_sub1_name": "ajouter",
|
|
||||||
"c_play_sub1_desc": "Ajoute la chanson/playlist à la file d'attente",
|
|
||||||
"c_play_sub2_name": "maintenant",
|
|
||||||
"c_play_sub2_desc": "Ajoute la chanson/playlist au début de la file",
|
|
||||||
"c_play_opt1_name": "requête",
|
"c_play_opt1_name": "requête",
|
||||||
"c_play_opt1_desc": "Ce que vous voulez écouter",
|
"c_play_opt1_desc": "Ce que vous voulez écouter",
|
||||||
"c_play1": "Tu n'es dans aucun salon vocal.",
|
"c_play1": "Tu n'es dans aucun salon vocal.",
|
||||||
|
@ -96,9 +68,6 @@
|
||||||
"c_play7": "Joue actuellement",
|
"c_play7": "Joue actuellement",
|
||||||
"c_play8": "Demandé par",
|
"c_play8": "Demandé par",
|
||||||
"c_play9": "Aucun résultat trouvé",
|
"c_play9": "Aucun résultat trouvé",
|
||||||
"c_play10": "en position",
|
|
||||||
"c_play11": "estimation",
|
|
||||||
"c_play12": "joue",
|
|
||||||
|
|
||||||
"c_stop_name": "stop",
|
"c_stop_name": "stop",
|
||||||
"c_stop_desc": "Stop la musique",
|
"c_stop_desc": "Stop la musique",
|
||||||
|
@ -151,19 +120,12 @@
|
||||||
"c_lyrics_sub2_desc": "Recherche de paroles romanisées (ex: hangul → latin)",
|
"c_lyrics_sub2_desc": "Recherche de paroles romanisées (ex: hangul → latin)",
|
||||||
"c_lyrics_sub3_name": "synced",
|
"c_lyrics_sub3_name": "synced",
|
||||||
"c_lyrics_sub3_desc": "Recherche de paroles synchronisées (se mettent à jour avec la chanson en direct)",
|
"c_lyrics_sub3_desc": "Recherche de paroles synchronisées (se mettent à jour avec la chanson en direct)",
|
||||||
"c_lyrics_sub4_name": "stop-synced",
|
|
||||||
"c_lyrics_sub4_desc": "Arrête les paroles synchronisées",
|
|
||||||
"c_lyrics_opt1_name": "chanson",
|
"c_lyrics_opt1_name": "chanson",
|
||||||
"c_lyrics_opt1_desc": "Chanson recherchée",
|
"c_lyrics_opt1_desc": "Chanson recherchée",
|
||||||
"c_lyrics1": "Le bot ne joue rien en ce moment et aucune chanson n'est renseignée.",
|
"c_lyrics1": "Le bot ne joue rien en ce moment et aucune chanson n'est renseignée.",
|
||||||
"c_lyrics2": "Impossible de trouver les paroles pour",
|
"c_lyrics2": "Impossible de trouver les paroles pour",
|
||||||
"c_lyrics3": "Impossible de trouver les paroles synchronisées pour",
|
"c_lyrics3": "Impossible de trouver les paroles synchronisées pour",
|
||||||
"c_lyrics4": "C'est parti !",
|
"c_lyrics4": "C'est parti !",
|
||||||
"c_lyrics5": "Impossible de poster les paroles ici.",
|
|
||||||
"c_lyrics6": "Suite de :",
|
|
||||||
"c_lyrics7": "Arrêt des paroles synchronisées.",
|
|
||||||
"c_lyrics8": "Pas de paroles synchronisées en cours.",
|
|
||||||
"c_lyrics9": "Paroles synchronisées déjà en cours.",
|
|
||||||
|
|
||||||
"c_repeat_name": "repeat",
|
"c_repeat_name": "repeat",
|
||||||
"c_repeat_desc": "Commande relative à la répétition des musiques",
|
"c_repeat_desc": "Commande relative à la répétition des musiques",
|
||||||
|
@ -183,8 +145,5 @@
|
||||||
"c_repeat6": "activé",
|
"c_repeat6": "activé",
|
||||||
|
|
||||||
"e_trackstart1": "Demandé par",
|
"e_trackstart1": "Demandé par",
|
||||||
"e_trackstart2": "Durée :",
|
"e_trackstart2": "Durée :"
|
||||||
|
|
||||||
"e_attachement": "Fichier joint",
|
|
||||||
"e_attachements": "Fichiers joint"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
import { Client, ModalSubmitInteraction } from "discord.js";
|
import { Client, ModalSubmitInteraction } from "discord.js";
|
||||||
import { getFilename } from "../../utils/misc";
|
import { getFilename } from "../../utils/misc";
|
||||||
import { newReminder } from "../../utils/commands/reminder";
|
import { newReminder } from "../../utils/reminder";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: {
|
data: {
|
||||||
name: getFilename(__filename),
|
name: getFilename(__filename),
|
||||||
},
|
},
|
||||||
interaction: async (interaction: ModalSubmitInteraction, client: Client) => {
|
interaction: async (interaction: ModalSubmitInteraction, client: Client) =>
|
||||||
const message = interaction.fields.fields.get("reminderGUI-message")?.value;
|
newReminder(client, interaction.fields.fields.get("reminderGUI-time")?.value as string, {
|
||||||
|
|
||||||
return newReminder(client, interaction.fields.fields.get("reminderGUI-time")!.value, {
|
|
||||||
locale: interaction.locale,
|
locale: interaction.locale,
|
||||||
message: message ? (message.length > 0 ? message : null) : null,
|
message: interaction.fields.fields.get("reminderGUI-message")?.value ?? null,
|
||||||
createdAt: interaction.createdAt.getTime(),
|
createdAt: interaction.createdAt.getTime(),
|
||||||
channelId: interaction.channelId,
|
channelId: interaction.channelId,
|
||||||
userId: interaction.user.id,
|
userId: interaction.user.id,
|
||||||
|
@ -21,6 +19,5 @@ export default {
|
||||||
content: msg as string,
|
content: msg as string,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
}),
|
}),
|
||||||
);
|
),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,8 +75,6 @@ declare module "discord.js" {
|
||||||
string,
|
string,
|
||||||
/** Command itself */
|
/** Command itself */
|
||||||
{
|
{
|
||||||
/** Guilds where the command is active */
|
|
||||||
scope: () => string[];
|
|
||||||
/** Data about the command */
|
/** Data about the command */
|
||||||
data: SlashCommandBuilder;
|
data: SlashCommandBuilder;
|
||||||
/** How the command interact */
|
/** How the command interact */
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
export {};
|
|
||||||
|
|
||||||
declare module "discord-player" {
|
|
||||||
export interface GuildQueue {
|
|
||||||
syncedLyricsMemory:
|
|
||||||
| {
|
|
||||||
isSubscribed: () => unknown;
|
|
||||||
unsubscribe: () => unknown;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,9 +12,5 @@ declare global {
|
||||||
|
|
||||||
/** Capitalize definition */
|
/** Capitalize definition */
|
||||||
String.prototype.capitalize = function (this: string) {
|
String.prototype.capitalize = function (this: string) {
|
||||||
if (this.length === 0) {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this[0].toUpperCase() + this.substring(1);
|
return this[0].toUpperCase() + this.substring(1);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
CREATE TABLE IF NOT EXISTS
|
|
||||||
reminder (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
data TEXT,
|
|
||||||
expiration_date TEXT,
|
|
||||||
option_id INTEGER,
|
|
||||||
channel_id TEXT,
|
|
||||||
creation_date TEXT,
|
|
||||||
user_id TEXT,
|
|
||||||
guild_id TEXT,
|
|
||||||
locale TEXT,
|
|
||||||
timeout_id TEXT
|
|
||||||
);
|
|
|
@ -1,14 +0,0 @@
|
||||||
INSERT INTO
|
|
||||||
reminder (
|
|
||||||
data,
|
|
||||||
expiration_date,
|
|
||||||
option_id,
|
|
||||||
channel_id,
|
|
||||||
creation_date,
|
|
||||||
user_id,
|
|
||||||
guild_id,
|
|
||||||
locale,
|
|
||||||
timeout_id
|
|
||||||
)
|
|
||||||
VALUES
|
|
||||||
(?, ?, ?, ?, ?, ?, ?, ?, ?);
|
|
|
@ -1,13 +0,0 @@
|
||||||
SELECT
|
|
||||||
data,
|
|
||||||
creation_date,
|
|
||||||
expiration_date,
|
|
||||||
id
|
|
||||||
FROM
|
|
||||||
reminder
|
|
||||||
WHERE
|
|
||||||
user_id = ?
|
|
||||||
AND (
|
|
||||||
guild_id = ?
|
|
||||||
OR guild_id = 0
|
|
||||||
)
|
|
|
@ -1,6 +0,0 @@
|
||||||
SELECT
|
|
||||||
*
|
|
||||||
FROM
|
|
||||||
reminder
|
|
||||||
WHERE
|
|
||||||
id = ?
|
|
|
@ -1,14 +0,0 @@
|
||||||
SELECT
|
|
||||||
EXISTS (
|
|
||||||
SELECT
|
|
||||||
1
|
|
||||||
FROM
|
|
||||||
reminder
|
|
||||||
WHERE
|
|
||||||
id = ?
|
|
||||||
AND user_id = ?
|
|
||||||
AND (
|
|
||||||
guild_id = ?
|
|
||||||
OR guild_id = 0
|
|
||||||
)
|
|
||||||
)
|
|
|
@ -1,4 +0,0 @@
|
||||||
DELETE FROM reminder
|
|
||||||
WHERE
|
|
||||||
creation_date = ?
|
|
||||||
AND user_id = ?
|
|
|
@ -1,4 +0,0 @@
|
||||||
SELECT
|
|
||||||
*
|
|
||||||
FROM
|
|
||||||
reminder
|
|
|
@ -1,13 +0,0 @@
|
||||||
UPDATE reminder
|
|
||||||
SET
|
|
||||||
data = ?,
|
|
||||||
expiration_date = ?,
|
|
||||||
option_id = ?,
|
|
||||||
channel_id = ?,
|
|
||||||
creation_date = ?,
|
|
||||||
user_id = ?,
|
|
||||||
guild_id = ?,
|
|
||||||
locale = ?,
|
|
||||||
timeout_id = ?
|
|
||||||
WHERE
|
|
||||||
ID = ?
|
|
|
@ -1,34 +0,0 @@
|
||||||
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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = "";
|
|
||||||
test(name, () => {
|
|
||||||
expect(name.capitalize()).toBe("");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { OptionReminder, splitTime } from "../../../utils/commands/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" });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,149 +0,0 @@
|
||||||
import { Attachment, Collection, EmbedBuilder } from "discord.js";
|
|
||||||
import { handleAttachments } from "../../../utils/events/citation";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a new random string
|
|
||||||
* @returns random string
|
|
||||||
*/
|
|
||||||
const newKey = () => Math.random().toString(36).substring(2);
|
|
||||||
|
|
||||||
describe("Attachements Handler", () => {
|
|
||||||
const map = new Map([
|
|
||||||
["e_attachements", "yes_s"],
|
|
||||||
["e_attachement", "no_s"],
|
|
||||||
]);
|
|
||||||
// 102 is the maximum for [f](url) before rupture in a field
|
|
||||||
const max = 102;
|
|
||||||
const max_field = Array.from({ length: max }, () => "[f](url)").join(", ");
|
|
||||||
{
|
|
||||||
const name = "One image";
|
|
||||||
test(name, () => {
|
|
||||||
const embedExpected = new EmbedBuilder();
|
|
||||||
embedExpected.setImage("http://url");
|
|
||||||
|
|
||||||
const embedTest = new EmbedBuilder();
|
|
||||||
handleAttachments(
|
|
||||||
map,
|
|
||||||
embedTest,
|
|
||||||
new Collection([[newKey(), { name: "image.png", url: "http://url" } as Attachment]]),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(embedTest).toStrictEqual(embedExpected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = "Two images";
|
|
||||||
test(name, () => {
|
|
||||||
const embedExpected = new EmbedBuilder();
|
|
||||||
embedExpected.addFields({
|
|
||||||
name: "yes_s",
|
|
||||||
value: "[image.png](http://url), [image.png](http://url)",
|
|
||||||
});
|
|
||||||
|
|
||||||
const embedTest = new EmbedBuilder();
|
|
||||||
handleAttachments(
|
|
||||||
map,
|
|
||||||
embedTest,
|
|
||||||
new Collection([
|
|
||||||
[newKey(), { name: "image.png", url: "http://url" } as Attachment],
|
|
||||||
[newKey(), { name: "image.png", url: "http://url" } as Attachment],
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(embedTest).toStrictEqual(embedExpected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = "One link";
|
|
||||||
test(name, () => {
|
|
||||||
const embedExpected = new EmbedBuilder();
|
|
||||||
embedExpected.addFields({ name: "no_s", value: "[f](url)" });
|
|
||||||
|
|
||||||
const embedTest = new EmbedBuilder();
|
|
||||||
handleAttachments(
|
|
||||||
map,
|
|
||||||
embedTest,
|
|
||||||
new Collection([[newKey(), { name: "f", url: "url" } as Attachment]]),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(embedTest).toStrictEqual(embedExpected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = "Two files";
|
|
||||||
test(name, () => {
|
|
||||||
const embedExpected = new EmbedBuilder();
|
|
||||||
embedExpected.addFields({ name: "yes_s", value: "[f](url), [f](url)" });
|
|
||||||
|
|
||||||
const embedTest = new EmbedBuilder();
|
|
||||||
handleAttachments(
|
|
||||||
map,
|
|
||||||
embedTest,
|
|
||||||
new Collection([
|
|
||||||
[newKey(), { name: "f", url: "url" } as Attachment],
|
|
||||||
[newKey(), { name: "f", url: "url" } as Attachment],
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(embedTest).toStrictEqual(embedExpected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = "Two fields with multiples files each";
|
|
||||||
test(name, () => {
|
|
||||||
const total = 150;
|
|
||||||
|
|
||||||
const embedExpected = new EmbedBuilder();
|
|
||||||
embedExpected.addFields(
|
|
||||||
{
|
|
||||||
name: "yes_s (1)",
|
|
||||||
value: max_field,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "yes_s (2)",
|
|
||||||
value: Array.from({ length: total - max }, () => "[f](url)").join(", "),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const embedTest = new EmbedBuilder();
|
|
||||||
handleAttachments(
|
|
||||||
map,
|
|
||||||
embedTest,
|
|
||||||
new Collection(
|
|
||||||
Array.from({ length: total }, () => [newKey(), { name: "f", url: "url" } as Attachment]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(embedTest).toStrictEqual(embedExpected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = "Two fields with one field with one element";
|
|
||||||
test(name, () => {
|
|
||||||
const total = 103;
|
|
||||||
|
|
||||||
const embedExpected = new EmbedBuilder();
|
|
||||||
embedExpected.addFields(
|
|
||||||
{
|
|
||||||
name: "yes_s (1)",
|
|
||||||
value: max_field,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "no_s (2)",
|
|
||||||
value: Array.from({ length: total - max }, () => "[f](url)").join(", "),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const embedTest = new EmbedBuilder();
|
|
||||||
handleAttachments(
|
|
||||||
map,
|
|
||||||
embedTest,
|
|
||||||
new Collection(
|
|
||||||
Array.from({ length: total }, () => [newKey(), { name: "f", url: "url" } as Attachment]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(embedTest).toStrictEqual(embedExpected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,151 +0,0 @@
|
||||||
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",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,26 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,120 +0,0 @@
|
||||||
import {
|
|
||||||
nextTimeUnit,
|
|
||||||
showDate,
|
|
||||||
strToSeconds,
|
|
||||||
timeDeltaToString,
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Relative time", () => {
|
|
||||||
// Thoses tests are based on time, we have 10s of acceptance.
|
|
||||||
{
|
|
||||||
const name = Date.now() + (10 * TimeSecond.Minute + 30) * 1000;
|
|
||||||
test(name.toString(), () => {
|
|
||||||
expect(timeDeltaToString(name)).toMatch(/10m 30s|10m 2\ds/);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = Date.now() + (12 * TimeSecond.Hour + 30 * TimeSecond.Minute) * 1000;
|
|
||||||
test(name.toString(), () => {
|
|
||||||
expect(timeDeltaToString(name)).toMatch(/12h 30m|12h 29m 5\ds/);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = Date.now() + (TimeSecond.Week + TimeSecond.Day + 6 * TimeSecond.Hour) * 1000;
|
|
||||||
test(name.toString(), () => {
|
|
||||||
expect(timeDeltaToString(name)).toMatch(/1w 1d 6h|1w 1d 5h 59m 5\ds/);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const name = Date.now();
|
|
||||||
test(name.toString(), () => {
|
|
||||||
expect(timeDeltaToString(name)).toMatch(/\ds/);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,17 +1,13 @@
|
||||||
import { Player } from "discord-player";
|
import { Player } from "discord-player";
|
||||||
import { ActivityType, Client, Collection, GatewayIntentBits } from "discord.js";
|
import { Client, Collection, GatewayIntentBits } from "discord.js";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { Database } from "sqlite3";
|
import { Database } from "sqlite3";
|
||||||
import "../modules/client";
|
import "../modules/client";
|
||||||
import { loadLocales } from "./locales";
|
import { loadLocales } from "./locales";
|
||||||
import { YoutubeiExtractor } from "discord-player-youtubei";
|
import { YoutubeiExtractor } from "discord-player-youtubei";
|
||||||
import { readSQL } from "./db";
|
|
||||||
import { isDev } from "./misc";
|
|
||||||
|
|
||||||
/** Creation of the client and definition of its properties */
|
/** Creation of the client and definition of its properties */
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const activities = isDev ? [] : [{ name: "/help", type: ActivityType.Watching }];
|
|
||||||
|
|
||||||
const client: Client = new Client({
|
const client: Client = new Client({
|
||||||
shards: "auto",
|
shards: "auto",
|
||||||
intents: [
|
intents: [
|
||||||
|
@ -20,9 +16,6 @@ export default async () => {
|
||||||
GatewayIntentBits.MessageContent,
|
GatewayIntentBits.MessageContent,
|
||||||
GatewayIntentBits.GuildVoiceStates,
|
GatewayIntentBits.GuildVoiceStates,
|
||||||
],
|
],
|
||||||
presence: {
|
|
||||||
activities,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
client.config = {
|
client.config = {
|
||||||
|
@ -61,7 +54,7 @@ export default async () => {
|
||||||
|
|
||||||
client.db = new Database(`${process.env.DOCKERIZED === "1" ? "/config" : "./config"}/db.sqlite3`);
|
client.db = new Database(`${process.env.DOCKERIZED === "1" ? "/config" : "./config"}/db.sqlite3`);
|
||||||
|
|
||||||
client.db.run(readSQL("init"));
|
initDatabase(client.db);
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
@ -77,3 +70,25 @@ export const quit = (client: Client) => {
|
||||||
// Close client
|
// Close client
|
||||||
client.destroy();
|
client.destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initalize the database
|
||||||
|
* @param db Database
|
||||||
|
*/
|
||||||
|
const initDatabase = (db: Database) => {
|
||||||
|
// Table for reminders
|
||||||
|
db.run(
|
||||||
|
"CREATE TABLE IF NOT EXISTS reminder ( \
|
||||||
|
id INTEGER PRIMARY KEY, \
|
||||||
|
data TEXT, \
|
||||||
|
expiration_date TEXT, \
|
||||||
|
option_id INTEGER, \
|
||||||
|
channel_id TEXT, \
|
||||||
|
creation_date TEXT, \
|
||||||
|
user_id TEXT, \
|
||||||
|
guild_id TEXT, \
|
||||||
|
locale TEXT, \
|
||||||
|
timeout_id TEXT \
|
||||||
|
);",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
import {
|
|
||||||
APIApplicationCommandSubcommandOption,
|
|
||||||
ApplicationCommandOptionType,
|
|
||||||
Client,
|
|
||||||
Locale,
|
|
||||||
SlashCommandBuilder,
|
|
||||||
} from "discord.js";
|
|
||||||
|
|
||||||
type Data = SlashCommandBuilder | APIApplicationCommandSubcommandOption;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the name of the command, trying to get the correct locale
|
|
||||||
* @param data Command data
|
|
||||||
* @param locale Locale wanted
|
|
||||||
* @returns Command's name
|
|
||||||
*/
|
|
||||||
export const goodName = (data: Data, locale: Locale) =>
|
|
||||||
data.name_localizations?.[locale] ?? data.name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the description of the command, trying to get the correct locale
|
|
||||||
* @param data Command data
|
|
||||||
* @param locale Locale wanted
|
|
||||||
* @returns Command's description
|
|
||||||
*/
|
|
||||||
export const goodDescription = (data: Data, locale: Locale) =>
|
|
||||||
data.description_localizations?.[locale] ?? data.description;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aux function for Sub/NameNotLocalized
|
|
||||||
* @param cmd data
|
|
||||||
* @param command command researched
|
|
||||||
* @returns if we found or not the researched command
|
|
||||||
*/
|
|
||||||
const filterLocalizations = (cmd: Data, command: string) => {
|
|
||||||
let res = false;
|
|
||||||
for (const key in cmd?.name_localizations) {
|
|
||||||
res = res || cmd.name_localizations?.[key as Locale] === command;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a command based on any string, localized or not
|
|
||||||
* @param command string
|
|
||||||
* @returns the not localized corresponding string's command name
|
|
||||||
*/
|
|
||||||
export const NameNotLocalized = (client: Client, command: string): SlashCommandBuilder | null => {
|
|
||||||
const list = client.commands.list.map((cmd) => cmd.data);
|
|
||||||
|
|
||||||
return (
|
|
||||||
list.find((cmd) => cmd.name === command) ||
|
|
||||||
list.filter((cmd) => filterLocalizations(cmd, command))[0]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a subcommand of a command based on any string, localized or not
|
|
||||||
* @param parent command of the subcommand
|
|
||||||
* @param command string
|
|
||||||
* @returns the not localized corresponding string's subcommand name
|
|
||||||
*/
|
|
||||||
export const SubnameNotLocalized = (parent: SlashCommandBuilder, command: string) => {
|
|
||||||
const list = parent
|
|
||||||
?.toJSON()
|
|
||||||
.options?.filter((option) => option.type === ApplicationCommandOptionType.Subcommand);
|
|
||||||
|
|
||||||
return (
|
|
||||||
list?.find((cmd) => cmd?.name === command) ||
|
|
||||||
list?.filter((cmd) => filterLocalizations(cmd, command))[0]
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
/** Max message length */
|
|
||||||
export const discord_limit_message = 2000;
|
|
||||||
|
|
||||||
/** Max embed field an embed can have */
|
|
||||||
export const discord_limit_embed_field = 25;
|
|
||||||
|
|
||||||
/** Max element the autocompletion of slash commands can have */
|
|
||||||
export const discord_limit_autocompletion_list_length = 25;
|
|
||||||
|
|
||||||
/** Max length of an element in autocompletion of slash commands */
|
|
||||||
export const discord_limit_autocompletion_value_length = 100;
|
|
|
@ -1,17 +0,0 @@
|
||||||
import fs from "node:fs";
|
|
||||||
import { isDev } from "./misc";
|
|
||||||
|
|
||||||
export const readSQL = (path: string) => {
|
|
||||||
const root = isDev ? "./src" : "./dist";
|
|
||||||
const dir = root + "/sql/";
|
|
||||||
if (!path.startsWith(dir)) {
|
|
||||||
path = dir + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ext = ".sql";
|
|
||||||
if (!path.endsWith(ext)) {
|
|
||||||
path += ext;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.readFileSync(path, "utf8");
|
|
||||||
};
|
|
|
@ -1,57 +0,0 @@
|
||||||
import { APIEmbedField, Attachment, Collection, EmbedBuilder } from "discord.js";
|
|
||||||
import { isImage } from "../misc";
|
|
||||||
|
|
||||||
export const handleAttachments = (
|
|
||||||
loc: Map<string, string>,
|
|
||||||
embed: EmbedBuilder,
|
|
||||||
attachments: Collection<string, Attachment>,
|
|
||||||
) => {
|
|
||||||
if (attachments.size === 1 && isImage(attachments.first()!.name)) {
|
|
||||||
// Only contains one image
|
|
||||||
embed.setImage(attachments.first()!.url);
|
|
||||||
} else {
|
|
||||||
// Contains more than one image and/or other files
|
|
||||||
|
|
||||||
// We are currently losing a link to a file if the link is too long
|
|
||||||
// We could truncate the filename ?
|
|
||||||
const maxFieldValueLength = 1024;
|
|
||||||
const files = attachments
|
|
||||||
.map((file) => `[${file.name}](${file.url})`)
|
|
||||||
.filter((link) => link.length <= maxFieldValueLength);
|
|
||||||
|
|
||||||
let currentField = "";
|
|
||||||
const fields: APIEmbedField[] = [];
|
|
||||||
let multipleFields = 0;
|
|
||||||
let numberOfLinks = 0;
|
|
||||||
files.forEach((file, idx) => {
|
|
||||||
numberOfLinks++;
|
|
||||||
const fieldValue = currentField.length > 0 ? `${currentField}, ${file}` : file;
|
|
||||||
|
|
||||||
if (fieldValue.length > maxFieldValueLength) {
|
|
||||||
multipleFields = multipleFields === 0 && idx !== files.length - 1 ? 1 : multipleFields + 1;
|
|
||||||
fields.push({
|
|
||||||
name:
|
|
||||||
loc.get(
|
|
||||||
attachments.size > 1 && numberOfLinks > 1 ? "e_attachements" : "e_attachement",
|
|
||||||
) + (multipleFields ? ` (${multipleFields})` : ""),
|
|
||||||
value: currentField,
|
|
||||||
});
|
|
||||||
currentField = file;
|
|
||||||
numberOfLinks = 0;
|
|
||||||
} else {
|
|
||||||
currentField = fieldValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (currentField.length > 0) {
|
|
||||||
fields.push({
|
|
||||||
name:
|
|
||||||
loc.get(attachments.size > 1 && numberOfLinks > 1 ? "e_attachements" : "e_attachement") +
|
|
||||||
(multipleFields ? ` (${multipleFields + 1})` : ""),
|
|
||||||
value: currentField,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
embed.addFields(fields);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -81,13 +81,9 @@ export const getLocalizations = (client: Client, text: string, lowercase = false
|
||||||
* @param lang Lang to fetch
|
* @param lang Lang to fetch
|
||||||
* @returns the map with the desired languaged clogged with the default one
|
* @returns the map with the desired languaged clogged with the default one
|
||||||
*/
|
*/
|
||||||
export const getLocale = (client: Client, lang: string | undefined = undefined) => {
|
export const getLocale = (client: Client, lang: string) => {
|
||||||
// Load default lang
|
// Load default lang
|
||||||
const default_locales = client.locales.get(client.config.default_lang);
|
const default_locales = client.locales.get(client.config.default_lang);
|
||||||
if (!lang) {
|
|
||||||
return default_locales!;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load desired lang
|
// Load desired lang
|
||||||
const desired_locales = client.locales.get(lang);
|
const desired_locales = client.locales.get(lang);
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import { GuildMember } from "discord.js";
|
import { GuildMember } from "discord.js";
|
||||||
|
|
||||||
/** Check if we are in the dev environnement */
|
|
||||||
export const isDev = process.env.NODE_ENV !== "production";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log module status
|
* Log module status
|
||||||
* @param {string} name Module name
|
* @param {string} name Module name
|
||||||
|
@ -31,41 +28,27 @@ export const getFilename = (path: string) => {
|
||||||
return removeExtension(filename_with_ext);
|
return removeExtension(filename_with_ext);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Split a filename and his extension
|
|
||||||
* @param filename string of the filename
|
|
||||||
* @returns Object with filename and extension splitted
|
|
||||||
*/
|
|
||||||
export const splitFilenameExtensions = (filename: string) => {
|
|
||||||
if (filename.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the filename starts with a dot and has no other dots
|
|
||||||
if (filename.startsWith(".") && filename.indexOf(".", 1) === -1) {
|
|
||||||
return { file: filename, ext: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastDotIndex = filename.lastIndexOf(".");
|
|
||||||
|
|
||||||
// If there's no dot or the dot is at the start, treat the whole string as the filename
|
|
||||||
if (lastDotIndex <= 0) {
|
|
||||||
return { file: filename, ext: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
const file = filename.slice(0, lastDotIndex);
|
|
||||||
const ext = filename.slice(lastDotIndex + 1);
|
|
||||||
|
|
||||||
return { file, ext };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove extension from a filename
|
* Remove extension from a filename
|
||||||
* @param filename string of the filename with an extension
|
* @param filename string of the filename with an extension
|
||||||
* @returns string of the filename without an extension
|
* @returns string of the filename without an extension
|
||||||
*/
|
*/
|
||||||
export const removeExtension = (filename: string) => {
|
export const removeExtension = (filename: string) => {
|
||||||
return splitFilenameExtensions(filename)!.file;
|
const array = filename.split(".");
|
||||||
|
array.pop();
|
||||||
|
|
||||||
|
return array.join(".");
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get extension from a filename
|
||||||
|
* @param filename string of the filename
|
||||||
|
* @returns string of the extension if it exists
|
||||||
|
*/
|
||||||
|
export const getExtension = (filename: string) => {
|
||||||
|
const array = filename.split(".");
|
||||||
|
|
||||||
|
return array.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,11 +57,7 @@ export const removeExtension = (filename: string) => {
|
||||||
* @returns true is file is a media
|
* @returns true is file is a media
|
||||||
*/
|
*/
|
||||||
export const isImage = (filename: string) => {
|
export const isImage = (filename: string) => {
|
||||||
return Boolean(
|
return Boolean(getExtension(filename)?.match(/jpg|jpeg|png|webp|gif/));
|
||||||
splitFilenameExtensions(filename)
|
|
||||||
?.ext?.toLowerCase()
|
|
||||||
.match(/jpg|jpeg|png|webp|gif/),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -111,16 +90,15 @@ export const cleanCodeBlock = (text: string) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keep links
|
// Keep links
|
||||||
// Reference: https://stackoverflow.com/a/3809435/15436737
|
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_+.~#?&//=]*/g,
|
/(http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)/g,
|
||||||
function (url: string) {
|
function (url: string) {
|
||||||
return `\`${url}\``;
|
return `\`${url}\``;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fix issues
|
// Fix issues
|
||||||
text = text.replaceAll("``", "");
|
text = text.replace("``", "");
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
@ -134,8 +112,3 @@ export const emojiPng = (emoji: string) =>
|
||||||
`https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/${emoji
|
`https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/${emoji
|
||||||
.codePointAt(0)
|
.codePointAt(0)
|
||||||
?.toString(16)}.png`;
|
?.toString(16)}.png`;
|
||||||
|
|
||||||
/**
|
|
||||||
* Blank character
|
|
||||||
*/
|
|
||||||
export const blank = "\u200b";
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { EmbedBuilder } from "@discordjs/builders";
|
import { EmbedBuilder } from "@discordjs/builders";
|
||||||
import { GuildQueue, QueueRepeatMode } from "discord-player";
|
import { GuildQueue, QueueRepeatMode, Track } from "discord-player";
|
||||||
import { Client } from "discord.js";
|
import { Client } from "discord.js";
|
||||||
import { getLocale } from "../locales";
|
import { getLocale } from "./locales";
|
||||||
import { blank } from "../misc";
|
|
||||||
import { discord_limit_embed_field } from "../constants";
|
|
||||||
|
|
||||||
export const embedListQueue = (
|
export const embedListQueue = (
|
||||||
client: Client,
|
client: Client,
|
||||||
|
@ -16,9 +14,10 @@ export const embedListQueue = (
|
||||||
const tracks = queue.tracks.toArray();
|
const tracks = queue.tracks.toArray();
|
||||||
|
|
||||||
// Add the current song at the top of the list
|
// Add the current song at the top of the list
|
||||||
tracks.unshift(queue.history.currentTrack!);
|
tracks.unshift(queue.history.currentTrack as Track);
|
||||||
|
|
||||||
const limit_fields = discord_limit_embed_field;
|
// Limit of discord is 25
|
||||||
|
const limit_fields = 25;
|
||||||
|
|
||||||
const pageMax = Math.ceil(tracks.length / limit_fields);
|
const pageMax = Math.ceil(tracks.length / limit_fields);
|
||||||
|
|
||||||
|
@ -26,12 +25,12 @@ export const embedListQueue = (
|
||||||
embed.setFooter({ text: `${printRepeatMode(queue.repeatMode, loc)}` });
|
embed.setFooter({ text: `${printRepeatMode(queue.repeatMode, loc)}` });
|
||||||
|
|
||||||
tracks.slice((page - 1) * limit_fields, page * limit_fields).forEach((t, idx) => {
|
tracks.slice((page - 1) * limit_fields, page * limit_fields).forEach((t, idx) => {
|
||||||
const now_playing = idx === 0 && page === 1;
|
const now_playing = idx == 0 && page == 1;
|
||||||
const name = now_playing
|
const name = now_playing
|
||||||
? loc.get("c_queue10")
|
? loc.get("c_queue10")
|
||||||
: (idx === 1 && page === 1) || (idx === 0 && page > 1)
|
: (idx == 1 && page == 1) || (idx == 0 && page > 1)
|
||||||
? loc.get("c_queue11")
|
? loc.get("c_queue11")
|
||||||
: blank;
|
: "\u200b";
|
||||||
const idx_track = now_playing ? "" : `${idx + limit_fields * (page - 1)}. `;
|
const idx_track = now_playing ? "" : `${idx + limit_fields * (page - 1)}. `;
|
||||||
embed.addFields({
|
embed.addFields({
|
||||||
name,
|
name,
|
|
@ -1,30 +0,0 @@
|
||||||
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));
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { Client, Colors, EmbedBuilder, User } from "discord.js";
|
import { Client, Colors, EmbedBuilder, User } from "discord.js";
|
||||||
import { getLocale } from "../locales";
|
import { getLocale } from "./locales";
|
||||||
import { blank, cleanCodeBlock } from "../misc";
|
import { cleanCodeBlock } from "./misc";
|
||||||
import { showDate, strToSeconds, timeDeltaToString } from "../time";
|
import { showDate, strToSeconds, timeDeltaToString } from "./time";
|
||||||
import { RegexC, RegExpFlags } from "../regex";
|
|
||||||
import { readSQL } from "../db";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Option possible for reminders
|
* Option possible for reminders
|
||||||
|
@ -47,26 +45,14 @@ 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
|
||||||
*/
|
*/
|
||||||
export const splitTime = (time: string) => {
|
const splitTime = (time: string) => {
|
||||||
const mapping = {
|
if (time?.endsWith("@")) {
|
||||||
[OptionReminder.DirectMessage]: "p",
|
return { time: time.slice(0, -1), option: OptionReminder.Mention };
|
||||||
[OptionReminder.Mention]: "@",
|
} else if (time?.toLowerCase().endsWith("p")) {
|
||||||
};
|
return { time: time.slice(0, -1), option: OptionReminder.DirectMessage };
|
||||||
|
|
||||||
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 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { time: time, option: OptionReminder.Nothing };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,25 +64,18 @@ export const splitTime = (time: string) => {
|
||||||
*/
|
*/
|
||||||
export const newReminder = async (client: Client, time: string, info: infoReminder) =>
|
export const newReminder = async (client: Client, time: string, info: infoReminder) =>
|
||||||
new Promise((ok, ko) => {
|
new Promise((ok, ko) => {
|
||||||
const loc = getLocale(client, info.locale);
|
|
||||||
|
|
||||||
const data = splitTime(time);
|
const data = splitTime(time);
|
||||||
const timeout = strToSeconds(data.time);
|
const timeout = strToSeconds(data.time);
|
||||||
if (timeout < 0) {
|
|
||||||
ko(loc.get("c_reminder18"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeoutId = setTimeoutReminder(client, info, data.option, timeout);
|
const timeoutId = setTimeoutReminder(client, info, data.option, timeout);
|
||||||
|
|
||||||
const expiration_date = info.createdAt + timeout * 1000;
|
|
||||||
|
|
||||||
// Add the remind to the db
|
// Add the remind to the db
|
||||||
client.db.run(
|
client.db.run(
|
||||||
readSQL("reminder/add"),
|
"INSERT INTO reminder ( \
|
||||||
|
data, expiration_date, option_id, channel_id, creation_date, user_id, guild_id, locale, timeout_id \
|
||||||
|
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? );",
|
||||||
[
|
[
|
||||||
info.message,
|
info.message,
|
||||||
`${expiration_date}`,
|
`${info.createdAt + timeout * 1000}`,
|
||||||
data.option.valueOf(),
|
data.option.valueOf(),
|
||||||
info.channelId,
|
info.channelId,
|
||||||
`${info.createdAt}`,
|
`${info.createdAt}`,
|
||||||
|
@ -111,7 +90,8 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send confirmation to user
|
// Send confirmation to user
|
||||||
ok(`${loc.get("c_reminder1")} ${timeDeltaToString(expiration_date)}.`);
|
const loc = getLocale(client, info.locale);
|
||||||
|
ok(`${loc.get("c_reminder1")} ${data.time}.`);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -126,30 +106,26 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
|
||||||
export const deleteReminder = (client: Client, createdAt: string, userId: string) => {
|
export const deleteReminder = (client: Client, createdAt: string, userId: string) => {
|
||||||
// Delete the reminder for the database
|
// Delete the reminder for the database
|
||||||
return new Promise((ok, ko) => {
|
return new Promise((ok, ko) => {
|
||||||
// Remove the remind to the db
|
// Add the remind to the db
|
||||||
client.db.run(readSQL("reminder/remove"), [createdAt, userId], (err) => {
|
client.db.run(
|
||||||
|
"DELETE FROM reminder WHERE creation_date = ? AND user_id = ?",
|
||||||
|
[createdAt, userId],
|
||||||
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
ko(err);
|
ko(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send confirmation to user
|
// Send confirmation to user
|
||||||
ok(true);
|
ok(true);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendReminder = (client: Client, info: infoReminder, option: OptionReminder) =>
|
export const sendReminder = (client: Client, info: infoReminder, option: OptionReminder) => {
|
||||||
new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
resolve(sendReminderAux(client, info, option));
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const sendReminderAux = (client: Client, info: infoReminder, option: OptionReminder) => {
|
|
||||||
const loc = getLocale(client, info.locale);
|
const loc = getLocale(client, info.locale);
|
||||||
// Send the message in the appropriate channel
|
// Send the message in the appropriate channel
|
||||||
|
// TODO: Embed
|
||||||
let message: string;
|
let message: string;
|
||||||
if (info.message === null || info.message.length === 0) {
|
if (info.message === null || info.message.length === 0) {
|
||||||
message = loc.get("c_reminder7");
|
message = loc.get("c_reminder7");
|
||||||
|
@ -184,7 +160,7 @@ const sendReminderAux = (client: Client, info: infoReminder, option: OptionRemin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (option === OptionReminder.DirectMessage || !channelOk || !guildOk) {
|
if (option == OptionReminder.DirectMessage || !channelOk || !guildOk) {
|
||||||
// Direct message
|
// Direct message
|
||||||
const user = client.users.cache.get(info.userId);
|
const user = client.users.cache.get(info.userId);
|
||||||
if (user !== undefined) {
|
if (user !== undefined) {
|
||||||
|
@ -194,18 +170,14 @@ const sendReminderAux = (client: Client, info: infoReminder, option: OptionRemin
|
||||||
// Channel
|
// Channel
|
||||||
client.channels.fetch(info.channelId!).then((channel) => {
|
client.channels.fetch(info.channelId!).then((channel) => {
|
||||||
if (channel?.isSendable()) {
|
if (channel?.isSendable()) {
|
||||||
const author_mention = `<@${info.userId}>`;
|
let content = `<@${info.userId}>`;
|
||||||
|
|
||||||
let content = author_mention;
|
|
||||||
embed.setFooter({
|
embed.setFooter({
|
||||||
text: `${loc.get("c_reminder17")} ${timeDeltaToString(info.createdAt)}`,
|
text: `${loc.get("c_reminder17")} ${timeDeltaToString(info.createdAt)}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mention everybody if needed
|
// Mention everybody if needed
|
||||||
if (option === OptionReminder.Mention) {
|
if (option == OptionReminder.Mention) {
|
||||||
[...new Set(info.message?.match(/<@\d+>/g) ?? [])]
|
(info.message?.match(/<@\d+>/g) ?? []).forEach((mention) => {
|
||||||
.filter((mention) => mention !== author_mention)
|
|
||||||
.forEach((mention: string) => {
|
|
||||||
content += " " + mention;
|
content += " " + mention;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -230,19 +202,8 @@ export const setTimeoutReminder = (
|
||||||
option: OptionReminder,
|
option: OptionReminder,
|
||||||
timeout: number,
|
timeout: number,
|
||||||
) => {
|
) => {
|
||||||
const setChunkedTimeout = (remainingTime: number) => {
|
return Number(
|
||||||
// Maximum for setTimeout is Int32
|
setTimeout(() => {
|
||||||
if (remainingTime > 2147483647) {
|
|
||||||
// Schedule a 24-hour delay (24 * 60 * 60 * 1000), then check again
|
|
||||||
const dayChunk = 86400000;
|
|
||||||
|
|
||||||
return setTimeout(() => {
|
|
||||||
setChunkedTimeout(remainingTime - dayChunk);
|
|
||||||
}, dayChunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final timeout when remaining time is within limit
|
|
||||||
return setTimeout(() => {
|
|
||||||
deleteReminder(client, String(info.createdAt), info.userId).then((val) => {
|
deleteReminder(client, String(info.createdAt), info.userId).then((val) => {
|
||||||
if (val != true) {
|
if (val != true) {
|
||||||
throw val;
|
throw val;
|
||||||
|
@ -250,11 +211,8 @@ export const setTimeoutReminder = (
|
||||||
|
|
||||||
sendReminder(client, info, option);
|
sendReminder(client, info, option);
|
||||||
});
|
});
|
||||||
}, remainingTime);
|
}, timeout * 1000),
|
||||||
};
|
);
|
||||||
|
|
||||||
// Convert to milliseconds
|
|
||||||
return Number(setChunkedTimeout(timeout * 1000));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -275,7 +233,12 @@ export const checkOwnershipReminder = async (
|
||||||
const data = (await new Promise((ok, ko) => {
|
const data = (await new Promise((ok, ko) => {
|
||||||
// Check the ownership
|
// Check the ownership
|
||||||
client.db.all<returnData>(
|
client.db.all<returnData>(
|
||||||
readSQL("reminder/ownership_check"),
|
"SELECT EXISTS ( \
|
||||||
|
SELECT 1 FROM reminder \
|
||||||
|
WHERE id = ? \
|
||||||
|
AND user_id = ? \
|
||||||
|
AND (guild_id = ? OR guild_id = 0) \
|
||||||
|
)",
|
||||||
[id, userId, guildId],
|
[id, userId, guildId],
|
||||||
(err, row) => {
|
(err, row) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -298,14 +261,19 @@ export const checkOwnershipReminder = async (
|
||||||
export const getReminderInfo = async (client: Client, id: number) => {
|
export const getReminderInfo = async (client: Client, id: number) => {
|
||||||
return (await new Promise((ok, ko) => {
|
return (await new Promise((ok, ko) => {
|
||||||
// Check the ownership
|
// Check the ownership
|
||||||
client.db.all<dbReminder>(readSQL("reminder/findById"), [id], (err, row) => {
|
client.db.all<dbReminder>(
|
||||||
|
"SELECT * FROM reminder \
|
||||||
|
WHERE id = ?",
|
||||||
|
[id],
|
||||||
|
(err, row) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
ko(err);
|
ko(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send all the current reminders
|
// Send all the current reminders
|
||||||
ok(row[0]);
|
ok(row[0]);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
})) as dbReminder;
|
})) as dbReminder;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -319,7 +287,17 @@ export const updateReminder = (client: Client, data: dbReminder) => {
|
||||||
return new Promise((ok, ko) => {
|
return new Promise((ok, ko) => {
|
||||||
// Update the db
|
// Update the db
|
||||||
client.db.run(
|
client.db.run(
|
||||||
readSQL("reminder/update"),
|
"UPDATE reminder \
|
||||||
|
SET data = ?, \
|
||||||
|
expiration_date = ?, \
|
||||||
|
option_id = ?, \
|
||||||
|
channel_id = ?, \
|
||||||
|
creation_date = ?, \
|
||||||
|
user_id = ?, \
|
||||||
|
guild_id = ?, \
|
||||||
|
locale = ?, \
|
||||||
|
timeout_id = ? \
|
||||||
|
WHERE ID = ?",
|
||||||
[
|
[
|
||||||
data.data,
|
data.data,
|
||||||
data.expiration_date,
|
data.expiration_date,
|
||||||
|
@ -353,14 +331,19 @@ export const updateReminder = (client: Client, data: dbReminder) => {
|
||||||
const listReminders = async (client: Client, userId: string, guildId: string | null) => {
|
const listReminders = async (client: Client, userId: string, guildId: string | null) => {
|
||||||
return (await new Promise((ok, ko) => {
|
return (await new Promise((ok, ko) => {
|
||||||
// Check the ownership
|
// Check the ownership
|
||||||
client.db.all<dbReminder>(readSQL("reminder/find"), [userId, guildId ?? 0], (err, row) => {
|
client.db.all<dbReminder>(
|
||||||
|
"SELECT data, creation_date, expiration_date, id FROM reminder \
|
||||||
|
WHERE user_id = ? AND (guild_id = ? OR guild_id = 0)",
|
||||||
|
[userId, guildId ?? 0],
|
||||||
|
(err, row) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
ko(err);
|
ko(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send all the current reminders
|
// Send all the current reminders
|
||||||
ok(row);
|
ok(row);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
})) as dbReminder[];
|
})) as dbReminder[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -424,7 +407,7 @@ export const embedListReminders = async (
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
embed.addFields({
|
embed.addFields({
|
||||||
name: blank,
|
name: "\u200b",
|
||||||
value: `${loc.get("c_reminder10")}${page} ${loc.get("c_reminder11")}.`,
|
value: `${loc.get("c_reminder10")}${page} ${loc.get("c_reminder11")}.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,133 +1,58 @@
|
||||||
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 lang Locale
|
* @param tz Lang
|
||||||
* @param translation Translation for "at"
|
* @param locale Locales
|
||||||
* @param date Date
|
* @param date Date
|
||||||
* @returns String
|
* @returns String
|
||||||
*/
|
*/
|
||||||
export const showDate = (lang: string, translation: Map<string, unknown>, date: Date) => {
|
export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) => {
|
||||||
const localeInfo = new Intl.Locale(lang);
|
return date.toLocaleString(tz).replace(" ", ` ${locale.get("u_time_at")} `);
|
||||||
const intlTimezone = moment.tz.zonesForCountry(localeInfo.region ?? localeInfo.baseName);
|
|
||||||
const formattedDate = new Intl.DateTimeFormat(lang, {
|
|
||||||
timeZone: intlTimezone ? intlTimezone[0] : "Factory",
|
|
||||||
dateStyle: "short",
|
|
||||||
timeStyle: "medium",
|
|
||||||
})
|
|
||||||
.format(date)
|
|
||||||
.split(" ");
|
|
||||||
|
|
||||||
return `${formattedDate[0]} ${translation.get("u_time_at")} ${formattedDate[1]}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum TimeSecond {
|
enum TimeSecond {
|
||||||
Year = 60 * 60 * 24 * 365,
|
Year = 31536000,
|
||||||
Week = 60 * 60 * 24 * 7,
|
Week = 604800,
|
||||||
Day = 60 * 60 * 24,
|
Day = 86400,
|
||||||
Hour = 60 * 60,
|
Hour = 3600,
|
||||||
Minute = 60,
|
Minute = 60,
|
||||||
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
|
||||||
* @returns time in seconds
|
* @returns time in seconds
|
||||||
*/
|
*/
|
||||||
export const strToSeconds = (time: string) => {
|
export const strToSeconds = (time: string) => {
|
||||||
if (time.length > 15) {
|
const regex = new RegExp(
|
||||||
// 15 is a magic value as it's weird to have time this long
|
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|(?<${
|
||||||
return -1;
|
TimeSecond[TimeSecond.Week]
|
||||||
}
|
}>[0-9]+(?=[w]))|(?<${TimeSecond[TimeSecond.Day]}>[0-9]+(?=[d|j]))|(?<${
|
||||||
|
TimeSecond[TimeSecond.Hour]
|
||||||
const noUnit = "unmarked";
|
}>[0-9]+(?=[h]))|(?<${TimeSecond[TimeSecond.Minute]}>[0-9]+(?=[m]))|(?<${
|
||||||
const regex = RegexC(
|
TimeSecond[TimeSecond.Second]
|
||||||
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|` +
|
}>[0-9]+(?=[s]?))`,
|
||||||
`(?<${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 = Array.from(time.matchAll(regex));
|
const data = Object.assign({}, regex.exec(time)?.groups);
|
||||||
if (data.length === 0) {
|
|
||||||
// Regex returned an invalid time
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = 0;
|
let res = 0;
|
||||||
let lastUnit = TimeSecond.Second;
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
data.forEach((match) => {
|
|
||||||
Object.entries(match.groups!).forEach(([key, value]) => {
|
|
||||||
if (value) {
|
if (value) {
|
||||||
let unit;
|
res += +value * TimeSecond[key as keyof typeof TimeSecond];
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the time in a readable way
|
|
||||||
* @param seconds Time in milliseconds
|
|
||||||
* @returns Time as string
|
|
||||||
*/
|
|
||||||
export const timeToString = (time: number) => {
|
|
||||||
let secondsDifference = Math.abs(Math.ceil(time / 1000));
|
|
||||||
|
|
||||||
if (secondsDifference === 0) {
|
|
||||||
return "0s";
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.entries(TimeSecond)
|
|
||||||
.map(([key, value]) => ({
|
|
||||||
label: key.charAt(0).toLowerCase(),
|
|
||||||
value: value as TimeSecond,
|
|
||||||
}))
|
|
||||||
.map(({ label, value }) => {
|
|
||||||
if (secondsDifference >= value) {
|
|
||||||
const amount = Math.floor(secondsDifference / value);
|
|
||||||
secondsDifference -= amount * value;
|
|
||||||
return `${amount}${label}`;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ");
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculating the difference between a date and now
|
* Calculating the difference between a date and now
|
||||||
* @param time Time in milliseconds
|
* @param time Time
|
||||||
* @returns Delta between the time and now
|
* @returns Delta between the time and now
|
||||||
*/
|
*/
|
||||||
export const timeDeltaToString = (time: number) => {
|
export const timeDeltaToString = (time: number) => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
return timeToString(time - now);
|
// TODO adapt the output and not always parse the time as seconds
|
||||||
|
return `${strToSeconds(`${(now - time) / 1000}`)} secs`;
|
||||||
};
|
};
|
||||||
|
|
|
@ -102,6 +102,5 @@
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
},
|
},
|
||||||
"include": ["./**/*.ts", "./src/locales/*.json"],
|
"include": ["./**/*.ts", "./src/locales/*.json"]
|
||||||
"exclude": ["./src/tests"]
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue