Meta: Use of Prettier #58

Merged
Anri merged 6 commits from meta/prettier into main 2023-01-17 23:11:23 +01:00
37 changed files with 2343 additions and 1962 deletions
Showing only changes of commit 983333238f - Show all commits

View file

@ -1,54 +1,43 @@
{ {
"extends": [
"extends": [ "eslint:recommended",
"eslint:recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended" "prettier"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
"parserOptions": { "parserOptions": {
"project": ["./tsconfig.json"] "project": ["./tsconfig.json"]
}, },
"plugins": [ "plugins": ["@typescript-eslint"],
"@typescript-eslint" "rules": {
], "arrow-spacing": ["warn", { "before": true, "after": true }],
"rules": { "comma-style": "error",
"arrow-spacing": ["warn", { "before": true, "after": true }], "curly": ["error", "multi-line", "consistent"],
"brace-style": ["error"], "dot-location": ["error", "property"],
"comma-dangle": ["error", "always-multiline"], "handle-callback-err": "off",
"comma-spacing": "error", "max-nested-callbacks": ["error", { "max": 4 }],
"comma-style": "error", "max-statements-per-line": ["error", { "max": 2 }],
"curly": ["error", "multi-line", "consistent"], "no-console": "off",
"dot-location": ["error", "property"], "no-empty-function": "error",
"handle-callback-err": "off", "no-floating-decimal": "error",
"indent": ["error", "tab", { "SwitchCase": 1 }], "no-inline-comments": "error",
"keyword-spacing": "error", "no-lonely-if": "error",
"max-nested-callbacks": ["error", { "max": 4 }], "no-multi-spaces": "error",
"max-statements-per-line": ["error", { "max": 2 }], "no-multiple-empty-lines": [
"no-console": "off", "error",
"no-empty-function": "error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }
"no-floating-decimal": "error", ],
"no-inline-comments": "error", "no-shadow": "off",
"no-lonely-if": "error", "@typescript-eslint/no-shadow": [
"no-multi-spaces": "error", "error",
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], { "allow": ["err", "resolve", "reject"] }
"no-shadow": "off", ],
"@typescript-eslint/no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }], "no-trailing-spaces": ["error"],
"no-trailing-spaces": ["error"], "no-var": "error",
"no-var": "error", "prefer-const": "error",
"object-curly-spacing": ["error", "always"], "space-in-parens": "error",
"prefer-const": "error", "space-unary-ops": "error",
"quotes": ["error", "single"], "spaced-comment": "error",
"semi": ["error", "always"], "yoda": "error"
"space-before-blocks": "error", }
"space-before-function-paren": ["error", {
"anonymous": "never",
"named": "never",
"asyncArrow": "always"
}],
"space-in-parens": "error",
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": "error",
"yoda": "error"
}
} }

View file

@ -1,13 +1,10 @@
--- ---
name: "🐞 Rapport d'un bug" name: "🐞 Rapport d'un bug"
about: "Signal un problème rencontré" about: "Signal un problème rencontré"
ref: "main" ref: "main"
labels: labels:
- bug
- bug - "help wanted"
- "help wanted"
--- ---
Bot version: v`X.Y.Z` Bot version: v`X.Y.Z`

View file

@ -1,10 +1,7 @@
--- ---
name: "💫 Demande une fonctionnalitée" name: "💫 Demande une fonctionnalitée"
about: "Propose une nouvelle fonctionnalité à ajouter" about: "Propose une nouvelle fonctionnalité à ajouter"
ref: "main" ref: "main"
labels: labels:
- enhancement
- enhancement
--- ---

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
dist

1
.prettierrc.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -1,5 +1,3 @@
{ {
"recommendations": [ "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
"dbaeumer.vscode-eslint"
]
} }

10
.vscode/settings.json vendored
View file

@ -1,8 +1,8 @@
{ {
"editor.tabSize": 4, "editor.tabSize": 2,
"editor.insertSpaces": false, "editor.insertSpaces": false,
"files.insertFinalNewline": true, "files.insertFinalNewline": true,
"files.trimFinalNewlines": true, "files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true "files.trimTrailingWhitespace": true
} }

View file

@ -40,26 +40,26 @@ contient toujours toutes les chaînes de caractère dont le bot a besoin.
La norme pour les nom dans les fichiers est la suivante : La norme pour les nom dans les fichiers est la suivante :
- Chaîne de charactère des commandes : - Chaîne de charactère des commandes :
`c` est utilisé pour `C`ommande. `c` est utilisé pour `C`ommande.
- `c_NOM-COMMANDE_name` : Nom de la commande - `c_NOM-COMMANDE_name` : Nom de la commande
- `c_NOM-COMMANDE_desc` : Description de la commande - `c_NOM-COMMANDE_desc` : Description de la commande
- `c_NOM-COMMANDE_optX_name` : Nom de l'option X - `c_NOM-COMMANDE_optX_name` : Nom de l'option X
- `c_NOM-COMMANDE_optX_desc` : Description de l'option X - `c_NOM-COMMANDE_optX_desc` : Description de l'option X
- `c_NOM-COMMANDE_subX_name` : Nom de la sous-commande X - `c_NOM-COMMANDE_subX_name` : Nom de la sous-commande X
- `c_NOM-COMMANDE_subX_desc` : Description de la sous-commande X - `c_NOM-COMMANDE_subX_desc` : Description de la sous-commande X
- `c_NOM-COMMANDEX` : `X` le numéro de la chaîne de caractère - `c_NOM-COMMANDEX` : `X` le numéro de la chaîne de caractère
Évidemment ça peut s'additionner, Évidemment ça peut s'additionner,
par exemple : `c_NOM-COMMANDE_subX_optX_desc`. par exemple : `c_NOM-COMMANDE_subX_optX_desc`.
- Chaîne de charactère des évènements : - Chaîne de charactère des évènements :
`e` est utilisé pour `E`vènements. `e` est utilisé pour `E`vènements.
- `e_NOM-EVENEMENT_N` : `N` le nom de la chaîne de caractère - `e_NOM-EVENEMENT_N` : `N` le nom de la chaîne de caractère
- Chaîne de charactère des utils : - Chaîne de charactère des utils :
`u` est utilisé pour `U`tilitaires. `u` est utilisé pour `U`tilitaires.
- `u_NOM-FICHIER-UTILS_N` : `N` le nom de la chaîne de caractère - `u_NOM-FICHIER-UTILS_N` : `N` le nom de la chaîne de caractère
### Ajouter une langue ### Ajouter une langue
@ -70,8 +70,8 @@ La norme pour les nom dans les fichiers est la suivante :
[cf. au dessus](#langues). [cf. au dessus](#langues).
3. Ce sont les valeurs des clés (le texte à gauche des `:`) qui doivent 3. Ce sont les valeurs des clés (le texte à gauche des `:`) qui doivent
être traduits. Merci par avance ! être traduits. Merci par avance !
> Ne vous forcez pas à tout traduire. Même une contribution avec > Ne vous forcez pas à tout traduire. Même une contribution avec
> une seule variable de modifiée compte ! > une seule variable de modifiée compte !
4. Une fois terminée, [ouvrez une Pull Request](#soumettre-ses-modifications). 4. Une fois terminée, [ouvrez une Pull Request](#soumettre-ses-modifications).
### Mettre à jour une langue ### Mettre à jour une langue
@ -79,23 +79,23 @@ La norme pour les nom dans les fichiers est la suivante :
1. Rechercher la langue dans le dossier [src/locales/](./src/locales/). 1. Rechercher la langue dans le dossier [src/locales/](./src/locales/).
2. Modifier/Ajouter des traductions comme 2. Modifier/Ajouter des traductions comme
[expliquer au dessus](#ajouter-une-langue) (à partir du `3.`). [expliquer au dessus](#ajouter-une-langue) (à partir du `3.`).
> Pensez à vérifier si de nouvelles valeurs n'ont pas été ajouté dans > Pensez à vérifier si de nouvelles valeurs n'ont pas été ajouté dans
> le fichier langue par défaut, [cf. au dessus](#langues). > le fichier langue par défaut, [cf. au dessus](#langues).
## Projet ## Projet
Le code se trouve dans le dosier [src/](./src/). Dans ce dossier il y a : Le code se trouve dans le dosier [src/](./src/). Dans ce dossier il y a :
- [commands/](./src/commands/) qui contient toutes les commandes, rangés par - [commands/](./src/commands/) qui contient toutes les commandes, rangés par
catégories catégories
- [events/](./src/events/) qui contient tous les évènements, rangés par - [events/](./src/events/) qui contient tous les évènements, rangés par
catégories catégories
- [locales/](./src/locales/) qui contient tous les fichiers de langue - [locales/](./src/locales/) qui contient tous les fichiers de langue
- [modules/](./src/modules/) qui contient les extensions utilisé, - [modules/](./src/modules/) qui contient les extensions utilisé,
par exemple, pour utiliser la fonction `capitalize()` d'un string, il faut par exemple, pour utiliser la fonction `capitalize()` d'un string, il faut
importer le fichier `string.ts` qui se trouve dans le dossier importer le fichier `string.ts` qui se trouve dans le dossier
- [utils/](./src/utils/) qui contient toutes les fonctions utilitaires, rangés - [utils/](./src/utils/) qui contient toutes les fonctions utilitaires, rangés
par fichiers par fichiers
Les dossiers [commands/](./src/commands/) et [events/](./src/events/) Les dossiers [commands/](./src/commands/) et [events/](./src/events/)
contiennent chaquin un fichier `loader.js` qui charge respectivement contiennent chaquin un fichier `loader.js` qui charge respectivement
@ -116,42 +116,42 @@ import { getLocale, getLocalizations } from "../../utils/locales";
import { getFilename } from "../../utils/misc"; import { getFilename } from "../../utils/misc";
export default { export default {
scope: () => [], scope: () => [],
data: (client: Client) => { data: (client: Client) => {
const filename = getFilename(__filename); const filename = getFilename(__filename);
return new SlashCommandBuilder() return new SlashCommandBuilder()
.setName(filename.toLowerCase()) .setName(filename.toLowerCase())
.setDescription( .setDescription(
client.locales client.locales
.get(client.config.default_lang) .get(client.config.default_lang)
?.get(`c_${filename}_desc`) ?? "" ?.get(`c_${filename}_desc`) ?? ""
) )
.setNameLocalizations( .setNameLocalizations(
getLocalizations(client, `c_${filename}_name`, true) getLocalizations(client, `c_${filename}_name`, true)
) )
.setDescriptionLocalizations( .setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`) getLocalizations(client, `c_${filename}_desc`)
); );
}, },
interaction: async ( interaction: async (
interaction: ChatInputCommandInteraction, interaction: ChatInputCommandInteraction,
client: Client client: Client
) => { ) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
/* Votre code ici */ /* Votre code ici */
}, },
}; };
``` ```
Rapidement, cette structure comporte 3 éléments : Rapidement, cette structure comporte 3 éléments :
- `scope` : une liste de guildId où la commande est disponible, si la liste est - `scope` : une liste de guildId où la commande est disponible, si la liste est
est vide, la commande est disponible partout est vide, la commande est disponible partout
- `data` : représente les données envoyées à l'API de Discord - `data` : représente les données envoyées à l'API de Discord
- `interaction` : représente le comportement de la commande - `interaction` : représente le comportement de la commande
Ce template vous permet de commencé rapidement votre commande car il contient Ce template vous permet de commencé rapidement votre commande car il contient
déjà tout ce qu'il faut pour le support des langues. Pensez bien à ne pas écrire déjà tout ce qu'il faut pour le support des langues. Pensez bien à ne pas écrire
@ -161,10 +161,10 @@ les [fichiers de langues](./src/locales/), c'est à ça que la variable
Vous devez aussi ajouter **obligatoirement** : Vous devez aussi ajouter **obligatoirement** :
- `"c_COMMANDE_name": "NOM"` au fichier de langue, avec `COMMANDE` le nom de - `"c_COMMANDE_name": "NOM"` au fichier de langue, avec `COMMANDE` le nom de
la commande et `NOM` le nom de votre commande. la commande et `NOM` le nom de votre commande.
- `"c_COMMANDE_desc": "DESCRIPTION"` au fichier de langue, avec `COMMANDE` - `"c_COMMANDE_desc": "DESCRIPTION"` au fichier de langue, avec `COMMANDE`
le nom de la commande et `DESCRIPTION` la description de votre commande. le nom de la commande et `DESCRIPTION` la description de votre commande.
## Ajouter un évènement ## Ajouter un évènement
@ -203,14 +203,14 @@ Quand vous modifiez quelque chose, pensez à mettre-à-jour les langues. Si vous
ne savez pas traduire dans une langue, ne vous forcez pas, supprimer simplement ne savez pas traduire dans une langue, ne vous forcez pas, supprimer simplement
la traduction. la traduction.
- [Créez un fork](https://git.kennel.ml/repo/fork/76) et poussez - [Créez un fork](https://git.kennel.ml/repo/fork/76) et poussez
vos modifications dans ce dernier. vos modifications dans ce dernier.
Pour commencer, vous pouvez jeté un oeil aux Pour commencer, vous pouvez jeté un oeil aux
[tickets facilement résolvable](https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/issues?state=open&labels=82). [tickets facilement résolvable](https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/issues?state=open&labels=82).
- De préférences, les fonctions, méthodes et variables seront écrites - De préférences, les fonctions, méthodes et variables seront écrites
en anglais, ainsi que les commits afin que chacun puisse contribuer. en anglais, ainsi que les commits afin que chacun puisse contribuer.
## Soumettre ses modifications ## Soumettre ses modifications
@ -243,10 +243,10 @@ Pour commencer, vous pouvez jeté un oeil aux
## Gestion du dépôt ## Gestion du dépôt
- On ne push jamais directement sur la branche `main`. - On ne push jamais directement sur la branche `main`.
- Quand on merge des modifications vers `main`, on fait un _squash_, - Quand on merge des modifications vers `main`, on fait un _squash_,
l'historique des modifications reste disponible dans l'historique des modifications reste disponible dans
[le graph](https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/graph). [le graph](https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/graph).
- De préférences, suivre les indications de - De préférences, suivre les indications de
[ce post](https://gist.github.com/revett/88ee5abf5a9a097b4c88) (c'est un peu la [ce post](https://gist.github.com/revett/88ee5abf5a9a097b4c88) (c'est un peu la
même que dans le `4.` de [la partie précédente](#soumettre-ses-modifications)). même que dans le `4.` de [la partie précédente](#soumettre-ses-modifications)).

View file

@ -3,23 +3,29 @@
[**Ajoute le bot à ton 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
### En local ### En local
> Cloner le repo. > Cloner le repo.
> Spécifier un fichier `.env` en suivant [l'exemple](config/example.env). > Spécifier un fichier `.env` en suivant [l'exemple](config/example.env).
> Installer les dépendences du bot. > Installer les dépendences du bot.
```bash ```bash
npm install npm install
``` ```
> Lancer le bot. > Lancer le bot.
```bash ```bash
npm run main npm run main
``` ```
### Avec Docker (Recommandé) ### Avec Docker (Recommandé)
> Facile avec `docker-compose` > Facile avec `docker-compose`
```docker ```docker
version: "3.9" version: "3.9"
services: services:
@ -34,23 +40,28 @@ services:
``` ```
## Variables d'environnements ## Variables d'environnements
| Nom | Description | Par défaut | Commentaire
| :-----------: | :--------------: | :--------: | :-: | Nom | Description | Par défaut | Commentaire |
| TOKEN_DISCORD | Token Discord | Aucune | | :-----------: | :---------------: | :--------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| DEFAULT_LANG | Langue par défaut | `fr` | Expérimental, si la langue par défaut n'est pas complète (càd 100%), le bot pourrait ne pas fonctionner correctement.<br>Liste des traductions disponibles [ici](./src/locales/). | TOKEN_DISCORD | Token Discord | Aucune |
| DEFAULT_LANG | Langue par défaut | `fr` | Expérimental, si la langue par défaut n'est pas complète (càd 100%), le bot pourrait ne pas fonctionner correctement.<br>Liste des traductions disponibles [ici](./src/locales/). |
## Volumes ## Volumes
| Chemin | Description
| :-------: | :-: | Chemin | Description |
| `/config` | Dossier de configuration, par exemple, c'est ici que la base de donnée est. | :-------: | :-------------------------------------------------------------------------: |
| `/config` | Dossier de configuration, par exemple, c'est ici que la base de donnée est. |
# Contribuer # Contribuer
Toute contribution est la bienvenue ! Toute contribution est la bienvenue !
Pour commencer, lis le [fichier de contribution](./CONTRIBUTING.md). Pour commencer, lis le [fichier de contribution](./CONTRIBUTING.md).
# Licence # Licence
Voir le [fichier LICENCE](./LICENCE). Voir le [fichier LICENCE](./LICENCE).
# Références # Références
[Photo de profil](https://picrew.me/image_maker/1497656) [Photo de profil](https://picrew.me/image_maker/1497656)

22
package-lock.json generated
View file

@ -23,6 +23,7 @@
"@typescript-eslint/parser": "^5.30.7", "@typescript-eslint/parser": "^5.30.7",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"eslint": "^8.20.0", "eslint": "^8.20.0",
"prettier": "2.8.3",
"ts-node-dev": "^2.0.0" "ts-node-dev": "^2.0.0"
} }
}, },
@ -2438,6 +2439,21 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/prettier": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/promise-inflight": { "node_modules/promise-inflight": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -5110,6 +5126,12 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true "dev": true
}, },
"prettier": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.3.tgz",
"integrity": "sha512-tJ/oJ4amDihPoufT5sM0Z1SKEuKay8LfVAMlbbhnnkvt6BUserZylqo2PN+p9KeljLr0OHa2rXHU1T8reeoTrw==",
"dev": true
},
"promise-inflight": { "promise-inflight": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",

View file

@ -6,7 +6,8 @@
"scripts": { "scripts": {
"main": "rm -r dist 2> /dev/null; npx tsc && node ./dist/index.js", "main": "rm -r dist 2> /dev/null; npx tsc && 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": "npx prettier --check src"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -17,18 +18,19 @@
"dependencies": { "dependencies": {
"@discordjs/rest": "^1.1.0", "@discordjs/rest": "^1.1.0",
"@types/sqlite3": "^3.1.8", "@types/sqlite3": "^3.1.8",
"@types/uuid": "^9.0.0",
"discord-api-types": "^0.36.3", "discord-api-types": "^0.36.3",
"discord.js": "^14.3.0", "discord.js": "^14.3.0",
"sqlite3": "^5.0.11", "sqlite3": "^5.0.11",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"uuid": "^9.0.0", "uuid": "^9.0.0"
"@types/uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.30.7", "@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7", "@typescript-eslint/parser": "^5.30.7",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"eslint": "^8.20.0", "eslint": "^8.20.0",
"prettier": "2.8.3",
"ts-node-dev": "^2.0.0" "ts-node-dev": "^2.0.0"
} }
} }

View file

@ -1,39 +1,44 @@
import { readdir } from 'fs/promises'; import { readdir } from "fs/promises";
import { removeExtension } from '../utils/misc'; import { removeExtension } from "../utils/misc";
import { ChatInputCommandInteraction, Client, MessageComponentInteraction } from 'discord.js'; import {
import { getLocale } from '../utils/locales'; ChatInputCommandInteraction,
Client,
MessageComponentInteraction,
} from "discord.js";
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)) const buttons_categories = (await readdir(__dirname)).filter(
.filter(element => !element.endsWith('.js') && !element.endsWith('.ts')); (element) => !element.endsWith(".js") && !element.endsWith(".ts")
);
await Promise.all( await Promise.all(
// For each categorie // For each categorie
buttons_categories.map(async buttons_category => { buttons_categories.map(async (buttons_category) => {
// Retrieve all the commands // Retrieve all the commands
const button_files = await readdir(`${__dirname}/${buttons_category}`); const button_files = await readdir(`${__dirname}/${buttons_category}`);
// Add the category to the collection for the help command // Add the category to the collection for the help command
client.buttons.categories.set( client.buttons.categories.set(
buttons_category, buttons_category,
button_files.map(removeExtension), button_files.map(removeExtension)
); );
// Add the button // Add the button
return Promise.all( return Promise.all(
button_files.map(async button_file => { button_files.map(async (button_file) => {
const button = ( const button = (
await import(`../buttons/${buttons_category}/${button_file}`) await import(`../buttons/${buttons_category}/${button_file}`)
).default; ).default;
// Add it to the collection so the interaction will work // Add it to the collection so the interaction will work
client.buttons.list.set(button.data.name, button); client.buttons.list.set(button.data.name, button);
return button.data; return button.data;
}), })
); );
}), })
); );
}; };
/** /**
@ -43,26 +48,34 @@ export default async (client: Client) => {
* @param id Button ID * @param id Button ID
* @param deferUpdate defer update in case update take time * @param deferUpdate defer update in case update take time
*/ */
export const collect = (client: Client, interaction: ChatInputCommandInteraction | MessageComponentInteraction, id: string, deferUpdate = false) => { export const collect = (
const loc = getLocale(client, interaction.locale); client: Client,
const button = client.buttons.list.get(id.split('_')[0]); interaction: ChatInputCommandInteraction | MessageComponentInteraction,
id: string,
deferUpdate = false
) => {
const loc = getLocale(client, interaction.locale);
const button = client.buttons.list.get(id.split("_")[0]);
if (!button) { if (!button) {
interaction.reply({ interaction.reply({
content: loc.get('e_interacreate_no_button'), content: loc.get("e_interacreate_no_button"),
ephemeral: true, ephemeral: true,
}); });
} }
const filter = (i: MessageComponentInteraction) => i.customId === id; const filter = (i: MessageComponentInteraction) => i.customId === id;
const collector = interaction.channel?.createMessageComponentCollector({ filter, max: 1 }); const collector = interaction.channel?.createMessageComponentCollector({
collector?.on('collect', async (i) => { filter,
if (deferUpdate) { max: 1,
await i.deferUpdate(); });
} collector?.on("collect", async (i) => {
const msg = await button?.interaction(i, client); if (deferUpdate) {
if (msg !== undefined) { await i.deferUpdate();
await i.update(msg); }
} const msg = await button?.interaction(i, client);
}); if (msg !== undefined) {
await i.update(msg);
}
});
}; };

View file

@ -1,56 +1,73 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, MessageComponentInteraction, User } from 'discord.js'; import {
import { v4 as uuidv4 } from 'uuid'; ActionRowBuilder,
import { getLocale } from '../../utils/locales'; ButtonBuilder,
import { getFilename } from '../../utils/misc'; ButtonStyle,
import { embedListReminders } from '../../utils/reminder'; Client,
import { collect } from '../loader'; MessageComponentInteraction,
User,
} from "discord.js";
import { v4 as uuidv4 } from "uuid";
import { getLocale } from "../../utils/locales";
import { getFilename } from "../../utils/misc";
import { embedListReminders } from "../../utils/reminder";
import { collect } from "../loader";
export default { export default {
data: { data: {
name: getFilename(__filename), name: getFilename(__filename),
}, },
interaction: async (interaction: MessageComponentInteraction, client: Client) => { interaction: async (
const loc = getLocale(client, interaction.locale); interaction: MessageComponentInteraction,
const embed_desc = interaction.message.embeds.at(0)?.description as string; client: Client
) => {
const loc = getLocale(client, interaction.locale);
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 {
page++; page++;
} }
// Retrieve user // Retrieve user
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string; const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
const user = client.users.cache.get(userId) as User; const user = client.users.cache.get(userId) as User;
// Fetch list // Fetch list
const list = await embedListReminders(client, user, interaction.guildId, page, interaction.locale); const list = await embedListReminders(
client,
user,
interaction.guildId,
page,
interaction.locale
);
const idPrec = 'reminderList-prec_' + uuidv4(); const idPrec = "reminderList-prec_" + uuidv4();
const idNext = 'reminderList-next_' + uuidv4(); const idNext = "reminderList-next_" + uuidv4();
const row = new ActionRowBuilder<ButtonBuilder>() const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId(idPrec) .setCustomId(idPrec)
.setLabel(loc.get('c_reminder12')) .setLabel(loc.get("c_reminder12"))
.setStyle(ButtonStyle.Primary)) .setStyle(ButtonStyle.Primary)
.addComponents( )
new ButtonBuilder() .addComponents(
.setCustomId(idNext) new ButtonBuilder()
.setLabel(loc.get('c_reminder13')) .setCustomId(idNext)
.setStyle(ButtonStyle.Primary), .setLabel(loc.get("c_reminder13"))
); .setStyle(ButtonStyle.Primary)
);
// Buttons interactions // Buttons interactions
collect(client, interaction, idPrec); collect(client, interaction, idPrec);
collect(client, interaction, idNext); collect(client, interaction, idNext);
return { return {
embeds: [list], embeds: [list],
components: [row], components: [row],
}; };
}, },
}; };

View file

@ -1,56 +1,73 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, MessageComponentInteraction, User } from 'discord.js'; import {
import { v4 as uuidv4 } from 'uuid'; ActionRowBuilder,
import { getLocale } from '../../utils/locales'; ButtonBuilder,
import { getFilename } from '../../utils/misc'; ButtonStyle,
import { embedListReminders } from '../../utils/reminder'; Client,
import { collect } from '../loader'; MessageComponentInteraction,
User,
} from "discord.js";
import { v4 as uuidv4 } from "uuid";
import { getLocale } from "../../utils/locales";
import { getFilename } from "../../utils/misc";
import { embedListReminders } from "../../utils/reminder";
import { collect } from "../loader";
export default { export default {
data: { data: {
name: getFilename(__filename), name: getFilename(__filename),
}, },
interaction: async (interaction: MessageComponentInteraction, client: Client) => { interaction: async (
const loc = getLocale(client, interaction.locale); interaction: MessageComponentInteraction,
const embed_desc = interaction.message.embeds.at(0)?.description as string; client: Client
) => {
const loc = getLocale(client, interaction.locale);
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] as string; const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
const user = client.users.cache.get(userId) as User; const user = client.users.cache.get(userId) as User;
// Fetch list // Fetch list
const list = await embedListReminders(client, user, interaction.guildId, page, interaction.locale); const list = await embedListReminders(
client,
user,
interaction.guildId,
page,
interaction.locale
);
const idPrec = 'reminderList-prec_' + uuidv4(); const idPrec = "reminderList-prec_" + uuidv4();
const idNext = 'reminderList-next_' + uuidv4(); const idNext = "reminderList-next_" + uuidv4();
const row = new ActionRowBuilder<ButtonBuilder>() const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId(idPrec) .setCustomId(idPrec)
.setLabel(loc.get('c_reminder12')) .setLabel(loc.get("c_reminder12"))
.setStyle(ButtonStyle.Primary)) .setStyle(ButtonStyle.Primary)
.addComponents( )
new ButtonBuilder() .addComponents(
.setCustomId(idNext) new ButtonBuilder()
.setLabel(loc.get('c_reminder13')) .setCustomId(idNext)
.setStyle(ButtonStyle.Primary), .setLabel(loc.get("c_reminder13"))
); .setStyle(ButtonStyle.Primary)
);
// Buttons interactions // Buttons interactions
collect(client, interaction, idPrec); collect(client, interaction, idPrec);
collect(client, interaction, idNext); collect(client, interaction, idNext);
return { return {
embeds: [list], embeds: [list],
components: [row], components: [row],
}; };
}, },
}; };

View file

@ -1,77 +1,84 @@
import { REST } from '@discordjs/rest'; import { REST } from "@discordjs/rest";
import { Routes } from 'discord-api-types/v9'; import { Routes } from "discord-api-types/v9";
import { Client } from 'discord.js'; import { Client } from "discord.js";
import { readdir } from 'fs/promises'; import { readdir } from "fs/promises";
import { removeExtension } from '../utils/misc'; import { removeExtension } from "../utils/misc";
/** Load all the commands. */ /** Load all the commands. */
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)) const command_categories = (await readdir(__dirname)).filter(
.filter(element => !element.endsWith('.js') && !element.endsWith('.ts')); (element) => !element.endsWith(".js") && !element.endsWith(".ts")
);
const commands = ( const commands = (
await Promise.all( await Promise.all(
// For each categorie // For each categorie
command_categories.map(async command_category => { command_categories.map(async (command_category) => {
// Retrieve all the commands // Retrieve all the commands
const command_files = await readdir(`${__dirname}/${command_category}`); const command_files = await readdir(`${__dirname}/${command_category}`);
// Add the category to the collection for the help command // Add the category to the collection for the help command
client.commands.categories.set( client.commands.categories.set(
command_category, command_category,
command_files.map(removeExtension), command_files.map(removeExtension)
); );
// Add the command // Add the command
return Promise.all( return Promise.all(
command_files.map(async command_file => { command_files.map(async (command_file) => {
const command = ( const command = (
await import(`../commands/${command_category}/${command_file}`) await import(`../commands/${command_category}/${command_file}`)
).default; ).default;
// Add it to the collection so the interaction will work // Add it to the collection so the interaction will work
command.data = command.data(client); command.data = command.data(client);
client.commands.list.set(command.data.name, command); client.commands.list.set(command.data.name, command);
return command; return command;
}), })
); );
}), })
) )
).flat(2); ).flat(2);
// Send guilds commands to Discord // Send guilds commands to Discord
const scopedCommands = new Map<string, unknown[]>(); const scopedCommands = new Map<string, unknown[]>();
// Add commands to guild where the bot is // Add commands to guild where the bot is
const allowedGuilds = client.guilds.cache; const allowedGuilds = client.guilds.cache;
// Assign each commands to the guilds // Assign each commands to the guilds
commands.filter((c) => c.scope().length > 0).forEach((c) => { commands
c.scope().forEach((guild: string) => { .filter((c) => c.scope().length > 0)
if (allowedGuilds.get(guild) !== undefined) { .forEach((c) => {
const guildCommands = scopedCommands.get(guild); c.scope().forEach((guild: string) => {
if (guildCommands === undefined) { if (allowedGuilds.get(guild) !== undefined) {
scopedCommands.set(guild, [c.data.toJSON()]); const guildCommands = scopedCommands.get(guild);
} else { if (guildCommands === undefined) {
guildCommands.push(c.data.toJSON()); scopedCommands.set(guild, [c.data.toJSON()]);
scopedCommands.set(guild, guildCommands); } else {
} guildCommands.push(c.data.toJSON());
} scopedCommands.set(guild, guildCommands);
}); }
}); }
});
});
scopedCommands scopedCommands.forEach(
.forEach(async (command, guild) => await rest.put( async (command, guild) =>
Routes.applicationGuildCommands(client.user?.id as string, guild), { await rest.put(
body: command, Routes.applicationGuildCommands(client.user?.id as string, guild),
})); {
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 as string), { 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()),
}); });
}; };

View file

@ -1,129 +1,159 @@
import { SlashCommandBuilder } from '@discordjs/builders'; import { SlashCommandBuilder } from "@discordjs/builders";
import { ChannelType, Client, Colors, CommandInteraction, EmbedBuilder, NonThreadGuildBasedChannel } from 'discord.js'; import {
import '../../modules/string'; ChannelType,
import { getLocale, getLocalizations } from '../../utils/locales'; Client,
import { getFilename } from '../../utils/misc'; Colors,
CommandInteraction,
EmbedBuilder,
NonThreadGuildBasedChannel,
} from "discord.js";
import "../../modules/string";
import { getLocale, getLocalizations } from "../../utils/locales";
import { getFilename } from "../../utils/misc";
export default { export default {
scope: () => ['807244911350906920'], scope: () => ["807244911350906920"],
data: (client: Client) => { data: (client: Client) => {
const filename = getFilename(__filename); const filename = getFilename(__filename);
return new SlashCommandBuilder() return (
.setName( new SlashCommandBuilder()
filename.toLowerCase()) .setName(filename.toLowerCase())
.setDescription(client.locales.get(client.config.default_lang) .setDescription(
?.get(`c_${filename}_desc`) ?? '') client.locales
.setNameLocalizations( .get(client.config.default_lang)
getLocalizations(client, `c_${filename}_name`, true)) ?.get(`c_${filename}_desc`) ?? ""
.setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_desc`)) .setNameLocalizations(
getLocalizations(client, `c_${filename}_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`)
)
// Command option // Command option
.addStringOption(option => option .addStringOption((option) =>
.setName(client.locales.get(client.config.default_lang) option
?.get(`c_${filename}_opt1_name`) ?? '') .setName(
.setDescription(client.locales.get(client.config.default_lang) client.locales
?.get(`c_${filename}_opt1_desc`) ?? '') .get(client.config.default_lang)
.setNameLocalizations( ?.get(`c_${filename}_opt1_name`) ?? ""
getLocalizations(client, `c_${filename}_opt1_name`, true)) )
.setDescriptionLocalizations( .setDescription(
getLocalizations(client, `c_${filename}_opt1_desc`)) 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) => { interaction: async (interaction: CommandInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
const desiredCat = interaction.options.get(client const desiredCat = interaction.options.get(
.locales client.locales
.get(client.config.default_lang) .get(client.config.default_lang)
?.get(`c_${getFilename(__filename)}_opt1_name`) ?? '')?.value as string; ?.get(`c_${getFilename(__filename)}_opt1_name`) ?? ""
)?.value as string;
// If a category isn't specified // If a category isn't specified
if (!desiredCat) { 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")),
],
});
}
// Sends a list of commands sorted into categories // If a category is specified
return interaction.reply({ const cleanCat = ["L1", "L2", "L3", "M1", "M2"];
embeds: [ const channel = cleanCat.includes(desiredCat);
new EmbedBuilder() if (!channel) {
.setColor(Colors.Blurple) // Category doesn't exist or is not included
.setTitle(loc.get('c_archive1')) return interaction.reply({
.setDescription(loc.get('c_archive2')), content: `${loc.get("c_archive3")} \`${desiredCat}\``,
], ephemeral: true,
}); });
} }
// If a category is specified const allChannel = interaction.guild?.channels.fetch();
const cleanCat = ['L1', 'L2', 'L3', 'M1', 'M2']; allChannel?.then(async (channelGuild) => {
const channel = cleanCat.includes(desiredCat); // Retrieve category to archive
if (!channel) { const catToArchive = channelGuild
// Category doesn't exist or is not included .filter((chan) => chan?.type == ChannelType.GuildCategory)
return interaction.reply({ .filter((chan) => chan?.name == desiredCat);
content: `${loc.get('c_archive3')} \`${desiredCat}\``,
ephemeral: true,
});
}
const allChannel = interaction.guild?.channels.fetch(); // Create/Retrieve the archive category
allChannel?.then(async channelGuild => { const catArchivedName = "archive - " + desiredCat;
// Retrieve category to archive const catArchivedMap = channelGuild
const catToArchive = channelGuild .filter((chan) => chan?.type == ChannelType.GuildCategory)
.filter(chan => chan?.type == ChannelType.GuildCategory) .filter((chan) => chan?.name == catArchivedName);
.filter(chan => chan?.name == desiredCat);
// Create/Retrieve the archive category let catArchived: NonThreadGuildBasedChannel | null | undefined;
const catArchivedName = 'archive - ' + desiredCat; if (catArchivedMap.size > 0) {
const catArchivedMap = channelGuild catArchived = catArchivedMap.at(0);
.filter(chan => chan?.type == ChannelType.GuildCategory) } else {
.filter(chan => chan?.name == catArchivedName); catArchived = await interaction.guild?.channels.create({
name: catArchivedName,
type: ChannelType.GuildCategory,
});
}
let catArchived: NonThreadGuildBasedChannel | null | undefined; const allChannelDesired = channelGuild
if (catArchivedMap.size > 0) { .filter((chan) => chan?.type == 0)
catArchived = catArchivedMap.at(0); .filter(
} else { (chan) => chan?.parentId == catToArchive.map((cat) => cat?.id)[0]
catArchived = await interaction.guild?.channels );
.create({ name: catArchivedName, type: ChannelType.GuildCategory });
}
const allChannelDesired = channelGuild // If no channels in the source category
.filter(chan => chan?.type == 0) if (allChannelDesired.size == 0) {
.filter(chan => chan?.parentId == catToArchive.map(cat => cat?.id)[0]); return interaction.reply({
embeds: [
new EmbedBuilder()
.setColor(Colors.Blurple)
.setTitle(loc.get("c_archive6"))
.setDescription(loc.get("c_archive7")),
],
});
}
// If no channels in the source category // Move channels to the archived categoryx
if (allChannelDesired.size == 0) { allChannelDesired.forEach((elem) =>
return interaction.reply({ elem?.setParent(catArchived?.id as string)
embeds: [ );
new EmbedBuilder()
.setColor(Colors.Blurple)
.setTitle(loc.get('c_archive6'))
.setDescription(
loc.get('c_archive7')
),
],
});
}
// Move channels to the archived categoryx return interaction.reply({
allChannelDesired.forEach(elem => elem?.setParent(catArchived?.id as string)); embeds: [
new EmbedBuilder()
return interaction.reply({ .setColor(Colors.Blurple)
embeds: [ .setTitle(
new EmbedBuilder() loc.get("c_archive4") +
.setColor(Colors.Blurple) " `" +
.setTitle(loc.get('c_archive4') catToArchive.map((cat) => cat?.name) +
+ ' `' "` " +
+ catToArchive.map(cat => cat?.name) loc.get("c_archive5") +
+ '` ' " `" +
+ loc.get('c_archive5') catArchivedName +
+ ' `' "`"
+ catArchivedName )
+ '`') .setDescription(
.setDescription( allChannelDesired
allChannelDesired .map((cgD) => cgD?.name)
.map(cgD => cgD?.name).toString().replaceAll(',', '\n') .toString()
), .replaceAll(",", "\n")
], ),
}); ],
}); });
}, });
},
}; };

View file

@ -1,98 +1,126 @@
import { SlashCommandBuilder } from '@discordjs/builders'; import { SlashCommandBuilder } from "@discordjs/builders";
import { Locale } from 'discord-api-types/v9'; import { Locale } from "discord-api-types/v9";
import { Client, ChatInputCommandInteraction, EmbedBuilder, Colors } from 'discord.js'; import {
import { getLocale, getLocalizations } from '../../utils/locales'; Client,
import { getFilename } from '../../utils/misc'; ChatInputCommandInteraction,
import '../../modules/string'; EmbedBuilder,
Colors,
} from "discord.js";
import { getLocale, getLocalizations } from "../../utils/locales";
import { getFilename } from "../../utils/misc";
import "../../modules/string";
export default { export default {
scope: () => [], scope: () => [],
data: (client: Client) => { data: (client: Client) => {
const filename = getFilename(__filename); const filename = getFilename(__filename);
return new SlashCommandBuilder() return (
.setName( new SlashCommandBuilder()
filename.toLowerCase()) .setName(filename.toLowerCase())
.setDescription(client.locales.get(client.config.default_lang) .setDescription(
?.get(`c_${filename}_desc`) ?? '') client.locales
.setNameLocalizations( .get(client.config.default_lang)
getLocalizations(client, `c_${filename}_name`, true)) ?.get(`c_${filename}_desc`) ?? ""
.setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_desc`)) .setNameLocalizations(
getLocalizations(client, `c_${filename}_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`)
)
// Command option // Command option
.addStringOption(option => option .addStringOption((option) =>
.setName(client.locales.get(client.config.default_lang) option
?.get(`c_${filename}_opt1_name`) ?? '') .setName(
.setDescription(client.locales.get(client.config.default_lang) client.locales
?.get(`c_${filename}_opt1_desc`) ?? '') .get(client.config.default_lang)
.setNameLocalizations( ?.get(`c_${filename}_opt1_name`) ?? ""
getLocalizations(client, `c_${filename}_opt1_name`, true)) )
.setDescriptionLocalizations( .setDescription(
getLocalizations(client, `c_${filename}_opt1_desc`)) 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: ChatInputCommandInteraction, client: Client) => { interaction: async (
const loc = getLocale(client, interaction.locale); interaction: ChatInputCommandInteraction,
const desired_command = interaction.options.getString(client client: Client
.locales ) => {
.get(client.config.default_lang) const loc = getLocale(client, interaction.locale);
?.get(`c_${getFilename(__filename)}_opt1_name`) ?? ''); const desired_command = interaction.options.getString(
client.locales
.get(client.config.default_lang)
?.get(`c_${getFilename(__filename)}_opt1_name`) ?? ""
);
// If a command isn't specified // If a command isn't specified
if (!desired_command) { if (!desired_command) {
const fields: { const fields: {
name: string; name: string;
value: string; value: string;
}[] = []; }[] = [];
// Load all the command per categories // Load all the command per categories
// TODO: Check if the command exist in the context (guild) // TODO: Check if the command exist in the context (guild)
client.commands.categories.forEach((commands_name, category) => { client.commands.categories.forEach((commands_name, category) => {
const commands = commands_name.reduce((data, command_name) => { const commands = commands_name.reduce((data, command_name) => {
return data + `\`/${command_name}\`, `; return data + `\`/${command_name}\`, `;
}, ''); }, "");
fields.push({ fields.push({
name: category.capitalize() + ` (${commands_name.length})`, name: category.capitalize() + ` (${commands_name.length})`,
value: commands.slice(0, -2), value: commands.slice(0, -2),
}); });
}); });
// Sends a list of commands sorted into categories // Sends a list of commands sorted into categories
return interaction.reply({ embeds: [ return interaction.reply({
new EmbedBuilder() embeds: [
.setColor(Colors.Blurple) new EmbedBuilder()
.setTitle(loc.get('c_help1')) .setColor(Colors.Blurple)
.setDescription(loc.get('c_help2')) .setTitle(loc.get("c_help1"))
.addFields(fields), .setDescription(loc.get("c_help2"))
] }); .addFields(fields),
} ],
});
}
// If a command is specified // If a command is specified
// TODO: Check if the command exist in the context (guild) // TODO: Check if the command exist in the context (guild)
const command = client.commands.list.get(desired_command); const command = client.commands.list.get(desired_command);
if (!command) { if (!command) {
// Command don't exist // Command don't exist
return interaction.reply({ return interaction.reply({
content: `${loc.get('c_help3')} \`${desired_command}\``, content: `${loc.get("c_help3")} \`${desired_command}\``,
ephemeral: true, ephemeral: true,
}); });
} }
// Send information about the command // Send information about the command
return interaction.reply({ embeds: [ return interaction.reply({
new EmbedBuilder() embeds: [
.setColor(Colors.Blurple) new EmbedBuilder()
.setTitle('`/' + command.data.name + '`') .setColor(Colors.Blurple)
.setDescription( .setTitle("`/" + command.data.name + "`")
// Loads the description .setDescription(
// according to the user's locals // Loads the description
command.data.description_localizations // according to the user's locals
?.[interaction.locale as Locale] command.data.description_localizations?.[
?? command.data.description interaction.locale as Locale
), ] ?? command.data.description
] }); ),
}, ],
});
},
}; };

View file

@ -1,36 +1,43 @@
import { SlashCommandBuilder } from '@discordjs/builders'; import { SlashCommandBuilder } from "@discordjs/builders";
import { ChatInputCommandInteraction, Client, Message } from 'discord.js'; import { ChatInputCommandInteraction, Client, Message } 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";
export default { export default {
scope: () => [], scope: () => [],
data: (client: Client) => { data: (client: Client) => {
const filename = getFilename(__filename); const filename = getFilename(__filename);
return new SlashCommandBuilder() return new SlashCommandBuilder()
.setName( .setName(filename.toLowerCase())
filename.toLowerCase()) .setDescription(
.setDescription(client.locales.get(client.config.default_lang) client.locales
?.get(`c_${filename}_desc`) ?? '') .get(client.config.default_lang)
.setNameLocalizations( ?.get(`c_${filename}_desc`) ?? ""
getLocalizations(client, `c_${filename}_name`, true)) )
.setDescriptionLocalizations( .setNameLocalizations(
getLocalizations(client, `c_${filename}_desc`) getLocalizations(client, `c_${filename}_name`, true)
); )
}, .setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`)
);
},
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => { interaction: async (
const loc = getLocale(client, interaction.locale); interaction: ChatInputCommandInteraction,
client: Client
) => {
const loc = getLocale(client, interaction.locale);
const sent = await interaction.reply({ const sent = (await interaction.reply({
content: 'Pinging...', content: "Pinging...",
fetchReply: true, fetchReply: true,
}) as Message; })) as Message;
interaction.editReply( interaction.editReply(
`${loc?.get('c_ping1')}: \ `${loc?.get("c_ping1")}: \
${sent.createdTimestamp - interaction.createdTimestamp}ms ${sent.createdTimestamp - interaction.createdTimestamp}ms
${loc?.get('c_ping2')}: ${client.ws.ping}ms.`); ${loc?.get("c_ping2")}: ${client.ws.ping}ms.`
}, );
},
}; };

View file

@ -1,117 +1,144 @@
import { SlashCommandBuilder } from '@discordjs/builders'; import { SlashCommandBuilder } from "@discordjs/builders";
import { ChannelType, Client, Colors, CommandInteraction, EmbedBuilder } from 'discord.js'; import {
import '../../modules/string'; ChannelType,
import { getLocale, getLocalizations } from '../../utils/locales'; Client,
import { getFilename } from '../../utils/misc'; Colors,
CommandInteraction,
EmbedBuilder,
} from "discord.js";
import "../../modules/string";
import { getLocale, getLocalizations } from "../../utils/locales";
import { getFilename } from "../../utils/misc";
export default { export default {
scope: () => ['807244911350906920'], scope: () => ["807244911350906920"],
data: (client: Client) => { data: (client: Client) => {
const filename = getFilename(__filename); const filename = getFilename(__filename);
return new SlashCommandBuilder() return (
.setName( new SlashCommandBuilder()
filename.toLowerCase()) .setName(filename.toLowerCase())
.setDescription(client.locales.get(client.config.default_lang) .setDescription(
?.get(`c_${filename}_desc`) ?? '') client.locales
.setNameLocalizations( .get(client.config.default_lang)
getLocalizations(client, `c_${filename}_name`, true)) ?.get(`c_${filename}_desc`) ?? ""
.setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_desc`)) .setNameLocalizations(
getLocalizations(client, `c_${filename}_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`)
)
// Command option // Command option
.addStringOption(option => option .addStringOption((option) =>
.setName(client.locales.get(client.config.default_lang) option
?.get(`c_${filename}_opt1_name`) ?? '') .setName(
.setDescription(client.locales.get(client.config.default_lang) client.locales
?.get(`c_${filename}_opt1_desc`) ?? '') .get(client.config.default_lang)
.setNameLocalizations( ?.get(`c_${filename}_opt1_name`) ?? ""
getLocalizations(client, `c_${filename}_opt1_name`, true)) )
.setDescriptionLocalizations( .setDescription(
getLocalizations(client, `c_${filename}_opt1_desc`)) 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) => { interaction: async (interaction: CommandInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
const desired_cat = interaction.options.get(client const desired_cat = interaction.options.get(
.locales client.locales
.get(client.config.default_lang) .get(client.config.default_lang)
?.get(`c_${getFilename(__filename)}_opt1_name`) ?? '')?.value as string; ?.get(`c_${getFilename(__filename)}_opt1_name`) ?? ""
)?.value as string;
// If a category isn't specified // If a category isn't specified
if (!desired_cat) { 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")),
],
});
}
// Sends a list of commands sorted into categories // If a category is specified
return interaction.reply({ const allowedCategories = ["L1", "L2", "L3", "M1", "M2"];
embeds: [ const channel = allowedCategories.includes(desired_cat);
new EmbedBuilder() if (!channel) {
.setColor(Colors.Blurple) // Category doesn't exist or is not allowed
.setTitle(loc.get('c_prep1')) return interaction.reply({
.setDescription(loc.get('c_prep2')), content: `${loc.get("c_prep3")} \`${desired_cat}\``,
], ephemeral: true,
}); });
} }
// If a category is specified // Send information about the command
const allowedCategories = ['L1', 'L2', 'L3', 'M1', 'M2']; const allChannel = interaction.guild?.channels.fetch();
const channel = allowedCategories.includes(desired_cat); allChannel?.then((channel_guild) => {
if (!channel) { const cat_to_prep = channel_guild
// Category doesn't exist or is not allowed .filter((chan) => chan?.type == ChannelType.GuildCategory)
return interaction.reply({ .filter((chan) => chan?.name == desired_cat);
content: `${loc.get('c_prep3')} \`${desired_cat}\``, const cat_to_prep_id = cat_to_prep.map((cat) => cat?.id);
ephemeral: true, const cat_to_prep_name = cat_to_prep.map((cat) => cat?.name);
});
}
// Send information about the command // console.log(cat_to_prep);
const allChannel = interaction.guild?.channels.fetch(); const all_channel_desired = channel_guild
allChannel?.then(channel_guild => { .filter((chan) => chan?.type == 0)
const cat_to_prep = channel_guild.filter(chan => chan?.type == ChannelType.GuildCategory).filter(chan => chan?.name == desired_cat); .filter((chan) => chan?.parentId == cat_to_prep_id[0]);
const cat_to_prep_id = cat_to_prep.map(cat => cat?.id); const all_channel_desired_name = all_channel_desired.map(
const cat_to_prep_name = cat_to_prep.map(cat => cat?.name); (c_d) => c_d?.name
);
// console.log(cat_to_prep); let desc = "";
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 general = 'général'; const info = "informations";
if (all_channel_desired_name.filter(cdn => cdn == general).length == 0) { if (all_channel_desired_name.filter((cdn) => cdn == info).length == 0) {
interaction.guild?.channels.create({ interaction.guild?.channels.create({
name: general, name: info,
type: 0, type: 0,
parent: cat_to_prep_id[0], parent: cat_to_prep_id[0],
}); });
desc = general + loc.get('c_prep5') + '\n';
}
const info = 'informations'; desc += "`" + info + "` " + loc.get("c_prep5") + "\n";
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");
}
if (desc == '') { return interaction.reply({
desc = loc.get('c_prep6'); embeds: [
} new EmbedBuilder()
.setColor(Colors.Blurple)
return interaction.reply({ .setTitle(loc.get("c_prep4") + cat_to_prep_name)
embeds: [ .setDescription(desc),
new EmbedBuilder() ],
.setColor(Colors.Blurple) });
.setTitle(loc.get('c_prep4') + cat_to_prep_name) });
.setDescription( },
desc,
),
],
});
});
},
}; };

View file

@ -1,277 +1,354 @@
import { ModalActionRowComponentBuilder, SlashCommandBuilder } from '@discordjs/builders'; import {
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, Client, ModalBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; ModalActionRowComponentBuilder,
import { v4 as uuidv4 } from 'uuid'; SlashCommandBuilder,
import { collect } from '../../buttons/loader'; } from "@discordjs/builders";
import { getLocale, getLocalizations } from '../../utils/locales'; import {
import { getFilename } from '../../utils/misc'; ActionRowBuilder,
import { checkOwnershipReminder, deleteReminder, embedListReminders, getReminderInfo, newReminder } from '../../utils/reminder'; ButtonBuilder,
ButtonStyle,
ChatInputCommandInteraction,
Client,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
} from "discord.js";
import { v4 as uuidv4 } from "uuid";
import { collect } from "../../buttons/loader";
import { getLocale, getLocalizations } from "../../utils/locales";
import { getFilename } from "../../utils/misc";
import {
checkOwnershipReminder,
deleteReminder,
embedListReminders,
getReminderInfo,
newReminder,
} from "../../utils/reminder";
export default { export default {
scope: () => [], scope: () => [],
data: (client: Client) => { data: (client: Client) => {
const filename = getFilename(__filename); const filename = getFilename(__filename);
const loc_default = client.locales.get(client.config.default_lang); const loc_default = client.locales.get(client.config.default_lang);
if (!loc_default) { if (!loc_default) {
return; return;
} }
return new SlashCommandBuilder() return (
// Command new SlashCommandBuilder()
.setName(filename.toLowerCase()) // Command
.setDescription(loc_default.get(`c_${filename}_desc`) ?? '') .setName(filename.toLowerCase())
.setNameLocalizations( .setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
getLocalizations(client, `c_${filename}_name`, true) .setNameLocalizations(
).setDescriptionLocalizations( getLocalizations(client, `c_${filename}_name`, true)
getLocalizations(client, `c_${filename}_desc`) )
) .setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`)
)
// New reminder // New reminder
.addSubcommand(subcommand => subcommand .addSubcommand((subcommand) =>
.setName( subcommand
loc_default.get(`c_${filename}_sub1_name`) .setName(
?.toLowerCase() ?? '' loc_default.get(`c_${filename}_sub1_name`)?.toLowerCase() ?? ""
).setDescription( )
loc_default.get(`c_${filename}_sub1_desc`) ?? '' .setDescription(loc_default.get(`c_${filename}_sub1_desc`) ?? "")
).setNameLocalizations( .setNameLocalizations(
getLocalizations(client, `c_${filename}_sub1_name`, true) getLocalizations(client, `c_${filename}_sub1_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub1_desc`) .setDescriptionLocalizations(
) getLocalizations(client, `c_${filename}_sub1_desc`)
)
// Specified Time // Specified Time
.addStringOption(option => option .addStringOption((option) =>
.setName( option
loc_default.get(`c_${filename}_sub1_opt1_name`) .setName(
?.toLowerCase() ?? '' loc_default
).setDescription( .get(`c_${filename}_sub1_opt1_name`)
loc_default.get(`c_${filename}_sub1_opt1_desc`) ?? '' ?.toLowerCase() ?? ""
).setNameLocalizations( )
getLocalizations( .setDescription(
client, loc_default.get(`c_${filename}_sub1_opt1_desc`) ?? ""
`c_${filename}_sub1_opt1_name`, )
true .setNameLocalizations(
) getLocalizations(client, `c_${filename}_sub1_opt1_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub1_opt1_desc`) .setDescriptionLocalizations(
) getLocalizations(client, `c_${filename}_sub1_opt1_desc`)
) )
)
// Specified message (not required) // Specified message (not required)
.addStringOption(option => option .addStringOption((option) =>
.setName( option
loc_default.get(`c_${filename}_sub1_opt2_name`) .setName(
?.toLowerCase() ?? '' loc_default
).setDescription( .get(`c_${filename}_sub1_opt2_name`)
loc_default.get(`c_${filename}_sub1_opt2_desc`) ?? '' ?.toLowerCase() ?? ""
).setNameLocalizations( )
getLocalizations( .setDescription(
client, loc_default.get(`c_${filename}_sub1_opt2_desc`) ?? ""
`c_${filename}_sub1_opt2_name`, )
true .setNameLocalizations(
) getLocalizations(client, `c_${filename}_sub1_opt2_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub1_opt2_desc`) .setDescriptionLocalizations(
) getLocalizations(client, `c_${filename}_sub1_opt2_desc`)
) )
) )
)
// List reminders // List reminders
.addSubcommand(subcommand => subcommand .addSubcommand((subcommand) =>
.setName( subcommand
loc_default.get(`c_${filename}_sub2_name`) .setName(
?.toLowerCase() ?? '' loc_default.get(`c_${filename}_sub2_name`)?.toLowerCase() ?? ""
).setDescription( )
loc_default.get(`c_${filename}_sub2_desc`) ?? '' .setDescription(loc_default.get(`c_${filename}_sub2_desc`) ?? "")
).setNameLocalizations( .setNameLocalizations(
getLocalizations(client, `c_${filename}_sub2_name`, true) getLocalizations(client, `c_${filename}_sub2_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub2_desc`) .setDescriptionLocalizations(
) getLocalizations(client, `c_${filename}_sub2_desc`)
)
// User // User
.addUserOption(option => option .addUserOption((option) =>
.setName( option
loc_default.get(`c_${filename}_sub2_opt1_name`) .setName(
?.toLowerCase() ?? '' loc_default
).setDescription( .get(`c_${filename}_sub2_opt1_name`)
loc_default.get(`c_${filename}_sub2_opt1_desc`) ?? '' ?.toLowerCase() ?? ""
).setNameLocalizations( )
getLocalizations( .setDescription(
client, loc_default.get(`c_${filename}_sub2_opt1_desc`) ?? ""
`c_${filename}_sub2_opt1_name`, )
true .setNameLocalizations(
) getLocalizations(client, `c_${filename}_sub2_opt1_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub2_opt1_desc`) .setDescriptionLocalizations(
) getLocalizations(client, `c_${filename}_sub2_opt1_desc`)
) )
)
// Page // Page
.addIntegerOption(option => option .addIntegerOption((option) =>
.setName( option
loc_default.get(`c_${filename}_sub2_opt2_name`) .setName(
?.toLowerCase() ?? '' loc_default
).setDescription( .get(`c_${filename}_sub2_opt2_name`)
loc_default.get(`c_${filename}_sub2_opt2_desc`) ?? '' ?.toLowerCase() ?? ""
).setNameLocalizations( )
getLocalizations( .setDescription(
client, loc_default.get(`c_${filename}_sub2_opt2_desc`) ?? ""
`c_${filename}_sub2_opt2_name`, )
true .setNameLocalizations(
) getLocalizations(client, `c_${filename}_sub2_opt2_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub2_opt2_desc`) .setDescriptionLocalizations(
) getLocalizations(client, `c_${filename}_sub2_opt2_desc`)
) )
) )
)
// Delete a reminder // Delete a reminder
.addSubcommand(subcommand => subcommand .addSubcommand((subcommand) =>
.setName( subcommand
loc_default.get(`c_${filename}_sub3_name`) .setName(
?.toLowerCase() ?? '' loc_default.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? ""
).setDescription( )
loc_default.get(`c_${filename}_sub3_desc`) ?? '' .setDescription(loc_default.get(`c_${filename}_sub3_desc`) ?? "")
).setNameLocalizations( .setNameLocalizations(
getLocalizations(client, `c_${filename}_sub3_name`, true) getLocalizations(client, `c_${filename}_sub3_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub3_desc`) .setDescriptionLocalizations(
) getLocalizations(client, `c_${filename}_sub3_desc`)
)
// ID // ID
.addIntegerOption(option => option .addIntegerOption((option) =>
.setName( option
loc_default.get(`c_${filename}_sub3_opt1_name`) .setName(
?.toLowerCase() ?? '' loc_default
).setDescription( .get(`c_${filename}_sub3_opt1_name`)
loc_default.get(`c_${filename}_sub3_opt1_desc`) ?? '' ?.toLowerCase() ?? ""
).setNameLocalizations( )
getLocalizations( .setDescription(
client, loc_default.get(`c_${filename}_sub3_opt1_desc`) ?? ""
`c_${filename}_sub3_opt1_name`, )
true .setNameLocalizations(
) getLocalizations(client, `c_${filename}_sub3_opt1_name`, true)
).setDescriptionLocalizations( )
getLocalizations(client, `c_${filename}_sub3_opt1_desc`) .setDescriptionLocalizations(
).setRequired(true) getLocalizations(client, `c_${filename}_sub3_opt1_desc`)
), )
); .setRequired(true)
}, )
)
);
},
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => { interaction: async (
const loc_default = client.locales.get(client.config.default_lang); interaction: ChatInputCommandInteraction,
const filename = getFilename(__filename); client: Client
const loc = getLocale(client, interaction.locale); ) => {
const loc_default = client.locales.get(client.config.default_lang);
const filename = getFilename(__filename);
const loc = getLocale(client, interaction.locale);
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
switch (subcommand) { switch (subcommand) {
// New reminder // New reminder
case loc_default?.get(`c_${filename}_sub1_name`) case loc_default?.get(`c_${filename}_sub1_name`)?.toLowerCase() ?? "": {
?.toLowerCase() ?? '': { // If time is already renseigned
const time = interaction.options.getString(
loc_default?.get(`c_${filename}_sub1_opt1_name`) as string
);
if (time != null) {
// Use the cli because we already have enough data
return newReminder(client, time, {
locale: interaction.locale,
message: interaction.options.getString(
loc_default?.get(`c_${filename}_sub1_opt2_name`) as string
),
createdAt: interaction.createdAt.getTime(),
channelId: interaction.channelId,
userId: interaction.user.id,
guildId: interaction.guildId,
}).then((msg) =>
interaction.reply({
content: msg as string,
ephemeral: true,
})
);
} else {
// Show modal to user to get at least the time
const modal = new ModalBuilder()
.setCustomId("reminderGUI")
.setTitle(loc.get(`c_${filename}_name`).capitalize());
// If time is already renseigned const timeGUI = new TextInputBuilder()
const time = interaction.options.getString(loc_default?.get(`c_${filename}_sub1_opt1_name`) as string); .setCustomId("reminderGUI-time")
if (time != null) { .setLabel(loc.get(`c_${filename}_sub1_opt1_name`).capitalize())
// Use the cli because we already have enough data .setStyle(TextInputStyle.Short)
return newReminder(client, time, { .setPlaceholder("1h")
locale: interaction.locale, .setRequired(true);
message: interaction.options.getString(loc_default?.get(`c_${filename}_sub1_opt2_name`) as string),
createdAt: interaction.createdAt.getTime(),
channelId: interaction.channelId,
userId: interaction.user.id,
guildId: interaction.guildId,
}).then((msg) => interaction.reply({
content: msg as string,
ephemeral: true,
}));
} else {
// Show modal to user to get at least the time
const modal = new ModalBuilder()
.setCustomId('reminderGUI')
.setTitle(loc.get(`c_${filename}_name`).capitalize());
const timeGUI = new TextInputBuilder() const messageGUI = new TextInputBuilder()
.setCustomId('reminderGUI-time') .setCustomId("reminderGUI-message")
.setLabel(loc.get(`c_${filename}_sub1_opt1_name`).capitalize()) .setLabel(loc.get(`c_${filename}_sub1_opt2_name`).capitalize())
.setStyle(TextInputStyle.Short) .setStyle(TextInputStyle.Paragraph)
.setPlaceholder('1h') .setPlaceholder(loc.get(`c_${filename}_sub1_opt2_desc`))
.setRequired(true); .setRequired(false);
const messageGUI = new TextInputBuilder() modal.addComponents(
.setCustomId('reminderGUI-message') new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
.setLabel(loc.get(`c_${filename}_sub1_opt2_name`).capitalize()) timeGUI
.setStyle(TextInputStyle.Paragraph) ),
.setPlaceholder(loc.get(`c_${filename}_sub1_opt2_desc`)) new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
.setRequired(false); messageGUI
)
);
modal.addComponents( return interaction.showModal(modal);
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(timeGUI), }
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(messageGUI) }
); // List reminders
case loc_default?.get(`c_${filename}_sub2_name`)?.toLowerCase() ?? "": {
// Which user to show
let user = interaction.options.getUser(
loc_default?.get(`c_${filename}_sub2_opt1_name`) as string
);
if (user == null) {
user = interaction.user;
}
return interaction.showModal(modal); const page =
} interaction.options.getInteger(
} loc_default?.get(`c_${filename}_sub2_opt2_name`) as string
// List reminders ) ?? 1;
case loc_default?.get(`c_${filename}_sub2_name`) const list = await embedListReminders(
?.toLowerCase() ?? '': { client,
// Which user to show user,
let user = interaction.options.getUser(loc_default?.get(`c_${filename}_sub2_opt1_name`) as string); interaction.guildId,
if (user == null) { page,
user = interaction.user; interaction.locale
} );
const page = interaction.options.getInteger(loc_default?.get(`c_${filename}_sub2_opt2_name`) as string) ?? 1; const idPrec = "reminderList-prec_" + uuidv4();
const list = await embedListReminders(client, user, interaction.guildId, page, interaction.locale); const idNext = "reminderList-next_" + uuidv4();
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(idPrec)
.setLabel(loc.get(`c_${filename}12`))
.setStyle(ButtonStyle.Primary)
)
.addComponents(
new ButtonBuilder()
.setCustomId(idNext)
.setLabel(loc.get(`c_${filename}13`))
.setStyle(ButtonStyle.Primary)
);
const idPrec = 'reminderList-prec_' + uuidv4(); // Buttons interactions
const idNext = 'reminderList-next_' + uuidv4(); collect(client, interaction, idPrec);
const row = new ActionRowBuilder<ButtonBuilder>() collect(client, interaction, idNext);
.addComponents(
new ButtonBuilder()
.setCustomId(idPrec)
.setLabel(loc.get(`c_${filename}12`))
.setStyle(ButtonStyle.Primary))
.addComponents(
new ButtonBuilder()
.setCustomId(idNext)
.setLabel(loc.get(`c_${filename}13`))
.setStyle(ButtonStyle.Primary),
);
// Buttons interactions return await interaction.reply({
collect(client, interaction, idPrec); ephemeral: true,
collect(client, interaction, idNext); embeds: [list],
components: [row],
});
}
// Delete a reminder
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "": {
const id = interaction.options.getInteger(
loc_default?.get(`c_${filename}_sub3_opt1_name`) as string
);
if (id === null) {
return interaction.reply({
content: loc.get(`c_${filename}2`),
ephemeral: true,
});
}
return await interaction.reply({ ephemeral: true, embeds: [list], components: [row] }); // Check if the ID exists and belongs to the user
} if (
// Delete a reminder await checkOwnershipReminder(
case loc_default?.get(`c_${filename}_sub3_name`) client,
?.toLowerCase() ?? '': { id,
const id = interaction.options.getInteger(loc_default?.get(`c_${filename}_sub3_opt1_name`) as string); interaction.user.id,
if (id === null) { interaction.guildId ?? "0"
return interaction.reply({ content: loc.get(`c_${filename}2`), ephemeral: true }); )
} ) {
return interaction.reply({
content: loc.get(`c_${filename}3`),
ephemeral: true,
});
}
// Check if the ID exists and belongs to the user // Stop timeout
if (await checkOwnershipReminder(client, id, interaction.user.id, interaction.guildId ?? '0')) { const reminderInfo = await getReminderInfo(client, id);
return interaction.reply({ content: loc.get(`c_${filename}3`), ephemeral: true }); clearTimeout(reminderInfo.timeout_id);
}
// Stop timeout // Delete from database
const reminderInfo = await getReminderInfo(client, id); return deleteReminder(
clearTimeout(reminderInfo.timeout_id); client,
reminderInfo.creation_date,
// Delete from database reminderInfo.user_id
return deleteReminder(client, reminderInfo.creation_date, reminderInfo.user_id) ).then(() =>
.then(() => interaction.reply({ content: `Reminder **#${id}** supprimé !`, ephemeral: true })); interaction.reply({
content: `Reminder **#${id}** supprimé !`,
} ephemeral: true,
default: { })
console.error(`${__filename}: unknown subcommand (${subcommand})`); );
break; }
} default: {
} console.error(`${__filename}: unknown subcommand (${subcommand})`);
}, break;
}
}
},
}; };

View file

@ -1,59 +1,78 @@
import { Client } from 'discord.js'; import { Client } from "discord.js";
import { logStart } from '../../utils/misc'; import { logStart } from "../../utils/misc";
import { dbReminder, deleteReminder, infoReminder, OptionReminder, sendReminder, setTimeoutReminder, updateReminder } from '../../utils/reminder'; import {
dbReminder,
deleteReminder,
infoReminder,
OptionReminder,
sendReminder,
setTimeoutReminder,
updateReminder,
} from "../../utils/reminder";
export const once = true; export const once = true;
/** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-ready */ /** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-ready */
export default async (client: Client) => { export default async (client: Client) => {
console.log(logStart('Connection', true)); console.log(logStart("Connection", true));
// 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('SELECT * FROM reminder', [], (err, row) => { client.db.all("SELECT * FROM reminder", [], (err, row) => {
if (err) { if (err) {
ko(err); ko(err);
} }
// Send all the current reminders // Send all the current reminders
ok(row); ok(row);
}); });
}).then((data) => { })
const now = Date.now(); .then((data) => {
(data as dbReminder[]).forEach((element) => { const now = Date.now();
const info = { (data as dbReminder[]).forEach((element) => {
locale: element.locale, const info = {
message: element.data, locale: element.locale,
createdAt: Number(element.creation_date), message: element.data,
channelId: `${element.channel_id}`, createdAt: Number(element.creation_date),
userId: `${element.user_id}`, channelId: `${element.channel_id}`,
guildId: `${element.guild_id}`, userId: `${element.user_id}`,
} as infoReminder; guildId: `${element.guild_id}`,
} as infoReminder;
if (element.expiration_date <= now) { if (element.expiration_date <= now) {
// Reminder expired // Reminder expired
deleteReminder(client, element.creation_date, `${element.user_id}`).then((res) => { deleteReminder(
if (res != true) { client,
throw res; element.creation_date,
} `${element.user_id}`
}); ).then((res) => {
if (res != true) {
throw res;
}
});
sendReminder(client, info, element.option_id as OptionReminder); sendReminder(client, info, element.option_id as OptionReminder);
} else { } else {
// Restart timeout // Restart timeout
const timeoutId = setTimeoutReminder(client, info, element.option_id, (element.expiration_date - now) / 1000); const timeoutId = setTimeoutReminder(
client,
info,
element.option_id,
(element.expiration_date - now) / 1000
);
// Update timeout in database // Update timeout in database
element.timeout_id = String(timeoutId); element.timeout_id = String(timeoutId);
updateReminder(client, element).then((res) => { updateReminder(client, element).then((res) => {
if (res != true) { if (res != true) {
throw res; throw res;
} }
}); });
} }
}); });
}).catch(err => { })
throw err; .catch((err) => {
}); throw err;
});
}; };

View file

@ -1,35 +1,35 @@
import { Client, Interaction, InteractionType } from 'discord.js'; import { Client, Interaction, InteractionType } from "discord.js";
import { getLocale } from '../../utils/locales'; import { getLocale } from "../../utils/locales";
/** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-interactionCreate */ /** https://discord.js.org/#/docs/discord.js/main/class/Client?scrollTo=e-interactionCreate */
export default (interaction: Interaction, client: Client) => { export default (interaction: Interaction, client: Client) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
switch (interaction.type) { switch (interaction.type) {
case InteractionType.ApplicationCommand: { case InteractionType.ApplicationCommand: {
const command = client.commands.list.get(interaction.commandName); const command = client.commands.list.get(interaction.commandName);
if (!command) { if (!command) {
return interaction.reply({ return interaction.reply({
content: loc.get('e_interacreate_no_command'), content: loc.get("e_interacreate_no_command"),
ephemeral: true, ephemeral: true,
}); });
} }
return command.interaction(interaction, client); return command.interaction(interaction, client);
} }
case InteractionType.ModalSubmit: { case InteractionType.ModalSubmit: {
const modal = client.modals.list.get(interaction.customId); const modal = client.modals.list.get(interaction.customId);
if (!modal) { if (!modal) {
return interaction.reply({ return interaction.reply({
content: loc.get('e_interacreate_no_modal'), content: loc.get("e_interacreate_no_modal"),
ephemeral: true, ephemeral: true,
}); });
} }
return modal.interaction(interaction, client); return modal.interaction(interaction, client);
} }
default: default:
break; break;
} }
}; };

View file

@ -1,40 +1,41 @@
import { Client } from 'discord.js'; import { Client } from "discord.js";
import { readdir } from 'fs/promises'; import { readdir } from "fs/promises";
/** Load all the events. */ /** Load all the events. */
export default async (client: Client) => { export default async (client: Client) => {
const events_categories = (await readdir(__dirname)) const events_categories = (await readdir(__dirname)).filter(
.filter(element => !element.endsWith('.js') && !element.endsWith('.ts')); (element) => !element.endsWith(".js") && !element.endsWith(".ts")
);
events_categories.forEach(async event_category => { events_categories.forEach(async (event_category) => {
// Retrieve events // Retrieve events
const events = await readdir(`${__dirname}/${event_category}`); const events = await readdir(`${__dirname}/${event_category}`);
// Load them into the client // Load them into the client
Promise.all( Promise.all(
events.map(async event_file => { events.map(async (event_file) => {
const { once, default: execute } = await import( const { once, default: execute } = await import(
`../events/${event_category}/${event_file}` `../events/${event_category}/${event_file}`
); );
// Remove extension // Remove extension
// TODO: use utils functions // TODO: use utils functions
const event_type_ext = event_file.split('.'); const event_type_ext = event_file.split(".");
const ext = event_type_ext.pop(); 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('.'); const event_type = event_type_ext.join(".");
if (once) { if (once) {
return client.once(event_type, (...args) => { return client.once(event_type, (...args) => {
execute(...args, client); execute(...args, client);
}); });
} }
return client.on(event_type, (...args) => { return client.on(event_type, (...args) => {
execute(...args, client); execute(...args, client);
}); });
}), })
); );
}); });
}; };

View file

@ -1,183 +1,191 @@
import { Client, GuildMember, Message, TextBasedChannel, EmbedBuilder } from 'discord.js'; import {
import { getLocale } from '../../utils/locales'; Client,
import { isImage, userWithNickname } from '../../utils/misc'; GuildMember,
import { showDate } from '../../utils/time'; Message,
TextBasedChannel,
EmbedBuilder,
} from "discord.js";
import { getLocale } from "../../utils/locales";
import { isImage, userWithNickname } from "../../utils/misc";
import { showDate } from "../../utils/time";
/** 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) => {
// Ignore message if // Ignore message if
if ( if (
// Author is a bot // Author is a bot
message.author.bot || message.author.bot ||
// Author is Discord // Author is Discord
message.author.system || message.author.system ||
// Message isn't a message // Message isn't a message
message.system || message.system ||
// Message is in PM (future-proof if we add Intents.FLAGS.DIRECT_MESSAGES) // Message is in PM (future-proof if we add Intents.FLAGS.DIRECT_MESSAGES)
!message.guild || !message.guild ||
// Guild is offline // Guild is offline
!message.guild.available !message.guild.available
) { ) {
return; return;
} }
/* Citation */ /* Citation */
const regex = 'https://(?:canary\\.|ptb\\.)?discord(?:app)?\\.com/channels/(\\d{17,19})/(\\d{17,19})/(\\d{17,19})'; const regex =
const urls = message.content.match(new RegExp(regex, 'g')); "https://(?:canary\\.|ptb\\.)?discord(?:app)?\\.com/channels/(\\d{17,19})/(\\d{17,19})/(\\d{17,19})";
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) {
return; return;
} }
const messages = ( const messages = (
await Promise.all( await Promise.all(
urls.reduce( urls
(data: { .reduce(
message_id: string; (
channel: TextBasedChannel; data: {
}[] = [], match) => { message_id: string;
const [, channel: TextBasedChannel;
guild_id, }[] = [],
channel_id, match
message_id, ) => {
] = new RegExp(regex).exec(match) as RegExpExecArray; const [, guild_id, channel_id, message_id] = new RegExp(regex).exec(
match
) as RegExpExecArray;
// If message posted in another guild // If message posted in another guild
if (guild_id !== message.guild?.id) { if (guild_id !== message.guild?.id) {
return data; return data;
} }
const channel = const channel = message.guild.channels.cache.get(
message.guild.channels.cache.get(channel_id) as TextBasedChannel; 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) { if (!channel) {
return data; return data;
} }
data.push({ message_id, channel }); data.push({ message_id, channel });
return data; return data;
}, [] },
).map(async ({ message_id, channel }) => { []
const quoted_message = await channel.messages )
.fetch(message_id) .map(async ({ message_id, channel }) => {
.catch(() => undefined); const quoted_message = await channel.messages
.fetch(message_id)
.catch(() => undefined);
// If message doesn't exist or empty // If message doesn't exist or empty
if (!quoted_message || ( if (
!quoted_message.content && !quoted_message ||
quoted_message.attachments.size == 0) (!quoted_message.content && quoted_message.attachments.size == 0)
) { ) {
return; return;
} }
return quoted_message; return quoted_message;
}) })
) )
// Remove undefined elements )
).filter(Boolean); // Remove undefined elements
.filter(Boolean);
const loc = getLocale(client, client.config.default_lang); 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)].map(quoted_post => { [...new Set(messages)].map((quoted_post) => {
const embed = new EmbedBuilder() const embed = new EmbedBuilder().setColor("#2f3136").setAuthor({
.setColor('#2f3136') name: "Citation",
.setAuthor({ iconURL: quoted_post?.author.displayAvatarURL(),
name: 'Citation', });
iconURL: quoted_post?.author.displayAvatarURL(),
});
// Handle attachments // Handle attachments
if (quoted_post?.attachments.size !== 0) { if (quoted_post?.attachments.size !== 0) {
if (quoted_post?.attachments.size === 1 && isImage( if (
quoted_post.attachments.first()?.name as string 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); // Only contains one image
} else { embed.setImage(quoted_post.attachments.first()?.url as string);
// Contains more than one image and/or other files } else {
let files = ''; // Contains more than one image and/or other files
quoted_post?.attachments.forEach(file => files += let files = "";
`[${file.name}](${file.url}), ` quoted_post?.attachments.forEach(
); (file) => (files += `[${file.name}](${file.url}), `)
embed.addFields({ );
// TODO: Don't pluralize when there is only one file. embed.addFields({
// TODO: Locales // TODO: Don't pluralize when there is only one file.
name: 'Fichiers joints', // TODO: Locales
// TODO: Check if don't exceed char limit, if yes, split name: "Fichiers joints",
// files into multiples field. // TODO: Check if don't exceed char limit, if yes, split
value: `${files.slice(0, -2)}.`, // files into multiples field.
}); value: `${files.slice(0, -2)}.`,
} });
} }
}
// Description as post content // Description as post content
embed.setDescription(quoted_post?.content ?? ''); embed.setDescription(quoted_post?.content ?? "");
// Footer // Footer
let footer = `Posté le ${showDate( let footer = `Posté le ${showDate(
client.config.default_lang, client.config.default_lang,
loc, loc,
quoted_post?.createdAt as Date quoted_post?.createdAt as Date
)}`; )}`;
if (quoted_post?.editedAt) { if (quoted_post?.editedAt) {
footer += ` et modifié le ${showDate( footer += ` et modifié le ${showDate(
client.config.default_lang, client.config.default_lang,
loc, loc,
quoted_post.editedAt 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( footer += `\nCité par ${
message.member as GuildMember userWithNickname(message.member as GuildMember) ?? "?"
) ?? '?'} le ${showDate( } le ${showDate(client.config.default_lang, loc, message.createdAt)}`;
client.config.default_lang, }
loc,
message.createdAt
)}`;
}
embed.setFooter({ embed.setFooter({
text: footer, text: footer,
iconURL: message.author.avatarURL() ?? undefined, iconURL: message.author.avatarURL() ?? undefined,
}); });
// Location/author of the quoted post // Location/author of the quoted post
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(new RegExp(regex, 'g'), '').trim() && !message.content.replace(new RegExp(regex, "g"), "").trim() &&
messages.length === urls.length && messages.length === urls.length &&
!message.mentions.repliedUser !message.mentions.repliedUser
) { ) {
message.delete(); message.delete();
return message.channel.send({ embeds: [embed] }); return message.channel.send({ embeds: [embed] });
} else { } else {
return message.reply({ return message.reply({
embeds: [embed], embeds: [embed],
allowedMentions: { allowedMentions: {
repliedUser: false, repliedUser: false,
}, },
}); });
} }
}); });
}; };

View file

@ -1,78 +1,80 @@
import loadClient, { quit } from './utils/client'; import loadClient, { quit } from "./utils/client";
import loadEvents from './events/loader'; import loadEvents from "./events/loader";
import loadModals from './modals/loader'; import loadModals from "./modals/loader";
import loadButtons from './buttons/loader'; import loadButtons from "./buttons/loader";
import loadCommands from './commands/loader'; import loadCommands from "./commands/loader";
import { logStart } from './utils/misc'; import { logStart } from "./utils/misc";
/** Run the bot. */ /** Run the bot. */
const run = async () => { const run = async () => {
console.log('Starting Botanique...'); console.log("Starting Botanique...");
// Load .env if not in prod // Load .env if not in prod
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
(await import('dotenv')).config({ path: './config/.env' }); (await import("dotenv")).config({ path: "./config/.env" });
} }
// Client Discord.JS // Client Discord.JS
const client_name = 'Client'; const client_name = "Client";
await loadClient() await loadClient()
.then(async client => { .then(async (client) => {
// Events Discord.JS // Events Discord.JS
const events_name = 'Events'; const events_name = "Events";
await loadEvents(client) await loadEvents(client)
.then(() => console.log(logStart(events_name, true))) .then(() => console.log(logStart(events_name, true)))
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
throw logStart(events_name, false); throw logStart(events_name, false);
}); });
// Connect the bot to Discord.com // Connect the bot to Discord.com
await client.login(client.config.token_discord); await client.login(client.config.token_discord);
// Modals Discord.JS // Modals Discord.JS
const modals_name = 'Modals'; const modals_name = "Modals";
await loadModals(client) await loadModals(client)
.then(() => console.log(logStart(modals_name, true))) .then(() => console.log(logStart(modals_name, true)))
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
throw logStart(modals_name, false); throw logStart(modals_name, false);
}); });
// Buttons Discord.JS // Buttons Discord.JS
const buttons_name = 'Buttons'; const buttons_name = "Buttons";
await loadButtons(client) await loadButtons(client)
.then(() => console.log(logStart(buttons_name, true))) .then(() => console.log(logStart(buttons_name, true)))
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
throw logStart(buttons_name, false); throw logStart(buttons_name, false);
}); });
// Commands Slash Discord.JS // Commands Slash Discord.JS
const commands_name = 'Commands'; const commands_name = "Commands";
await loadCommands(client) await loadCommands(client)
.then(() => console.log(logStart(commands_name, true))) .then(() => console.log(logStart(commands_name, true)))
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
throw logStart(commands_name, false); throw logStart(commands_name, false);
}); });
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!`
);
// ^C // ^C
process.on('SIGINT', () => quit(client)); process.on("SIGINT", () => quit(client));
// Container force closed // Container force closed
process.on('SIGTERM', () => quit(client)); process.on("SIGTERM", () => quit(client));
}) })
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
throw logStart(client_name, false); throw logStart(client_name, false);
}); });
}; };
run().catch(error => console.error(error)); run().catch((error) => console.error(error));

View file

@ -1,18 +1,18 @@
{ {
"e_interacreate_no_command": "Sorry, the command probably no longer exists...", "e_interacreate_no_command": "Sorry, the command probably no longer exists...",
"c_ping_name": "Ping", "c_ping_name": "Ping",
"c_ping_desc": "Pong!", "c_ping_desc": "Pong!",
"c_ping1": "Roundtrip latency", "c_ping1": "Roundtrip latency",
"c_ping2": "Websocket heartbeat", "c_ping2": "Websocket heartbeat",
"c_help_name": "Help", "c_help_name": "Help",
"c_help_desc": "Informations about commands", "c_help_desc": "Informations about commands",
"c_help_opt1_name": "command", "c_help_opt1_name": "command",
"c_help_opt1_desc": "Command wanted in depth.", "c_help_opt1_desc": "Command wanted in depth.",
"c_help1": "List of categories and associated commands", "c_help1": "List of categories and associated commands",
"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 :",
"u_time_at": "at" "u_time_at": "at"
} }

View file

@ -1,80 +1,80 @@
{ {
"e_interacreate_no_command": "Désolé, la commande n'existe plus...", "e_interacreate_no_command": "Désolé, la commande n'existe plus...",
"e_interacreate_no_modal": "Désolé, le modèle n'existe plus...", "e_interacreate_no_modal": "Désolé, le modèle n'existe plus...",
"e_interacreate_no_button": "Désolé, le bouton n'existe plus...", "e_interacreate_no_button": "Désolé, le bouton n'existe plus...",
"c_ping_name": "Ping", "c_ping_name": "Ping",
"c_ping_desc": "Pong!", "c_ping_desc": "Pong!",
"c_ping1": "Latence totale", "c_ping1": "Latence totale",
"c_ping2": "Latence du Websocket", "c_ping2": "Latence du Websocket",
"c_help_name": "Aide", "c_help_name": "Aide",
"c_help_desc": "Informations sur les commandes", "c_help_desc": "Informations sur les commandes",
"c_help_opt1_name": "commande", "c_help_opt1_name": "commande",
"c_help_opt1_desc": "Commande voulu en détail.", "c_help_opt1_desc": "Commande voulu en détail.",
"c_help1": "Liste des catégories et des commandes associées", "c_help1": "Liste des catégories et des commandes associées",
"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_name": "Nettoyer",
"c_archive_desc": "Nettoyage pour le passage à niveau", "c_archive_desc": "Nettoyage pour le passage à niveau",
"c_archive_opt1_name": "catégorie", "c_archive_opt1_name": "catégorie",
"c_archive_opt1_desc": "Nom de la catégorie à nettoyer", "c_archive_opt1_desc": "Nom de la catégorie à nettoyer",
"c_archive1": "Liste des catégories soumis au nettoyage", "c_archive1": "Liste des catégories soumis au nettoyage",
"c_archive2": "`L1`, `L2`, `L3`, `M1`, `M2`", "c_archive2": "`L1`, `L2`, `L3`, `M1`, `M2`",
"c_archive3": "Impossible de trouver/nettoyer le salon :", "c_archive3": "Impossible de trouver/nettoyer le salon :",
"c_archive4": "Liste des salons archivés de la catégorie", "c_archive4": "Liste des salons archivés de la catégorie",
"c_archive5": "vers", "c_archive5": "vers",
"c_archive6": "Nettoyage", "c_archive6": "Nettoyage",
"c_archive7": "Catégorie déjà nettoyée", "c_archive7": "Catégorie déjà nettoyée",
"c_prep_name": "Préparation", "c_prep_name": "Préparation",
"c_prep_desc": "Préparation des salons généraux pour la nouvelle année", "c_prep_desc": "Préparation des salons généraux pour la nouvelle année",
"c_prep_opt1_name": "année", "c_prep_opt1_name": "année",
"c_prep_opt1_desc": "Nom de l'année à préparer'", "c_prep_opt1_desc": "Nom de l'année à préparer'",
"c_prep1": "Liste des catégories soumis à la préparation", "c_prep1": "Liste des catégories soumis à la préparation",
"c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`", "c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`",
"c_prep3": "Impossible de trouver/nettoyer le salon :", "c_prep3": "Impossible de trouver/nettoyer le salon :",
"c_prep4": "Listes des Salons préparés `", "c_prep4": "Listes des Salons préparés `",
"c_prep5": "créé", "c_prep5": "créé",
"c_prep6": "Pas besoin de préparation", "c_prep6": "Pas besoin de préparation",
"u_time_at": "à", "u_time_at": "à",
"c_reminder_name": "rappel", "c_reminder_name": "rappel",
"c_reminder_desc": "Commande relative aux rappels", "c_reminder_desc": "Commande relative aux rappels",
"c_reminder_sub1_name": "nouveau", "c_reminder_sub1_name": "nouveau",
"c_reminder_sub1_desc": "Met en place un rappel", "c_reminder_sub1_desc": "Met en place un rappel",
"c_reminder_sub1_opt1_name": "temps", "c_reminder_sub1_opt1_name": "temps",
"c_reminder_sub1_opt1_desc": "Temps désiré avant le rappel, accolez un @ pour activer la mention ou un p pour envoyer en DM", "c_reminder_sub1_opt1_desc": "Temps désiré avant le rappel, accolez un @ pour activer la mention ou un p pour envoyer en DM",
"c_reminder_sub1_opt2_name": "message", "c_reminder_sub1_opt2_name": "message",
"c_reminder_sub1_opt2_desc": "Message du rappel", "c_reminder_sub1_opt2_desc": "Message du rappel",
"c_reminder_sub2_name": "liste", "c_reminder_sub2_name": "liste",
"c_reminder_sub2_desc": "Affiche la liste des rappels d'un utilisateur", "c_reminder_sub2_desc": "Affiche la liste des rappels d'un utilisateur",
"c_reminder_sub2_opt1_name": "utilisateur", "c_reminder_sub2_opt1_name": "utilisateur",
"c_reminder_sub2_opt1_desc": "Affiche la liste de l'utilisateur en question", "c_reminder_sub2_opt1_desc": "Affiche la liste de l'utilisateur en question",
"c_reminder_sub2_opt2_name": "page", "c_reminder_sub2_opt2_name": "page",
"c_reminder_sub2_opt2_desc": "Page à afficher", "c_reminder_sub2_opt2_desc": "Page à afficher",
"c_reminder_sub3_name": "efface", "c_reminder_sub3_name": "efface",
"c_reminder_sub3_desc": "Supprime un rappel", "c_reminder_sub3_desc": "Supprime un rappel",
"c_reminder_sub3_opt1_name": "id", "c_reminder_sub3_opt1_name": "id",
"c_reminder_sub3_opt1_desc": "Rappel à supprimé", "c_reminder_sub3_opt1_desc": "Rappel à supprimé",
"c_reminder1": "Un rappel a été configuré pour dans", "c_reminder1": "Un rappel a été configuré pour dans",
"c_reminder2": "L'ID renseigné n'est pas valide.", "c_reminder2": "L'ID renseigné n'est pas valide.",
"c_reminder3": "Rappel non trouvé, pas sur le bon serveur ou qui ne vous appartiens pas.", "c_reminder3": "Rappel non trouvé, pas sur le bon serveur ou qui ne vous appartiens pas.",
"c_reminder4": "Utilisateur inconnu.", "c_reminder4": "Utilisateur inconnu.",
"c_reminder5": "Rappels de", "c_reminder5": "Rappels de",
"c_reminder6": "Page", "c_reminder6": "Page",
"c_reminder7": "Pas de message", "c_reminder7": "Pas de message",
"c_reminder8": "Expire dans", "c_reminder8": "Expire dans",
"c_reminder9": "Fais le", "c_reminder9": "Fais le",
"c_reminder10": "L'utilisateur n'a aucun rappel en attente ou page n°", "c_reminder10": "L'utilisateur n'a aucun rappel en attente ou page n°",
"c_reminder11": "vide", "c_reminder11": "vide",
"c_reminder12": "Précédent", "c_reminder12": "Précédent",
"c_reminder13": "Suivant", "c_reminder13": "Suivant",
"c_reminder14": "Message envoyé en DM car le salon n'est plus disponible.", "c_reminder14": "Message envoyé en DM car le salon n'est plus disponible.",
"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": "Pas de message" "c_reminder18": "Pas de message"
} }

View file

@ -1,36 +1,37 @@
import { readdir } from 'fs/promises'; import { readdir } from "fs/promises";
import { removeExtension } from '../utils/misc'; import { removeExtension } from "../utils/misc";
import { Client } from 'discord.js'; import { Client } from "discord.js";
export default async (client: Client) => { export default async (client: Client) => {
// Dossier des modals // Dossier des modals
const modals_categories = (await readdir(__dirname)) const modals_categories = (await readdir(__dirname)).filter(
.filter(element => !element.endsWith('.js') && !element.endsWith('.ts')); (element) => !element.endsWith(".js") && !element.endsWith(".ts")
);
await Promise.all( await Promise.all(
// For each categorie // For each categorie
modals_categories.map(async modals_category => { modals_categories.map(async (modals_category) => {
// Retrieve all the commands // Retrieve all the commands
const modal_files = await readdir(`${__dirname}/${modals_category}`); const modal_files = await readdir(`${__dirname}/${modals_category}`);
// Add the category to the collection for the help command // Add the category to the collection for the help command
client.modals.categories.set( client.modals.categories.set(
modals_category, modals_category,
modal_files.map(removeExtension), modal_files.map(removeExtension)
); );
// Add the modal // Add the modal
return Promise.all( return Promise.all(
modal_files.map(async modal_file => { modal_files.map(async (modal_file) => {
const modal = ( const modal = (
await import(`../modals/${modals_category}/${modal_file}`) await import(`../modals/${modals_category}/${modal_file}`)
).default; ).default;
// Add it to the collection so the interaction will work // Add it to the collection so the interaction will work
client.modals.list.set(modal.data.name, modal); client.modals.list.set(modal.data.name, modal);
return modal.data; return modal.data;
}), })
); );
}), })
); );
}; };

View file

@ -1,21 +1,28 @@
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/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) =>
newReminder(client, interaction.fields.fields.get('reminderGUI-time')?.value as string, { newReminder(
locale: interaction.locale, client,
message: interaction.fields.fields.get('reminderGUI-message')?.value ?? null, interaction.fields.fields.get("reminderGUI-time")?.value as string,
createdAt: interaction.createdAt.getTime(), {
channelId: interaction.channelId, locale: interaction.locale,
userId: interaction.user.id, message:
guildId: interaction.guildId, interaction.fields.fields.get("reminderGUI-message")?.value ?? null,
}).then((msg) => interaction.reply({ createdAt: interaction.createdAt.getTime(),
content: msg as string, channelId: interaction.channelId,
ephemeral: true, userId: interaction.user.id,
})), guildId: interaction.guildId,
}
).then((msg) =>
interaction.reply({
content: msg as string,
ephemeral: true,
})
),
}; };

View file

@ -1,87 +1,96 @@
import { Collection } from 'discord.js'; import { Collection } from "discord.js";
import { SlashCommandBuilder } from '@discordjs/builders'; import { SlashCommandBuilder } from "@discordjs/builders";
import { Database } from 'sqlite3'; import { Database } from "sqlite3";
export { }; export {};
declare module 'discord.js' { declare module "discord.js" {
// eslint-disable-next-line no-shadow // eslint-disable-next-line no-shadow
export interface Client { export interface Client {
/** Store the configuration */ /** Store the configuration */
config: { config: {
/** Bot version */ /** Bot version */
version: string, version: string;
/** Bot token from env variable */ /** Bot token from env variable */
token_discord: string | undefined, token_discord: string | undefined;
/** Default lang used */ /** Default lang used */
default_lang: string default_lang: string;
}, };
/** Store all the modals */ /** Store all the modals */
modals: { modals: {
categories: Collection< categories: Collection<
/** Category name */ /** Category name */
string, string,
/** Name of the modals in the category */ /** Name of the modals in the category */
string[] string[]
>, >;
list: Collection< list: Collection<
/** Modal name */ /** Modal name */
string, string,
/** Modal itself */ /** Modal itself */
{ {
/** Data about the modal */ /** Data about the modal */
data: { data: {
name: string name: string;
}, };
/** How the modal interact */ /** How the modal interact */
interaction: (interaction: ModalSubmitInteraction, client: Client) => unknown interaction: (
} interaction: ModalSubmitInteraction,
>, client: Client
}, ) => unknown;
/** Store all the buttons */ }
buttons: { >;
categories: Collection< };
/** Category name */ /** Store all the buttons */
string, buttons: {
/** Name of the buttons in the category */ categories: Collection<
string[] /** Category name */
>, string,
list: Collection< /** Name of the buttons in the category */
/** Button name */ string[]
string, >;
/** Button itself */ list: Collection<
{ /** Button name */
/** Data about the button */ string,
data: { /** Button itself */
name: string {
}, /** Data about the button */
/** How the button interact */ data: {
interaction: (interaction: MessageComponentInteraction, client: Client) => Promise<InteractionUpdateOptions> name: string;
} };
>, /** How the button interact */
}, interaction: (
/** Store all the slash commands */ interaction: MessageComponentInteraction,
commands: { client: Client
categories: Collection< ) => Promise<InteractionUpdateOptions>;
/** Category name */ }
string, >;
/** Name of the commands in the category */ };
string[] /** Store all the slash commands */
>, commands: {
list: Collection< categories: Collection<
/** Command name */ /** Category name */
string, string,
/** Command itself */ /** Name of the commands in the category */
{ string[]
/** Data about the command */ >;
data: SlashCommandBuilder, list: Collection<
/** How the command interact */ /** Command name */
interaction: (interaction: CommandInteraction, client: Client) => unknown string,
} /** Command itself */
>, {
}, /** Data about the command */
/** Store all the localizations */ data: SlashCommandBuilder;
locales: Map<string, Map<string, string>>, /** How the command interact */
db: Database, interaction: (
} interaction: CommandInteraction,
client: Client
) => unknown;
}
>;
};
/** Store all the localizations */
locales: Map<string, Map<string, string>>;
db: Database;
}
} }

View file

@ -1,16 +1,16 @@
export {}; export {};
declare global { declare global {
// Declarations // Declarations
interface String { interface String {
/** /**
* Returns a copy of the string with the first letter capitalized. * Returns a copy of the string with the first letter capitalized.
*/ */
capitalize(): string, capitalize(): string;
} }
} }
/** Capitalize definition */ /** Capitalize definition */
String.prototype.capitalize = function(this: string) { String.prototype.capitalize = function (this: string) {
return this[0].toUpperCase() + this.substring(1); return this[0].toUpperCase() + this.substring(1);
}; };

View file

@ -1,47 +1,46 @@
import { Client, Collection, GatewayIntentBits } from 'discord.js'; import { Client, Collection, GatewayIntentBits } from "discord.js";
import { readFileSync } from 'fs'; import { readFileSync } from "fs";
import { loadLocales } from './locales'; import { loadLocales } from "./locales";
import '../modules/client'; import "../modules/client";
import { Database } from 'sqlite3'; import { Database } from "sqlite3";
/** 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 client: Client = new Client({ const client: Client = new Client({
intents: [ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
GatewayIntentBits.Guilds, });
GatewayIntentBits.GuildMessages,
],
});
client.config = { client.config = {
version: JSON.parse(readFileSync('./package.json').toString()).version, version: JSON.parse(readFileSync("./package.json").toString()).version,
token_discord: process.env.TOKEN_DISCORD, token_discord: process.env.TOKEN_DISCORD,
default_lang: process.env.DEFAULT_LANG ?? 'fr', default_lang: process.env.DEFAULT_LANG ?? "fr",
}; };
client.modals = { client.modals = {
categories: new Collection(), categories: new Collection(),
list: new Collection(), list: new Collection(),
}; };
client.buttons = { client.buttons = {
categories: new Collection(), categories: new Collection(),
list: new Collection(), list: new Collection(),
}; };
client.commands = { client.commands = {
categories: new Collection(), categories: new Collection(),
list: new Collection(), list: new Collection(),
}; };
console.log('Translations progression :'); console.log("Translations progression :");
client.locales = await loadLocales(client.config.default_lang); client.locales = await loadLocales(client.config.default_lang);
client.db = new Database(`${process.env.DOCKERIZED === '1' ? '/config' : './config'}/db.sqlite3`); client.db = new Database(
`${process.env.DOCKERIZED === "1" ? "/config" : "./config"}/db.sqlite3`
);
initDatabase(client.db); initDatabase(client.db);
return client; return client;
}; };
/** /**
@ -49,11 +48,11 @@ export default async () => {
* @param client Client * @param client Client
*/ */
export const quit = (client: Client) => { export const quit = (client: Client) => {
// Close DB // Close DB
client.db.close(); client.db.close();
// Close client // Close client
client.destroy(); client.destroy();
}; };
/** /**
@ -61,8 +60,9 @@ export const quit = (client: Client) => {
* @param db Database * @param db Database
*/ */
const initDatabase = (db: Database) => { const initDatabase = (db: Database) => {
// Table for reminders // Table for reminders
db.run('CREATE TABLE IF NOT EXISTS reminder ( \ db.run(
"CREATE TABLE IF NOT EXISTS reminder ( \
id INTEGER PRIMARY KEY, \ id INTEGER PRIMARY KEY, \
data TEXT, \ data TEXT, \
expiration_date TEXT, \ expiration_date TEXT, \
@ -73,5 +73,6 @@ const initDatabase = (db: Database) => {
guild_id TEXT, \ guild_id TEXT, \
locale TEXT, \ locale TEXT, \
timeout_id TEXT \ timeout_id TEXT \
);'); );"
);
}; };

View file

@ -1,6 +1,6 @@
import { Client } from 'discord.js'; import { Client } from "discord.js";
import { readdir } from 'fs/promises'; import { readdir } from "fs/promises";
import { removeExtension } from './misc'; import { removeExtension } from "./misc";
/** /**
* Load the localizations files into memory. * Load the localizations files into memory.
@ -10,40 +10,39 @@ import { removeExtension } from './misc';
* @returns Map of map with all the localizations * @returns Map of map with all the localizations
*/ */
export const loadLocales = async (default_lang: string) => { export const loadLocales = async (default_lang: string) => {
// Get files from locales/ directory // Get files from locales/ directory
const old_path = __dirname.split('/'); const old_path = __dirname.split("/");
old_path.pop(); old_path.pop();
const files = await readdir(`${old_path.join('/')}/locales`); const files = await readdir(`${old_path.join("/")}/locales`);
// Read JSON files content and load it into the memory // Read JSON files content and load it into the memory
const locales = new Map<string, Map<string, string>>(); const locales = new Map<string, Map<string, string>>();
await Promise.all( await Promise.all(
files.map(async lang => { files.map(async (lang) => {
// Import file // Import file
const content: { const content: {
[key: string]: string [key: string]: string;
} = await import( } = await import(`../locales/${lang}`);
`../locales/${lang}`
);
// Add it to the memory // Add it to the memory
locales.set( locales.set(
removeExtension(lang), removeExtension(lang),
new Map(Object.keys(content) new Map(
// Ignore the default key Object.keys(content)
.filter(str => str !== 'default') // Ignore the default key
.map(str => { .filter((str) => str !== "default")
return [str, content[str]]; .map((str) => {
}), return [str, content[str]];
) })
); )
}) );
); })
);
// Check locales sanity // Check locales sanity
checkLocales(locales, default_lang); checkLocales(locales, default_lang);
return locales; return locales;
}; };
/** /**
@ -53,26 +52,31 @@ export const loadLocales = async (default_lang: string) => {
* @param text Name of string to fetch * @param text Name of string to fetch
* @returns the dictionary * @returns the dictionary
*/ */
export const getLocalizations = (client: Client, text: string, lowercase = false) => { export const getLocalizations = (
const data: Record<string, string> = {}; client: Client,
text: string,
lowercase = false
) => {
const data: Record<string, string> = {};
// Load all the localizations // Load all the localizations
client.locales.forEach((locale, lang) => { client.locales.forEach((locale, lang) => {
// Fetch the text and fallback to default lang if needed // Fetch the text and fallback to default lang if needed
// See getLocale for more info on why we *can* fallback // See getLocale for more info on why we *can* fallback
let str = locale.get(text) let str =
?? client.locales.get(client.config.default_lang)?.get(text); locale.get(text) ??
client.locales.get(client.config.default_lang)?.get(text);
// Store it if defined // Store it if defined
if (str !== undefined) { if (str !== undefined) {
if (lowercase) { if (lowercase) {
str = str.toLowerCase(); str = str.toLowerCase();
} }
data[lang] = str; data[lang] = str;
} }
}); });
return data; return data;
}; };
/** /**
@ -83,22 +87,22 @@ export const getLocalizations = (client: Client, text: string, lowercase = false
* @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) => { 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);
// Load desired lang // Load desired lang
const desired_locales = client.locales.get(lang); const desired_locales = client.locales.get(lang);
// Get text and fallback to default lang if needed // Get text and fallback to default lang if needed
// //
// We can fallback to the default one without any problem // We can fallback to the default one without any problem
// because we make sure that the default language always contains // because we make sure that the default language always contains
// the desired text, and that the other languages are only translations // the desired text, and that the other languages are only translations
const locales = new Map(); const locales = new Map();
default_locales?.forEach((_, key) => { default_locales?.forEach((_, key) => {
locales.set(key, desired_locales?.get(key) ?? default_locales.get(key)); locales.set(key, desired_locales?.get(key) ?? default_locales.get(key));
}); });
return locales; return locales;
}; };
/** /**
@ -110,65 +114,72 @@ export const getLocale = (client: Client, lang: string) => {
* @param default_lang default lang * @param default_lang default lang
* @returns void * @returns void
*/ */
const checkLocales = const checkLocales = async (
async (locales: Map<string, Map<string, string>>, default_lang: string) => { locales: Map<string, Map<string, string>>,
// Associate each lang with the number of locale it has default_lang: string
let locales_size = new Map<string, number>(); ) => {
locales.forEach((locales_data, lang) => { // Associate each lang with the number of locale it has
locales_size.set(lang, locales_data.size); let locales_size = new Map<string, number>();
}); locales.forEach((locales_data, lang) => {
locales_size.set(lang, locales_data.size);
});
// Sort the map // Sort the map
locales_size = new Map([...locales_size.entries()] locales_size = new Map(
.sort((a, b) => b[1] - a[1])); [...locales_size.entries()].sort((a, b) => b[1] - a[1])
);
// Check if default lang is 100% // Check if default lang is 100%
const [max_size_name] = locales_size.keys(); const [max_size_name] = locales_size.keys();
const [max_size] = locales_size.values(); const [max_size] = locales_size.values();
const default_lang_size = locales_size.get(default_lang) ?? 0; const default_lang_size = locales_size.get(default_lang) ?? 0;
if (max_size > default_lang_size) { if (max_size > default_lang_size) {
// Throw error because in this case we are sure than the security // Throw error because in this case we are sure than the security
// explained in getLocale isn't true. // explained in getLocale isn't true.
// However, it is possible that this condition is true // However, it is possible that this condition is true
// and the security is poor, but it's better than nothing. // and the security is poor, but it's better than nothing.
throw new Error( throw new Error(
`The default locale (${default_lang} = ${default_lang_size}) isn't complete ` `The default locale (${default_lang} = ${default_lang_size}) isn't complete ` +
+ `(${max_size_name} = ${max_size}).` `(${max_size_name} = ${max_size}).`
); );
} }
// Remove the default language as it is used as a reference // Remove the default language as it is used as a reference
locales_size.delete(default_lang); locales_size.delete(default_lang);
// Displays the percentages according to the default language // Displays the percentages according to the default language
// lower is bigger // lower is bigger
const bar_size = 4; const bar_size = 4;
locales_size.forEach((size, lang) => { locales_size.forEach((size, lang) => {
const percentage = (size / max_size) * 100; const percentage = (size / max_size) * 100;
// Colored bar part // Colored bar part
const blocks = ' '.repeat(Math.floor(percentage / bar_size)); const blocks = " ".repeat(Math.floor(percentage / bar_size));
// Blank bar part // Blank bar part
const blank = ' '.repeat(Math.ceil((100 - percentage) / bar_size)); const blank = " ".repeat(Math.ceil((100 - percentage) / bar_size));
const color = () => { const color = () => {
switch (true) { switch (true) {
case percentage <= 25: case percentage <= 25:
// Red // Red
return '\x1b[41m'; return "\x1b[41m";
case percentage <= 50: case percentage <= 50:
// Mangeta // Mangeta
return '\x1b[45m'; return "\x1b[45m";
case percentage <= 75: case percentage <= 75:
// Cyan // Cyan
return '\x1b[46m'; return "\x1b[46m";
case percentage <= 100: case percentage <= 100:
// Green // Green
return '\x1b[42m'; return "\x1b[42m";
default: default:
return ''; return "";
} }
}; };
const padding = ' '.repeat(lang.length === 5 ? 1 : 4); const padding = " ".repeat(lang.length === 5 ? 1 : 4);
console.log(`${padding}${lang} | ${color()}${blocks}\x1b[0m${blank} | ${percentage.toPrecision(3)}%`); console.log(
}); `${padding}${lang} | ${color()}${blocks}\x1b[0m${blank} | ${percentage.toPrecision(
3
)}%`
);
});
}; };

View file

@ -1,4 +1,4 @@
import { GuildMember } from 'discord.js'; import { GuildMember } from "discord.js";
/** /**
* Log module status. * Log module status.
@ -7,8 +7,8 @@ import { GuildMember } from 'discord.js';
* @returns String * @returns String
*/ */
export const logStart = (name: string, status: boolean) => { export const logStart = (name: string, status: boolean) => {
// TODO Handle precision about the error if status is false // TODO Handle precision about the error if status is false
return `> ${name}\t${status === true ? '✅' : '❌'}`; return `> ${name}\t${status === true ? "✅" : "❌"}`;
}; };
/** /**
@ -17,15 +17,15 @@ export const logStart = (name: string, status: boolean) => {
* @returns string * @returns string
*/ */
export const getFilename = (path: string) => { export const getFilename = (path: string) => {
const path_list = path.split('/'); const path_list = path.split("/");
// Check if filename exist // Check if filename exist
const filename_with_ext = path_list.pop(); const filename_with_ext = path_list.pop();
if (filename_with_ext === undefined) { if (filename_with_ext === undefined) {
throw new Error(`Filename error: don't exist in ${path}`); throw new Error(`Filename error: don't exist in ${path}`);
} }
return removeExtension(filename_with_ext); return removeExtension(filename_with_ext);
}; };
/** /**
@ -34,10 +34,10 @@ export const getFilename = (path: string) => {
* @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) => {
const array = filename.split('.'); const array = filename.split(".");
array.pop(); array.pop();
return array.join('.'); return array.join(".");
}; };
/** /**
@ -46,9 +46,9 @@ export const removeExtension = (filename: string) => {
* @returns string of the extension if it exists * @returns string of the extension if it exists
*/ */
export const getExtension = (filename: string) => { export const getExtension = (filename: string) => {
const array = filename.split('.'); const array = filename.split(".");
return array.pop(); return array.pop();
}; };
/** /**
@ -57,9 +57,7 @@ export const getExtension = (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(getExtension(filename)?.match( return Boolean(getExtension(filename)?.match(/jpg|jpeg|png|webp|gif/));
/jpg|jpeg|png|webp|gif/
));
}; };
/** /**
@ -68,14 +66,14 @@ export const isImage = (filename: string) => {
* @returns string * @returns string
*/ */
export const userWithNickname = (member: GuildMember) => { export const userWithNickname = (member: GuildMember) => {
if (!member) { if (!member) {
return undefined; return undefined;
} }
if (member.nickname) { if (member.nickname) {
return `${member.nickname} (${member.user.tag})`; return `${member.nickname} (${member.user.tag})`;
} else { } else {
return member.user.tag; return member.user.tag;
} }
}; };
/** /**
@ -84,20 +82,23 @@ export const userWithNickname = (member: GuildMember) => {
* @returns Formatted text * @returns Formatted text
*/ */
export const cleanCodeBlock = (text: string) => { export const cleanCodeBlock = (text: string) => {
text = `\`${text.trim()}\``; text = `\`${text.trim()}\``;
// Keep mentions // Keep mentions
text = text.replace(/(<@\d+>)/g, function(mention: string) { text = text.replace(/(<@\d+>)/g, function (mention: string) {
return `\`${mention}\``; return `\`${mention}\``;
}); });
// Keep links // Keep links
text = text.replace(/(http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)/g, function(url: string) { text = text.replace(
return `\`${url}\``; /(http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)/g,
}); function (url: string) {
return `\`${url}\``;
}
);
// Fix issues // Fix issues
text = text.replace('``', ''); text = text.replace("``", "");
return text; return text;
}; };

View file

@ -1,44 +1,44 @@
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 { cleanCodeBlock } from './misc'; import { cleanCodeBlock } from "./misc";
import { showDate, strToSeconds, timeDeltaToString } from './time'; import { showDate, strToSeconds, timeDeltaToString } from "./time";
/** /**
* Option possible for reminders * Option possible for reminders
*/ */
export enum OptionReminder { export enum OptionReminder {
/** No parameters */ /** No parameters */
Nothing, Nothing,
/** @ */ /** @ */
Mention, Mention,
/** p */ /** p */
DirectMessage, DirectMessage,
} }
/** /**
* Store data about the remidner * Store data about the remidner
*/ */
export type infoReminder = { export type infoReminder = {
locale: string, locale: string;
message: string | null, message: string | null;
createdAt: number, createdAt: number;
channelId: string | null, channelId: string | null;
userId: string, userId: string;
guildId: string | null guildId: string | null;
} };
export type dbReminder = { export type dbReminder = {
id: number, id: number;
data: string | null, data: string | null;
expiration_date: number, expiration_date: number;
option_id: OptionReminder, option_id: OptionReminder;
channel_id: string | null, channel_id: string | null;
creation_date: string, creation_date: string;
user_id: string, user_id: string;
guild_id: string | null, guild_id: string | null;
locale: string, locale: string;
timeout_id: string timeout_id: string;
} };
/** /**
* Split the time and the extra args `p` and `@` * Split the time and the extra args `p` and `@`
@ -46,13 +46,13 @@ export type dbReminder = {
* @returns An object with the time and the option * @returns An object with the time and the option
*/ */
const splitTime = (time: string) => { const splitTime = (time: string) => {
if (time?.endsWith('@')) { if (time?.endsWith("@")) {
return { time: time.slice(0, -1), option: OptionReminder.Mention }; return { time: time.slice(0, -1), option: OptionReminder.Mention };
} else if (time?.toLowerCase().endsWith('p')) { } else if (time?.toLowerCase().endsWith("p")) {
return { time: time.slice(0, -1), option: OptionReminder.DirectMessage }; return { time: time.slice(0, -1), option: OptionReminder.DirectMessage };
} }
return { time: time, option: OptionReminder.Nothing }; return { time: time, option: OptionReminder.Nothing };
}; };
/** /**
@ -62,34 +62,43 @@ const splitTime = (time: string) => {
* @param info data about the context of the reminder * @param info data about the context of the reminder
* @returns Promise resolution of the sql request * @returns Promise resolution of the sql request
*/ */
export const newReminder = async (client: Client, time: string, info: infoReminder) => export const newReminder = async (
new Promise((ok, ko) => { client: Client,
const data = splitTime(time); time: string,
const timeout = strToSeconds(data.time); info: infoReminder
const timeoutId = setTimeoutReminder(client, info, data.option, timeout); ) =>
new Promise((ok, ko) => {
const data = splitTime(time);
const timeout = strToSeconds(data.time);
const timeoutId = setTimeoutReminder(client, info, data.option, timeout);
// Add the remind to the db // Add the remind to the db
client.db.run('INSERT INTO reminder ( \ client.db.run(
"INSERT INTO reminder ( \
data, expiration_date, option_id, channel_id, creation_date, user_id, guild_id, locale, timeout_id \ data, expiration_date, option_id, channel_id, creation_date, user_id, guild_id, locale, timeout_id \
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? );', [ ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? );",
info.message, [
`${info.createdAt + (timeout * 1000)}`, info.message,
data.option.valueOf(), `${info.createdAt + timeout * 1000}`,
info.channelId, data.option.valueOf(),
`${info.createdAt}`, info.channelId,
info.userId, `${info.createdAt}`,
info.guildId, info.userId,
info.locale, info.guildId,
timeoutId], (err) => { info.locale,
if (err) { timeoutId,
ko(err); ],
} (err) => {
if (err) {
ko(err);
}
// Send confirmation to user // Send confirmation to user
const loc = getLocale(client, info.locale); const loc = getLocale(client, info.locale);
ok(`${loc.get('c_reminder1')} ${data.time}.`); ok(`${loc.get("c_reminder1")} ${data.time}.`);
}); }
}); );
});
/** /**
* Delete a reminder from the database * Delete a reminder from the database
@ -98,83 +107,99 @@ export const newReminder = async (client: Client, time: string, info: infoRemind
* @param userId User ID who created the reminder * @param userId User ID who created the reminder
* @returns what the SQlite request sended * @returns what the SQlite request sended
*/ */
export const deleteReminder = (client: Client, createdAt: string, userId: string) => { export const deleteReminder = (
// Delete the reminder for the database client: Client,
return new Promise((ok, ko) => { createdAt: string,
// Add the remind to the db userId: string
client.db.run('DELETE FROM reminder WHERE creation_date = ? AND user_id = ?', [createdAt, userId], (err) => { ) => {
if (err) { // Delete the reminder for the database
ko(err); return new Promise((ok, ko) => {
} // Add the remind to the db
client.db.run(
"DELETE FROM reminder WHERE creation_date = ? AND user_id = ?",
[createdAt, userId],
(err) => {
if (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 = (
const loc = getLocale(client, info.locale); client: Client,
// Send the message in the appropriate channel info: infoReminder,
// TODO: Embed option: OptionReminder
let message: string; ) => {
if (info.message === null) { const loc = getLocale(client, info.locale);
message = loc.get('c_reminder18'); // Send the message in the appropriate channel
} else { // TODO: Embed
message = cleanCodeBlock(info.message); let message: string;
} if (info.message === null) {
const embed = new EmbedBuilder() message = loc.get("c_reminder18");
.setColor('Random') } else {
.setDescription(message) message = cleanCodeBlock(info.message);
.setTimestamp(info.createdAt); }
const embed = new EmbedBuilder()
.setColor("Random")
.setDescription(message)
.setTimestamp(info.createdAt);
let channelOk = false; let channelOk = false;
if (info.channelId !== null) { if (info.channelId !== null) {
if (client.channels.cache.get(info.channelId) !== undefined) { if (client.channels.cache.get(info.channelId) !== undefined) {
channelOk = true; channelOk = true;
} else { } else {
embed.setFooter({ text: loc.get('c_reminder14') }); embed.setFooter({ text: loc.get("c_reminder14") });
} }
} }
let guildOk = false; let guildOk = false;
if (info.guildId !== null) { if (info.guildId !== null) {
const guild = client.guilds.cache.get(info.guildId); const guild = client.guilds.cache.get(info.guildId);
if (guild !== undefined) { if (guild !== undefined) {
if (guild.members.cache.get(info.userId) !== undefined) { if (guild.members.cache.get(info.userId) !== undefined) {
guildOk = true; guildOk = true;
} else { } else {
embed.setFooter({ text: `${loc.get('c_reminder15')} ${guild.name}.` }); embed.setFooter({ text: `${loc.get("c_reminder15")} ${guild.name}.` });
} }
} else { } else {
embed.setFooter({ text: loc.get('c_reminder16') }); embed.setFooter({ text: loc.get("c_reminder16") });
} }
} }
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) {
user.send({ embeds: [embed] }); user.send({ embeds: [embed] });
} }
} else { } else {
// Channel // Channel
client.channels.fetch(info.channelId ?? '').then((channel) => { client.channels.fetch(info.channelId ?? "").then((channel) => {
if (channel?.isTextBased()) { if (channel?.isTextBased()) {
let content = `<@${info.userId}>`; let content = `<@${info.userId}>`;
embed.setFooter({ text: `${loc.get('c_reminder17')} ${timeDeltaToString(info.createdAt)}` }); embed.setFooter({
text: `${loc.get("c_reminder17")} ${timeDeltaToString(
info.createdAt
)}`,
});
// Mention everybody if needed // Mention everybody if needed
if (option == OptionReminder.Mention) { if (option == OptionReminder.Mention) {
(info.message?.match(/<@\d+>/g) ?? []).forEach(mention => { (info.message?.match(/<@\d+>/g) ?? []).forEach((mention) => {
content += ' ' + mention; content += " " + mention;
}); });
} }
channel.send({ content, embeds: [embed] }); channel.send({ content, embeds: [embed] });
} }
}); });
} }
}; };
/** /**
@ -185,16 +210,25 @@ export const sendReminder = (client: Client, info: infoReminder, option: OptionR
* @param timeout Amout of time before the reminder ends * @param timeout Amout of time before the reminder ends
* @returns Timeout's ID * @returns Timeout's ID
*/ */
export const setTimeoutReminder = (client: Client, info: infoReminder, option: OptionReminder, timeout: number) => { export const setTimeoutReminder = (
return Number(setTimeout(() => { client: Client,
deleteReminder(client, String(info.createdAt), info.userId).then((val) => { info: infoReminder,
if (val != true) { option: OptionReminder,
throw val; timeout: number
} ) => {
return Number(
setTimeout(() => {
deleteReminder(client, String(info.createdAt), info.userId).then(
(val) => {
if (val != true) {
throw val;
}
sendReminder(client, info, option); sendReminder(client, info, option);
}); }
}, timeout * 1000)); );
}, timeout * 1000)
);
}; };
/** /**
@ -204,26 +238,33 @@ export const setTimeoutReminder = (client: Client, info: infoReminder, option: O
* @param userId user ID to check * @param userId user ID to check
* @param guildId guild ID where the ownership request as been send, 0 if DM * @param guildId guild ID where the ownership request as been send, 0 if DM
*/ */
export const checkOwnershipReminder = async (client: Client, id: number, userId: string, guildId: string) => { export const checkOwnershipReminder = async (
const data = await new Promise((ok, ko) => { client: Client,
// Check the ownership id: number,
client.db.all('SELECT EXISTS ( \ userId: string,
guildId: string
) => {
const data = (await new Promise((ok, ko) => {
// Check the ownership
client.db.all(
"SELECT EXISTS ( \
SELECT 1 FROM reminder \ SELECT 1 FROM reminder \
WHERE id = ? \ WHERE id = ? \
AND user_id = ? \ AND user_id = ? \
AND (guild_id = ? OR guild_id = 0) \ AND (guild_id = ? OR guild_id = 0) \
)', [ )",
id, userId, guildId, [id, userId, guildId],
], (err, row) => { (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 { [key: string]: number }; );
return Object.keys(data).map((key) => data[key])[0] === 0 ? true : false; })) as { [key: string]: number };
return Object.keys(data).map((key) => data[key])[0] === 0 ? true : false;
}; };
/** /**
@ -232,19 +273,22 @@ export const checkOwnershipReminder = async (client: Client, id: number, userId:
* @param id Reminder's ID * @param id Reminder's ID
*/ */
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('SELECT * FROM reminder \ client.db.all(
WHERE id = ?', [ "SELECT * FROM reminder \
id], (err, row) => { WHERE id = ?",
if (err) { [id],
ko(err); (err, row) => {
} if (err) {
ko(err);
}
// Send all the current reminders // Send all the current reminders
ok(row[0]); ok(row[0]);
}); }
}) as dbReminder; );
})) as dbReminder;
}; };
/** /**
@ -253,10 +297,11 @@ export const getReminderInfo = async (client: Client, id: number) => {
* @param data Data who will override the data in database * @param data Data who will override the data in database
*/ */
export const updateReminder = (client: Client, data: dbReminder) => { export const updateReminder = (client: Client, data: dbReminder) => {
// Delete the reminder for the database // Delete the reminder for the database
return new Promise((ok, ko) => { return new Promise((ok, ko) => {
// Update the db // Update the db
client.db.run('UPDATE reminder \ client.db.run(
"UPDATE reminder \
SET data = ?, \ SET data = ?, \
expiration_date = ?, \ expiration_date = ?, \
option_id = ?, \ option_id = ?, \
@ -266,24 +311,28 @@ export const updateReminder = (client: Client, data: dbReminder) => {
guild_id = ?, \ guild_id = ?, \
locale = ?, \ locale = ?, \
timeout_id = ? \ timeout_id = ? \
WHERE ID = ?', [ WHERE ID = ?",
data.data, [
data.expiration_date, data.data,
data.option_id, data.expiration_date,
data.channel_id, data.option_id,
data.creation_date, data.channel_id,
data.user_id, data.creation_date,
data.guild_id, data.user_id,
data.locale, data.guild_id,
data.timeout_id, data.locale,
data.id], (err) => { data.timeout_id,
if (err) { data.id,
ko(err); ],
} (err) => {
if (err) {
ko(err);
}
ok(true); ok(true);
}); }
}); );
});
}; };
/** /**
@ -293,20 +342,27 @@ export const updateReminder = (client: Client, data: dbReminder) => {
* @param guildId guild ID * @param guildId guild ID
* @returns List of reminders of a user in a guild * @returns List of reminders of a user in a guild
*/ */
const listReminders = async (client: Client, userId: string, guildId: string | null) => { const listReminders = async (
return await new Promise((ok, ko) => { client: Client,
// Check the ownership userId: string,
client.db.all('SELECT data, creation_date, expiration_date, id FROM reminder \ guildId: string | null
WHERE user_id = ? AND (guild_id = ? OR guild_id = 0)', [ ) => {
userId, guildId ?? 0], (err, row) => { return (await new Promise((ok, ko) => {
if (err) { // Check the ownership
ko(err); client.db.all(
} "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) {
ko(err);
}
// Send all the current reminders // Send all the current reminders
ok(row); ok(row);
}); }
}) as dbReminder[]; );
})) as dbReminder[];
}; };
/** /**
@ -318,46 +374,65 @@ const listReminders = async (client: Client, userId: string, guildId: string | n
* @param local Lang * @param local Lang
* @returns Pretty embed who list reminders * @returns Pretty embed who list reminders
*/ */
export const embedListReminders = async (client: Client, user: User, guildId: string | null, page: number, local: string) => { export const embedListReminders = async (
const loc = getLocale(client, local); client: Client,
const reminders = await listReminders(client, user.id, guildId); user: User,
guildId: string | null,
page: number,
local: string
) => {
const loc = getLocale(client, local);
const reminders = await listReminders(client, user.id, guildId);
const elementPerPage = 5; const elementPerPage = 5;
const pageMax = Math.ceil(reminders.length / elementPerPage); const pageMax = Math.ceil(reminders.length / elementPerPage);
if (pageMax <= 1) { if (pageMax <= 1) {
page = 1; page = 1;
} }
// TODO: Use Random color or force a color from args // TODO: Use Random color or force a color from args
const embed = new EmbedBuilder() const embed = new EmbedBuilder()
.setColor(Colors.DarkGrey) .setColor(Colors.DarkGrey)
.setDescription(`${loc.get('c_reminder5')} ${user}${loc.get('c_reminder6')} ${page}/${pageMax}`) .setDescription(
.setThumbnail(user.displayAvatarURL()); `${loc.get("c_reminder5")} ${user}${loc.get(
"c_reminder6"
)} ${page}/${pageMax}`
)
.setThumbnail(user.displayAvatarURL());
const limit = elementPerPage * page; const limit = elementPerPage * page;
if (reminders.length > 0 && page <= pageMax) { if (reminders.length > 0 && page <= pageMax) {
let curseur = limit - elementPerPage; let curseur = limit - elementPerPage;
reminders.splice(0, limit - elementPerPage); reminders.splice(0, limit - elementPerPage);
reminders.forEach((remind) => { reminders.forEach((remind) => {
if (curseur < limit) { if (curseur < limit) {
let text = remind.data ?? loc.get('c_reminder7'); let text = remind.data ?? loc.get("c_reminder7");
if (text.length > 1024) { if (text.length > 1024) {
text = `${text.substring(0, 1021)}...`; text = `${text.substring(0, 1021)}...`;
} }
const expiration = `${loc.get('c_reminder8')} ${timeDeltaToString(remind.expiration_date)}`; const expiration = `${loc.get("c_reminder8")} ${timeDeltaToString(
embed.addFields({ remind.expiration_date
name: `#${remind.id}${loc.get('c_reminder9')} ${showDate(local, loc, new Date(Number(remind.creation_date)))}\n${expiration}`, )}`;
value: text, embed.addFields({
inline: false, name: `#${remind.id}${loc.get("c_reminder9")} ${showDate(
}); local,
loc,
new Date(Number(remind.creation_date))
)}\n${expiration}`,
value: text,
inline: false,
});
curseur++; curseur++;
} }
}); });
} else { } else {
embed.addFields({ name: '\u200b', value: `${loc.get('c_reminder10')}${page} ${loc.get('c_reminder11')}.` }); embed.addFields({
} name: "\u200b",
value: `${loc.get("c_reminder10")}${page} ${loc.get("c_reminder11")}.`,
});
}
return embed; return embed;
}; };

View file

@ -6,20 +6,20 @@
* @returns String * @returns String
*/ */
export const showDate = ( export const showDate = (
tz: string, tz: string,
locale: Map<string, unknown>, locale: Map<string, unknown>,
date: Date date: Date
) => { ) => {
return date.toLocaleString(tz).replace(' ', ` ${locale.get('u_time_at')} `); return date.toLocaleString(tz).replace(" ", ` ${locale.get("u_time_at")} `);
}; };
enum TimeSecond { enum TimeSecond {
Year = 31536000, Year = 31536000,
Week = 604800, Week = 604800,
Day = 86400, Day = 86400,
Hour = 3600, Hour = 3600,
Minute = 60, Minute = 60,
Second = 1 Second = 1,
} }
/** /**
@ -28,18 +28,26 @@ enum TimeSecond {
* @returns time in seconds * @returns time in seconds
*/ */
export const strToSeconds = (time: string) => { export const strToSeconds = (time: string) => {
const regex = new RegExp(`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|(?<${TimeSecond[TimeSecond.Week]}>[0-9]+(?=[w]))|(?<${TimeSecond[TimeSecond.Day]}>[0-9]+(?=[d|j]))|(?<${TimeSecond[TimeSecond.Hour]}>[0-9]+(?=[h]))|(?<${TimeSecond[TimeSecond.Minute]}>[0-9]+(?=[m]))|(?<${TimeSecond[TimeSecond.Second]}>[0-9]+(?=[s]?))`); const regex = new RegExp(
`(?<${TimeSecond[TimeSecond.Year]}>[0-9]+(?=[y|a]))|(?<${
TimeSecond[TimeSecond.Week]
}>[0-9]+(?=[w]))|(?<${TimeSecond[TimeSecond.Day]}>[0-9]+(?=[d|j]))|(?<${
TimeSecond[TimeSecond.Hour]
}>[0-9]+(?=[h]))|(?<${TimeSecond[TimeSecond.Minute]}>[0-9]+(?=[m]))|(?<${
TimeSecond[TimeSecond.Second]
}>[0-9]+(?=[s]?))`
);
const data = Object.assign({}, regex.exec(time)?.groups); const data = Object.assign({}, regex.exec(time)?.groups);
let res = 0; let res = 0;
Object.entries(data).forEach(([key, value]) => { Object.entries(data).forEach(([key, value]) => {
if (value) { if (value) {
res += +value * TimeSecond[key as keyof typeof TimeSecond]; res += +value * TimeSecond[key as keyof typeof TimeSecond];
} }
}); });
return res; return res;
}; };
/** /**
@ -48,7 +56,7 @@ export const strToSeconds = (time: string) => {
* @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();
// TODO adapt the output and not always parse the time as seconds // TODO adapt the output and not always parse the time as seconds
return `${strToSeconds(`${(now - time) / 1000}`)} secs`; return `${strToSeconds(`${(now - time) / 1000}`)} secs`;
}; };

View file

@ -11,10 +11,10 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */ /* Language and Environment */
"target": "es6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "target": "es6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [ "lib": [
"ES2021.String" "ES2021.String"
], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */ // "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
@ -27,8 +27,8 @@
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */ /* Modules */
"module": "commonjs", /* Specify what module code is generated. */ "module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./src", /* Specify the root folder within your source files. */ "rootDir": "./src" /* Specify the root folder within your source files. */,
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
@ -37,7 +37,7 @@
// "types": [], /* Specify type package names to be included without being referenced in a source file. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
"resolveJsonModule": true, /* Enable importing .json files. */ "resolveJsonModule": true /* Enable importing .json files. */,
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */ /* JavaScript Support */
@ -51,7 +51,7 @@
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist", /* Specify an output folder for all emitted files. */ "outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */ // "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */ // "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
@ -73,12 +73,12 @@
/* Interop Constraints */ /* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */ /* Type Checking */
"strict": true, /* Enable all strict type-checking options. */ "strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
@ -100,10 +100,7 @@
/* Completeness */ /* Completeness */
// "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": [ "include": ["./**/*.ts", "./src/locales/*.json"]
"./**/*.ts",
"./src/locales/*.json"
]
} }