Meta: Use of Prettier (#58)

Usage of Prettier alongs with ESlint

Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Reviewed-on: https://git.kennel.ml/ConfrerieDuKassoulait/Botanique/pulls/58
This commit is contained in:
Anri 2023-01-17 23:11:22 +01:00
parent 42b4257ac0
commit 23c6b6eb48
38 changed files with 2564 additions and 1963 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
dist

View file

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

View file

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

View file

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

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
dist

1
.prettierrc.json Normal file
View file

@ -0,0 +1 @@
{ "printWidth": 100 }

View file

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

10
.vscode/settings.json vendored
View file

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

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)
## Lancer le bot
### En local
> Cloner le repo.
> Spécifier un fichier `.env` en suivant [l'exemple](config/example.env).
> Installer les dépendences du bot.
```bash
npm install
```
> Lancer le bot.
```bash
npm run main
```
### Avec Docker (Recommandé)
> Facile avec `docker-compose`
```docker
version: "3.9"
services:
@ -34,23 +40,28 @@ services:
```
## Variables d'environnements
| 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/).
| 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/). |
## Volumes
| Chemin | Description
| :-------: | :-:
| `/config` | Dossier de configuration, par exemple, c'est ici que la base de donnée est.
| Chemin | Description |
| :-------: | :-------------------------------------------------------------------------: |
| `/config` | Dossier de configuration, par exemple, c'est ici que la base de donnée est. |
# Contribuer
Toute contribution est la bienvenue !
Pour commencer, lis le [fichier de contribution](./CONTRIBUTING.md).
# Licence
Voir le [fichier LICENCE](./LICENCE).
# Références
[Photo de profil](https://picrew.me/image_maker/1497656)

513
package-lock.json generated
View file

@ -22,7 +22,7 @@
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"dotenv": "^16.0.1",
"eslint": "^8.20.0",
"prettier-eslint": "^15.0.1",
"ts-node-dev": "^2.0.0"
}
},
@ -332,6 +332,22 @@
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"node_modules/@types/eslint": {
"version": "8.4.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz",
"integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==",
"dev": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -343,6 +359,12 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
},
"node_modules/@types/prettier": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
"integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==",
"dev": true
},
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@ -920,6 +942,15 @@
"color-support": "bin.js"
}
},
"node_modules/common-tags": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
"integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
"dev": true,
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1047,6 +1078,12 @@
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.28.tgz",
"integrity": "sha512-K0fw7m7km9th3dCQ2AR90q/FwX3uAj+OLc+Zuo39VY9vCn0Ux/iObM4y1zJYIH3vTc+QlrksVErUvyeONjOKMQ=="
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -1596,6 +1633,27 @@
"node": ">= 0.4.0"
}
},
"node_modules/has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
"dev": true,
"dependencies": {
"ansi-regex": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/has-ansi/node_modules/ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -1720,7 +1778,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"optional": true,
"devOptional": true,
"engines": {
"node": ">=8"
}
@ -1912,6 +1970,93 @@
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
"node_modules/loglevel": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
"integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
"dev": true,
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/loglevel-colored-level-prefix": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz",
"integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==",
"dev": true,
"dependencies": {
"chalk": "^1.1.3",
"loglevel": "^1.4.1"
}
},
"node_modules/loglevel-colored-level-prefix/node_modules/ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/loglevel-colored-level-prefix/node_modules/chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
"dev": true,
"dependencies": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/loglevel-colored-level-prefix/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
"dev": true,
"dependencies": {
"ansi-regex": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/loglevel-colored-level-prefix/node_modules/supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -2438,6 +2583,92 @@
"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/prettier-eslint": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-15.0.1.tgz",
"integrity": "sha512-mGOWVHixSvpZWARqSDXbdtTL54mMBxc5oQYQ6RAqy8jecuNJBgN3t9E5a81G66F8x8fsKNiR1HWaBV66MJDOpg==",
"dev": true,
"dependencies": {
"@types/eslint": "^8.4.2",
"@types/prettier": "^2.6.0",
"@typescript-eslint/parser": "^5.10.0",
"common-tags": "^1.4.0",
"dlv": "^1.1.0",
"eslint": "^8.7.0",
"indent-string": "^4.0.0",
"lodash.merge": "^4.6.0",
"loglevel-colored-level-prefix": "^1.0.0",
"prettier": "^2.5.1",
"pretty-format": "^23.0.1",
"require-relative": "^0.8.7",
"typescript": "^4.5.4",
"vue-eslint-parser": "^8.0.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/pretty-format": {
"version": "23.6.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
"integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==",
"dev": true,
"dependencies": {
"ansi-regex": "^3.0.0",
"ansi-styles": "^3.2.0"
}
},
"node_modules/pretty-format/node_modules/ansi-regex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
"integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/pretty-format/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pretty-format/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/pretty-format/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -2538,6 +2769,12 @@
"url": "https://github.com/sponsors/mysticatea"
}
},
"node_modules/require-relative": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
"integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==",
"dev": true
},
"node_modules/resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -3200,6 +3437,52 @@
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"node_modules/vue-eslint-parser": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
"integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==",
"dev": true,
"dependencies": {
"debug": "^4.3.2",
"eslint-scope": "^7.0.0",
"eslint-visitor-keys": "^3.1.0",
"espree": "^9.0.0",
"esquery": "^1.4.0",
"lodash": "^4.17.21",
"semver": "^7.3.5"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/mysticatea"
},
"peerDependencies": {
"eslint": ">=6.0.0"
}
},
"node_modules/vue-eslint-parser/node_modules/eslint-scope": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
"dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/vue-eslint-parser/node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@ -3553,6 +3836,22 @@
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
"dev": true
},
"@types/eslint": {
"version": "8.4.10",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz",
"integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==",
"dev": true,
"requires": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"@types/estree": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -3564,6 +3863,12 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
"integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
},
"@types/prettier": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz",
"integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==",
"dev": true
},
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
@ -3965,6 +4270,12 @@
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="
},
"common-tags": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
"integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -4068,6 +4379,12 @@
}
}
},
"dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -4491,6 +4808,23 @@
"function-bind": "^1.1.1"
}
},
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
},
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true
}
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@ -4577,7 +4911,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"optional": true
"devOptional": true
},
"infer-owner": {
"version": "1.0.4",
@ -4729,6 +5063,70 @@
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
"loglevel": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz",
"integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==",
"dev": true
},
"loglevel-colored-level-prefix": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz",
"integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==",
"dev": true,
"requires": {
"chalk": "^1.1.3",
"loglevel": "^1.4.1"
},
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
"dev": true
},
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
"dev": true,
"requires": {
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
"dev": true
}
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -5110,6 +5508,76 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"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
},
"prettier-eslint": {
"version": "15.0.1",
"resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-15.0.1.tgz",
"integrity": "sha512-mGOWVHixSvpZWARqSDXbdtTL54mMBxc5oQYQ6RAqy8jecuNJBgN3t9E5a81G66F8x8fsKNiR1HWaBV66MJDOpg==",
"dev": true,
"requires": {
"@types/eslint": "^8.4.2",
"@types/prettier": "^2.6.0",
"@typescript-eslint/parser": "^5.10.0",
"common-tags": "^1.4.0",
"dlv": "^1.1.0",
"eslint": "^8.7.0",
"indent-string": "^4.0.0",
"lodash.merge": "^4.6.0",
"loglevel-colored-level-prefix": "^1.0.0",
"prettier": "^2.5.1",
"pretty-format": "^23.0.1",
"require-relative": "^0.8.7",
"typescript": "^4.5.4",
"vue-eslint-parser": "^8.0.1"
}
},
"pretty-format": {
"version": "23.6.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz",
"integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0",
"ansi-styles": "^3.2.0"
},
"dependencies": {
"ansi-regex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
"integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "^1.9.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
}
}
},
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -5171,6 +5639,12 @@
"integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
"dev": true
},
"require-relative": {
"version": "0.8.7",
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
"integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==",
"dev": true
},
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@ -5623,6 +6097,39 @@
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true
},
"vue-eslint-parser": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz",
"integrity": "sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g==",
"dev": true,
"requires": {
"debug": "^4.3.2",
"eslint-scope": "^7.0.0",
"eslint-visitor-keys": "^3.1.0",
"espree": "^9.0.0",
"esquery": "^1.4.0",
"lodash": "^4.17.21",
"semver": "^7.3.5"
},
"dependencies": {
"eslint-scope": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
}
},
"estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true
}
}
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View file

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

View file

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

View file

@ -1,56 +1,70 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, 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';
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
Client,
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 {
data: {
name: getFilename(__filename),
},
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale);
const embed_desc = interaction.message.embeds.at(0)?.description as string;
data: {
name: getFilename(__filename),
},
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale);
const embed_desc = interaction.message.embeds.at(0)?.description as string;
// Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
if (page + 1 > pageMax) {
page = 1;
} else {
page++;
}
// Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
if (page + 1 > pageMax) {
page = 1;
} else {
page++;
}
// Retrieve user
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
const user = client.users.cache.get(userId) as User;
// Retrieve user
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
const user = client.users.cache.get(userId) as User;
// Fetch list
const list = await embedListReminders(client, user, interaction.guildId, page, interaction.locale);
// Fetch list
const list = await embedListReminders(
client,
user,
interaction.guildId,
page,
interaction.locale
);
const idPrec = 'reminderList-prec_' + uuidv4();
const idNext = 'reminderList-next_' + uuidv4();
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(idPrec)
.setLabel(loc.get('c_reminder12'))
.setStyle(ButtonStyle.Primary))
.addComponents(
new ButtonBuilder()
.setCustomId(idNext)
.setLabel(loc.get('c_reminder13'))
.setStyle(ButtonStyle.Primary),
);
const idPrec = "reminderList-prec_" + uuidv4();
const idNext = "reminderList-next_" + uuidv4();
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(idPrec)
.setLabel(loc.get("c_reminder12"))
.setStyle(ButtonStyle.Primary)
)
.addComponents(
new ButtonBuilder()
.setCustomId(idNext)
.setLabel(loc.get("c_reminder13"))
.setStyle(ButtonStyle.Primary)
);
// Buttons interactions
collect(client, interaction, idPrec);
collect(client, interaction, idNext);
// Buttons interactions
collect(client, interaction, idPrec);
collect(client, interaction, idNext);
return {
embeds: [list],
components: [row],
};
},
return {
embeds: [list],
components: [row],
};
},
};

View file

@ -1,56 +1,70 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, 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';
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
Client,
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 {
data: {
name: getFilename(__filename),
},
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale);
const embed_desc = interaction.message.embeds.at(0)?.description as string;
data: {
name: getFilename(__filename),
},
interaction: async (interaction: MessageComponentInteraction, client: Client) => {
const loc = getLocale(client, interaction.locale);
const embed_desc = interaction.message.embeds.at(0)?.description as string;
// Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
if (page - 1 == 0) {
page = pageMax;
} else {
page--;
}
// Retrieve Pages
const pageMax = Number(/(\d+)(?!.*\d)/gm.exec(embed_desc)?.[0]);
let page = Number(/(?!• \s+)\d(?=\/)/gm.exec(embed_desc)?.[0]);
if (page - 1 == 0) {
page = pageMax;
} else {
page--;
}
// Retrieve user
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
const user = client.users.cache.get(userId) as User;
// Retrieve user
const userId = /(?!<@)\d+(?=>)/gm.exec(embed_desc)?.[0] as string;
const user = client.users.cache.get(userId) as User;
// Fetch list
const list = await embedListReminders(client, user, interaction.guildId, page, interaction.locale);
// Fetch list
const list = await embedListReminders(
client,
user,
interaction.guildId,
page,
interaction.locale
);
const idPrec = 'reminderList-prec_' + uuidv4();
const idNext = 'reminderList-next_' + uuidv4();
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(idPrec)
.setLabel(loc.get('c_reminder12'))
.setStyle(ButtonStyle.Primary))
.addComponents(
new ButtonBuilder()
.setCustomId(idNext)
.setLabel(loc.get('c_reminder13'))
.setStyle(ButtonStyle.Primary),
);
const idPrec = "reminderList-prec_" + uuidv4();
const idNext = "reminderList-next_" + uuidv4();
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(idPrec)
.setLabel(loc.get("c_reminder12"))
.setStyle(ButtonStyle.Primary)
)
.addComponents(
new ButtonBuilder()
.setCustomId(idNext)
.setLabel(loc.get("c_reminder13"))
.setStyle(ButtonStyle.Primary)
);
// Buttons interactions
collect(client, interaction, idPrec);
collect(client, interaction, idNext);
// Buttons interactions
collect(client, interaction, idPrec);
collect(client, interaction, idNext);
return {
embeds: [list],
components: [row],
};
},
return {
embeds: [list],
components: [row],
};
},
};

View file

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

View file

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

View file

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

View file

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

View file

@ -1,277 +1,283 @@
import { ModalActionRowComponentBuilder, SlashCommandBuilder } from '@discordjs/builders';
import { ActionRowBuilder, 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';
import { ModalActionRowComponentBuilder, SlashCommandBuilder } from "@discordjs/builders";
import {
ActionRowBuilder,
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 {
scope: () => [],
scope: () => [],
data: (client: Client) => {
const filename = getFilename(__filename);
const loc_default = client.locales.get(client.config.default_lang);
if (!loc_default) {
return;
}
data: (client: Client) => {
const filename = getFilename(__filename);
const loc_default = client.locales.get(client.config.default_lang);
if (!loc_default) {
return;
}
return new SlashCommandBuilder()
// Command
.setName(filename.toLowerCase())
.setDescription(loc_default.get(`c_${filename}_desc`) ?? '')
.setNameLocalizations(
getLocalizations(client, `c_${filename}_name`, true)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_desc`)
)
return (
new SlashCommandBuilder()
// Command
.setName(filename.toLowerCase())
.setDescription(loc_default.get(`c_${filename}_desc`) ?? "")
.setNameLocalizations(getLocalizations(client, `c_${filename}_name`, true))
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_desc`))
// New reminder
.addSubcommand(subcommand => subcommand
.setName(
loc_default.get(`c_${filename}_sub1_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub1_desc`) ?? ''
).setNameLocalizations(
getLocalizations(client, `c_${filename}_sub1_name`, true)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub1_desc`)
)
// New reminder
.addSubcommand((subcommand) =>
subcommand
.setName(loc_default.get(`c_${filename}_sub1_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub1_desc`) ?? "")
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub1_name`, true))
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub1_desc`))
// Specified Time
.addStringOption(option => option
.setName(
loc_default.get(`c_${filename}_sub1_opt1_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub1_opt1_desc`) ?? ''
).setNameLocalizations(
getLocalizations(
client,
`c_${filename}_sub1_opt1_name`,
true
)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub1_opt1_desc`)
)
)
// Specified Time
.addStringOption((option) =>
option
.setName(loc_default.get(`c_${filename}_sub1_opt1_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub1_opt1_desc`) ?? "")
.setNameLocalizations(
getLocalizations(client, `c_${filename}_sub1_opt1_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub1_opt1_desc`)
)
)
// Specified message (not required)
.addStringOption(option => option
.setName(
loc_default.get(`c_${filename}_sub1_opt2_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub1_opt2_desc`) ?? ''
).setNameLocalizations(
getLocalizations(
client,
`c_${filename}_sub1_opt2_name`,
true
)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub1_opt2_desc`)
)
)
)
// Specified message (not required)
.addStringOption((option) =>
option
.setName(loc_default.get(`c_${filename}_sub1_opt2_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub1_opt2_desc`) ?? "")
.setNameLocalizations(
getLocalizations(client, `c_${filename}_sub1_opt2_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub1_opt2_desc`)
)
)
)
// List reminders
.addSubcommand(subcommand => subcommand
.setName(
loc_default.get(`c_${filename}_sub2_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub2_desc`) ?? ''
).setNameLocalizations(
getLocalizations(client, `c_${filename}_sub2_name`, true)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub2_desc`)
)
// List reminders
.addSubcommand((subcommand) =>
subcommand
.setName(loc_default.get(`c_${filename}_sub2_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub2_desc`) ?? "")
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub2_name`, true))
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub2_desc`))
// User
.addUserOption(option => option
.setName(
loc_default.get(`c_${filename}_sub2_opt1_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub2_opt1_desc`) ?? ''
).setNameLocalizations(
getLocalizations(
client,
`c_${filename}_sub2_opt1_name`,
true
)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub2_opt1_desc`)
)
)
// User
.addUserOption((option) =>
option
.setName(loc_default.get(`c_${filename}_sub2_opt1_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub2_opt1_desc`) ?? "")
.setNameLocalizations(
getLocalizations(client, `c_${filename}_sub2_opt1_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub2_opt1_desc`)
)
)
// Page
.addIntegerOption(option => option
.setName(
loc_default.get(`c_${filename}_sub2_opt2_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub2_opt2_desc`) ?? ''
).setNameLocalizations(
getLocalizations(
client,
`c_${filename}_sub2_opt2_name`,
true
)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub2_opt2_desc`)
)
)
)
// Page
.addIntegerOption((option) =>
option
.setName(loc_default.get(`c_${filename}_sub2_opt2_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub2_opt2_desc`) ?? "")
.setNameLocalizations(
getLocalizations(client, `c_${filename}_sub2_opt2_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub2_opt2_desc`)
)
)
)
// Delete a reminder
.addSubcommand(subcommand => subcommand
.setName(
loc_default.get(`c_${filename}_sub3_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub3_desc`) ?? ''
).setNameLocalizations(
getLocalizations(client, `c_${filename}_sub3_name`, true)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub3_desc`)
)
// Delete a reminder
.addSubcommand((subcommand) =>
subcommand
.setName(loc_default.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub3_desc`) ?? "")
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub3_name`, true))
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub3_desc`))
// ID
.addIntegerOption(option => option
.setName(
loc_default.get(`c_${filename}_sub3_opt1_name`)
?.toLowerCase() ?? ''
).setDescription(
loc_default.get(`c_${filename}_sub3_opt1_desc`) ?? ''
).setNameLocalizations(
getLocalizations(
client,
`c_${filename}_sub3_opt1_name`,
true
)
).setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub3_opt1_desc`)
).setRequired(true)
),
);
},
// ID
.addIntegerOption((option) =>
option
.setName(loc_default.get(`c_${filename}_sub3_opt1_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub3_opt1_desc`) ?? "")
.setNameLocalizations(
getLocalizations(client, `c_${filename}_sub3_opt1_name`, true)
)
.setDescriptionLocalizations(
getLocalizations(client, `c_${filename}_sub3_opt1_desc`)
)
.setRequired(true)
)
)
);
},
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
const loc_default = client.locales.get(client.config.default_lang);
const filename = getFilename(__filename);
const loc = getLocale(client, interaction.locale);
interaction: async (interaction: ChatInputCommandInteraction, client: Client) => {
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();
switch (subcommand) {
// New reminder
case loc_default?.get(`c_${filename}_sub1_name`)
?.toLowerCase() ?? '': {
const subcommand = interaction.options.getSubcommand();
switch (subcommand) {
// New reminder
case loc_default?.get(`c_${filename}_sub1_name`)?.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 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());
const timeGUI = new TextInputBuilder()
.setCustomId("reminderGUI-time")
.setLabel(loc.get(`c_${filename}_sub1_opt1_name`).capitalize())
.setStyle(TextInputStyle.Short)
.setPlaceholder("1h")
.setRequired(true);
const timeGUI = new TextInputBuilder()
.setCustomId('reminderGUI-time')
.setLabel(loc.get(`c_${filename}_sub1_opt1_name`).capitalize())
.setStyle(TextInputStyle.Short)
.setPlaceholder('1h')
.setRequired(true);
const messageGUI = new TextInputBuilder()
.setCustomId("reminderGUI-message")
.setLabel(loc.get(`c_${filename}_sub1_opt2_name`).capitalize())
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder(loc.get(`c_${filename}_sub1_opt2_desc`))
.setRequired(false);
const messageGUI = new TextInputBuilder()
.setCustomId('reminderGUI-message')
.setLabel(loc.get(`c_${filename}_sub1_opt2_name`).capitalize())
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder(loc.get(`c_${filename}_sub1_opt2_desc`))
.setRequired(false);
modal.addComponents(
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(timeGUI),
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(messageGUI)
);
modal.addComponents(
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(timeGUI),
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(messageGUI)
);
return interaction.showModal(modal);
}
}
// 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);
}
}
// 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;
}
const page =
interaction.options.getInteger(
loc_default?.get(`c_${filename}_sub2_opt2_name`) as string
) ?? 1;
const list = await embedListReminders(
client,
user,
interaction.guildId,
page,
interaction.locale
);
const page = interaction.options.getInteger(loc_default?.get(`c_${filename}_sub2_opt2_name`) as string) ?? 1;
const list = await embedListReminders(client, user, interaction.guildId, page, interaction.locale);
const idPrec = "reminderList-prec_" + uuidv4();
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();
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),
);
// Buttons interactions
collect(client, interaction, idPrec);
collect(client, interaction, idNext);
// Buttons interactions
collect(client, interaction, idPrec);
collect(client, interaction, idNext);
return await interaction.reply({
ephemeral: true,
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] });
}
// 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 });
}
// Check if the ID exists and belongs to the user
if (
await checkOwnershipReminder(client, id, interaction.user.id, interaction.guildId ?? "0")
) {
return interaction.reply({
content: loc.get(`c_${filename}3`),
ephemeral: true,
});
}
// Check if the ID exists and belongs to the user
if (await checkOwnershipReminder(client, id, interaction.user.id, interaction.guildId ?? '0')) {
return interaction.reply({ content: loc.get(`c_${filename}3`), ephemeral: true });
}
// Stop timeout
const reminderInfo = await getReminderInfo(client, id);
clearTimeout(reminderInfo.timeout_id);
// Stop timeout
const reminderInfo = await getReminderInfo(client, id);
clearTimeout(reminderInfo.timeout_id);
// Delete from database
return deleteReminder(client, reminderInfo.creation_date, reminderInfo.user_id)
.then(() => interaction.reply({ content: `Reminder **#${id}** supprimé !`, ephemeral: true }));
}
default: {
console.error(`${__filename}: unknown subcommand (${subcommand})`);
break;
}
}
},
// Delete from database
return deleteReminder(client, reminderInfo.creation_date, reminderInfo.user_id).then(() =>
interaction.reply({
content: `Reminder **#${id}** supprimé !`,
ephemeral: true,
})
);
}
default: {
console.error(`${__filename}: unknown subcommand (${subcommand})`);
break;
}
}
},
};

View file

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

View file

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

View file

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

View file

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

View file

@ -1,78 +1,78 @@
import loadClient, { quit } from './utils/client';
import loadEvents from './events/loader';
import loadModals from './modals/loader';
import loadButtons from './buttons/loader';
import loadCommands from './commands/loader';
import loadClient, { quit } from "./utils/client";
import loadEvents from "./events/loader";
import loadModals from "./modals/loader";
import loadButtons from "./buttons/loader";
import loadCommands from "./commands/loader";
import { logStart } from './utils/misc';
import { logStart } from "./utils/misc";
/** Run the bot. */
const run = async () => {
console.log('Starting Botanique...');
console.log("Starting Botanique...");
// Load .env if not in prod
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(await import('dotenv')).config({ path: './config/.env' });
}
// Load .env if not in prod
if (process.env.NODE_ENV !== "production") {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(await import("dotenv")).config({ path: "./config/.env" });
}
// Client Discord.JS
const client_name = 'Client';
await loadClient()
.then(async client => {
// Events Discord.JS
const events_name = 'Events';
await loadEvents(client)
.then(() => console.log(logStart(events_name, true)))
.catch((err) => {
console.error(err);
throw logStart(events_name, false);
});
// Client Discord.JS
const client_name = "Client";
await loadClient()
.then(async (client) => {
// Events Discord.JS
const events_name = "Events";
await loadEvents(client)
.then(() => console.log(logStart(events_name, true)))
.catch((err) => {
console.error(err);
throw logStart(events_name, false);
});
// Connect the bot to Discord.com
await client.login(client.config.token_discord);
// Connect the bot to Discord.com
await client.login(client.config.token_discord);
// Modals Discord.JS
const modals_name = 'Modals';
await loadModals(client)
.then(() => console.log(logStart(modals_name, true)))
.catch((err) => {
console.error(err);
throw logStart(modals_name, false);
});
// Modals Discord.JS
const modals_name = "Modals";
await loadModals(client)
.then(() => console.log(logStart(modals_name, true)))
.catch((err) => {
console.error(err);
throw logStart(modals_name, false);
});
// Buttons Discord.JS
const buttons_name = 'Buttons';
await loadButtons(client)
.then(() => console.log(logStart(buttons_name, true)))
.catch((err) => {
console.error(err);
throw logStart(buttons_name, false);
});
// Buttons Discord.JS
const buttons_name = "Buttons";
await loadButtons(client)
.then(() => console.log(logStart(buttons_name, true)))
.catch((err) => {
console.error(err);
throw logStart(buttons_name, false);
});
// Commands Slash Discord.JS
const commands_name = 'Commands';
await loadCommands(client)
.then(() => console.log(logStart(commands_name, true)))
.catch((err) => {
console.error(err);
throw logStart(commands_name, false);
});
// Commands Slash Discord.JS
const commands_name = "Commands";
await loadCommands(client)
.then(() => console.log(logStart(commands_name, true)))
.catch((err) => {
console.error(err);
throw logStart(commands_name, false);
});
console.log(logStart(client_name, true));
console.log(`Botanique "${client.user?.username}" v${client.config.version} started!`);
console.log(logStart(client_name, true));
console.log(`Botanique "${client.user?.username}" v${client.config.version} started!`);
// ^C
process.on('SIGINT', () => quit(client));
// ^C
process.on("SIGINT", () => quit(client));
// Container force closed
process.on('SIGTERM', () => quit(client));
})
.catch((err) => {
console.error(err);
throw logStart(client_name, false);
});
// Container force closed
process.on("SIGTERM", () => quit(client));
})
.catch((err) => {
console.error(err);
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_desc": "Pong!",
"c_ping1": "Roundtrip latency",
"c_ping2": "Websocket heartbeat",
"c_ping_name": "Ping",
"c_ping_desc": "Pong!",
"c_ping1": "Roundtrip latency",
"c_ping2": "Websocket heartbeat",
"c_help_name": "Help",
"c_help_desc": "Informations about commands",
"c_help_opt1_name": "command",
"c_help_opt1_desc": "Command wanted in depth.",
"c_help1": "List of categories and associated commands",
"c_help2": "`/help <command>` to get more information about a command.",
"c_help3": "Can't find :",
"c_help_name": "Help",
"c_help_desc": "Informations about commands",
"c_help_opt1_name": "command",
"c_help_opt1_desc": "Command wanted in depth.",
"c_help1": "List of categories and associated commands",
"c_help2": "`/help <command>` to get more information about a command.",
"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_modal": "Désolé, le modèle n'existe plus...",
"e_interacreate_no_button": "Désolé, le bouton 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_button": "Désolé, le bouton n'existe plus...",
"c_ping_name": "Ping",
"c_ping_desc": "Pong!",
"c_ping1": "Latence totale",
"c_ping2": "Latence du Websocket",
"c_ping_name": "Ping",
"c_ping_desc": "Pong!",
"c_ping1": "Latence totale",
"c_ping2": "Latence du Websocket",
"c_help_name": "Aide",
"c_help_desc": "Informations sur les commandes",
"c_help_opt1_name": "commande",
"c_help_opt1_desc": "Commande voulu en détail.",
"c_help1": "Liste des catégories et des commandes associées",
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
"c_help3": "Impossible de trouver :",
"c_help_name": "Aide",
"c_help_desc": "Informations sur les commandes",
"c_help_opt1_name": "commande",
"c_help_opt1_desc": "Commande voulu en détail.",
"c_help1": "Liste des catégories et des commandes associées",
"c_help2": "`/help <commande>` pour obtenir plus d'informations sur une commande.",
"c_help3": "Impossible de trouver :",
"c_archive_name": "Nettoyer",
"c_archive_desc": "Nettoyage pour le passage à niveau",
"c_archive_opt1_name": "catégorie",
"c_archive_opt1_desc": "Nom de la catégorie à nettoyer",
"c_archive1": "Liste des catégories soumis au nettoyage",
"c_archive2": "`L1`, `L2`, `L3`, `M1`, `M2`",
"c_archive3": "Impossible de trouver/nettoyer le salon :",
"c_archive4": "Liste des salons archivés de la catégorie",
"c_archive5": "vers",
"c_archive6": "Nettoyage",
"c_archive7": "Catégorie déjà nettoyée",
"c_archive_name": "Nettoyer",
"c_archive_desc": "Nettoyage pour le passage à niveau",
"c_archive_opt1_name": "catégorie",
"c_archive_opt1_desc": "Nom de la catégorie à nettoyer",
"c_archive1": "Liste des catégories soumis au nettoyage",
"c_archive2": "`L1`, `L2`, `L3`, `M1`, `M2`",
"c_archive3": "Impossible de trouver/nettoyer le salon :",
"c_archive4": "Liste des salons archivés de la catégorie",
"c_archive5": "vers",
"c_archive6": "Nettoyage",
"c_archive7": "Catégorie déjà nettoyée",
"c_prep_name": "Préparation",
"c_prep_desc": "Préparation des salons généraux pour la nouvelle année",
"c_prep_opt1_name": "année",
"c_prep_opt1_desc": "Nom de l'année à préparer'",
"c_prep1": "Liste des catégories soumis à la préparation",
"c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`",
"c_prep3": "Impossible de trouver/nettoyer le salon :",
"c_prep4": "Listes des Salons préparés `",
"c_prep5": "créé",
"c_prep6": "Pas besoin de préparation",
"c_prep_name": "Préparation",
"c_prep_desc": "Préparation des salons généraux pour la nouvelle année",
"c_prep_opt1_name": "année",
"c_prep_opt1_desc": "Nom de l'année à préparer'",
"c_prep1": "Liste des catégories soumis à la préparation",
"c_prep2": "`L1`, `L2`, `L3`, `M1`, `M2`",
"c_prep3": "Impossible de trouver/nettoyer le salon :",
"c_prep4": "Listes des Salons préparés `",
"c_prep5": "créé",
"c_prep6": "Pas besoin de préparation",
"u_time_at": "à",
"u_time_at": "à",
"c_reminder_name": "rappel",
"c_reminder_desc": "Commande relative aux rappels",
"c_reminder_sub1_name": "nouveau",
"c_reminder_sub1_desc": "Met en place un rappel",
"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_opt2_name": "message",
"c_reminder_sub1_opt2_desc": "Message du rappel",
"c_reminder_sub2_name": "liste",
"c_reminder_sub2_desc": "Affiche la liste des rappels d'un utilisateur",
"c_reminder_sub2_opt1_name": "utilisateur",
"c_reminder_sub2_opt1_desc": "Affiche la liste de l'utilisateur en question",
"c_reminder_sub2_opt2_name": "page",
"c_reminder_sub2_opt2_desc": "Page à afficher",
"c_reminder_sub3_name": "efface",
"c_reminder_sub3_desc": "Supprime un rappel",
"c_reminder_sub3_opt1_name": "id",
"c_reminder_sub3_opt1_desc": "Rappel à supprimé",
"c_reminder1": "Un rappel a été configuré pour dans",
"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_reminder4": "Utilisateur inconnu.",
"c_reminder5": "Rappels de",
"c_reminder6": "Page",
"c_reminder7": "Pas de message",
"c_reminder8": "Expire dans",
"c_reminder9": "Fais le",
"c_reminder10": "L'utilisateur n'a aucun rappel en attente ou page n°",
"c_reminder11": "vide",
"c_reminder12": "Précédent",
"c_reminder13": "Suivant",
"c_reminder14": "Message envoyé en DM car le salon n'est plus disponible.",
"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_reminder17": "Message d'il y a",
"c_reminder18": "Pas de message"
"c_reminder_name": "rappel",
"c_reminder_desc": "Commande relative aux rappels",
"c_reminder_sub1_name": "nouveau",
"c_reminder_sub1_desc": "Met en place un rappel",
"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_opt2_name": "message",
"c_reminder_sub1_opt2_desc": "Message du rappel",
"c_reminder_sub2_name": "liste",
"c_reminder_sub2_desc": "Affiche la liste des rappels d'un utilisateur",
"c_reminder_sub2_opt1_name": "utilisateur",
"c_reminder_sub2_opt1_desc": "Affiche la liste de l'utilisateur en question",
"c_reminder_sub2_opt2_name": "page",
"c_reminder_sub2_opt2_desc": "Page à afficher",
"c_reminder_sub3_name": "efface",
"c_reminder_sub3_desc": "Supprime un rappel",
"c_reminder_sub3_opt1_name": "id",
"c_reminder_sub3_opt1_desc": "Rappel à supprimé",
"c_reminder1": "Un rappel a été configuré pour dans",
"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_reminder4": "Utilisateur inconnu.",
"c_reminder5": "Rappels de",
"c_reminder6": "Page",
"c_reminder7": "Pas de message",
"c_reminder8": "Expire dans",
"c_reminder9": "Fais le",
"c_reminder10": "L'utilisateur n'a aucun rappel en attente ou page n°",
"c_reminder11": "vide",
"c_reminder12": "Précédent",
"c_reminder13": "Suivant",
"c_reminder14": "Message envoyé en DM car le salon n'est plus disponible.",
"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_reminder17": "Message d'il y a",
"c_reminder18": "Pas de message"
}

View file

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

View file

@ -1,21 +1,23 @@
import { Client, ModalSubmitInteraction } from 'discord.js';
import { getFilename } from '../../utils/misc';
import { newReminder } from '../../utils/reminder';
import { Client, ModalSubmitInteraction } from "discord.js";
import { getFilename } from "../../utils/misc";
import { newReminder } from "../../utils/reminder";
export default {
data: {
name: getFilename(__filename),
},
interaction: async (interaction: ModalSubmitInteraction, client: Client) =>
newReminder(client, interaction.fields.fields.get('reminderGUI-time')?.value as string, {
locale: interaction.locale,
message: interaction.fields.fields.get('reminderGUI-message')?.value ?? null,
createdAt: interaction.createdAt.getTime(),
channelId: interaction.channelId,
userId: interaction.user.id,
guildId: interaction.guildId,
}).then((msg) => interaction.reply({
content: msg as string,
ephemeral: true,
})),
data: {
name: getFilename(__filename),
},
interaction: async (interaction: ModalSubmitInteraction, client: Client) =>
newReminder(client, interaction.fields.fields.get("reminderGUI-time")?.value as string, {
locale: interaction.locale,
message: interaction.fields.fields.get("reminderGUI-message")?.value ?? null,
createdAt: interaction.createdAt.getTime(),
channelId: interaction.channelId,
userId: interaction.user.id,
guildId: interaction.guildId,
}).then((msg) =>
interaction.reply({
content: msg as string,
ephemeral: true,
})
),
};

View file

@ -1,87 +1,90 @@
import { Collection } from 'discord.js';
import { SlashCommandBuilder } from '@discordjs/builders';
import { Database } from 'sqlite3';
import { Collection } from "discord.js";
import { SlashCommandBuilder } from "@discordjs/builders";
import { Database } from "sqlite3";
export { };
export {};
declare module 'discord.js' {
// eslint-disable-next-line no-shadow
export interface Client {
/** Store the configuration */
config: {
/** Bot version */
version: string,
/** Bot token from env variable */
token_discord: string | undefined,
/** Default lang used */
default_lang: string
},
/** Store all the modals */
modals: {
categories: Collection<
/** Category name */
string,
/** Name of the modals in the category */
string[]
>,
list: Collection<
/** Modal name */
string,
/** Modal itself */
{
/** Data about the modal */
data: {
name: string
},
/** How the modal interact */
interaction: (interaction: ModalSubmitInteraction, client: Client) => unknown
}
>,
},
/** Store all the buttons */
buttons: {
categories: Collection<
/** Category name */
string,
/** Name of the buttons in the category */
string[]
>,
list: Collection<
/** Button name */
string,
/** Button itself */
{
/** Data about the button */
data: {
name: string
},
/** How the button interact */
interaction: (interaction: MessageComponentInteraction, client: Client) => Promise<InteractionUpdateOptions>
}
>,
},
/** Store all the slash commands */
commands: {
categories: Collection<
/** Category name */
string,
/** Name of the commands in the category */
string[]
>,
list: Collection<
/** Command name */
string,
/** Command itself */
{
/** Data about the command */
data: SlashCommandBuilder,
/** How the command interact */
interaction: (interaction: CommandInteraction, client: Client) => unknown
}
>,
},
/** Store all the localizations */
locales: Map<string, Map<string, string>>,
db: Database,
}
declare module "discord.js" {
// eslint-disable-next-line no-shadow
export interface Client {
/** Store the configuration */
config: {
/** Bot version */
version: string;
/** Bot token from env variable */
token_discord: string | undefined;
/** Default lang used */
default_lang: string;
};
/** Store all the modals */
modals: {
categories: Collection<
/** Category name */
string,
/** Name of the modals in the category */
string[]
>;
list: Collection<
/** Modal name */
string,
/** Modal itself */
{
/** Data about the modal */
data: {
name: string;
};
/** How the modal interact */
interaction: (interaction: ModalSubmitInteraction, client: Client) => unknown;
}
>;
};
/** Store all the buttons */
buttons: {
categories: Collection<
/** Category name */
string,
/** Name of the buttons in the category */
string[]
>;
list: Collection<
/** Button name */
string,
/** Button itself */
{
/** Data about the button */
data: {
name: string;
};
/** How the button interact */
interaction: (
interaction: MessageComponentInteraction,
client: Client
) => Promise<InteractionUpdateOptions>;
}
>;
};
/** Store all the slash commands */
commands: {
categories: Collection<
/** Category name */
string,
/** Name of the commands in the category */
string[]
>;
list: Collection<
/** Command name */
string,
/** Command itself */
{
/** Data about the command */
data: SlashCommandBuilder;
/** How the command interact */
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 {};
declare global {
// Declarations
interface String {
/**
* Returns a copy of the string with the first letter capitalized.
*/
capitalize(): string,
}
// Declarations
interface String {
/**
* Returns a copy of the string with the first letter capitalized.
*/
capitalize(): string;
}
}
/** Capitalize definition */
String.prototype.capitalize = function(this: string) {
return this[0].toUpperCase() + this.substring(1);
String.prototype.capitalize = function (this: string) {
return this[0].toUpperCase() + this.substring(1);
};

View file

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

View file

@ -1,6 +1,6 @@
import { Client } from 'discord.js';
import { readdir } from 'fs/promises';
import { removeExtension } from './misc';
import { Client } from "discord.js";
import { readdir } from "fs/promises";
import { removeExtension } from "./misc";
/**
* Load the localizations files into memory.
@ -10,40 +10,39 @@ import { removeExtension } from './misc';
* @returns Map of map with all the localizations
*/
export const loadLocales = async (default_lang: string) => {
// Get files from locales/ directory
const old_path = __dirname.split('/');
old_path.pop();
const files = await readdir(`${old_path.join('/')}/locales`);
// Get files from locales/ directory
const old_path = __dirname.split("/");
old_path.pop();
const files = await readdir(`${old_path.join("/")}/locales`);
// Read JSON files content and load it into the memory
const locales = new Map<string, Map<string, string>>();
await Promise.all(
files.map(async lang => {
// Import file
const content: {
[key: string]: string
} = await import(
`../locales/${lang}`
);
// Read JSON files content and load it into the memory
const locales = new Map<string, Map<string, string>>();
await Promise.all(
files.map(async (lang) => {
// Import file
const content: {
[key: string]: string;
} = await import(`../locales/${lang}`);
// Add it to the memory
locales.set(
removeExtension(lang),
new Map(Object.keys(content)
// Ignore the default key
.filter(str => str !== 'default')
.map(str => {
return [str, content[str]];
}),
)
);
})
);
// Add it to the memory
locales.set(
removeExtension(lang),
new Map(
Object.keys(content)
// Ignore the default key
.filter((str) => str !== "default")
.map((str) => {
return [str, content[str]];
})
)
);
})
);
// Check locales sanity
checkLocales(locales, default_lang);
// Check locales sanity
checkLocales(locales, default_lang);
return locales;
return locales;
};
/**
@ -54,25 +53,24 @@ export const loadLocales = async (default_lang: string) => {
* @returns the dictionary
*/
export const getLocalizations = (client: Client, text: string, lowercase = false) => {
const data: Record<string, string> = {};
const data: Record<string, string> = {};
// Load all the localizations
client.locales.forEach((locale, lang) => {
// Fetch the text and fallback to default lang if needed
// See getLocale for more info on why we *can* fallback
let str = locale.get(text)
?? client.locales.get(client.config.default_lang)?.get(text);
// Load all the localizations
client.locales.forEach((locale, lang) => {
// Fetch the text and fallback to default lang if needed
// See getLocale for more info on why we *can* fallback
let str = locale.get(text) ?? client.locales.get(client.config.default_lang)?.get(text);
// Store it if defined
if (str !== undefined) {
if (lowercase) {
str = str.toLowerCase();
}
data[lang] = str;
}
});
// Store it if defined
if (str !== undefined) {
if (lowercase) {
str = str.toLowerCase();
}
data[lang] = str;
}
});
return data;
return data;
};
/**
@ -83,22 +81,22 @@ export const getLocalizations = (client: Client, text: string, lowercase = false
* @returns the map with the desired languaged clogged with the default one
*/
export const getLocale = (client: Client, lang: string) => {
// Load default lang
const default_locales = client.locales.get(client.config.default_lang);
// Load desired lang
const desired_locales = client.locales.get(lang);
// Load default lang
const default_locales = client.locales.get(client.config.default_lang);
// Load desired lang
const desired_locales = client.locales.get(lang);
// Get text and fallback to default lang if needed
//
// We can fallback to the default one without any problem
// because we make sure that the default language always contains
// the desired text, and that the other languages are only translations
const locales = new Map();
default_locales?.forEach((_, key) => {
locales.set(key, desired_locales?.get(key) ?? default_locales.get(key));
});
// Get text and fallback to default lang if needed
//
// We can fallback to the default one without any problem
// because we make sure that the default language always contains
// the desired text, and that the other languages are only translations
const locales = new Map();
default_locales?.forEach((_, key) => {
locales.set(key, desired_locales?.get(key) ?? default_locales.get(key));
});
return locales;
return locales;
};
/**
@ -110,65 +108,65 @@ export const getLocale = (client: Client, lang: string) => {
* @param default_lang default lang
* @returns void
*/
const checkLocales =
async (locales: Map<string, Map<string, string>>, default_lang: string) => {
// Associate each lang with the number of locale it has
let locales_size = new Map<string, number>();
locales.forEach((locales_data, lang) => {
locales_size.set(lang, locales_data.size);
});
const checkLocales = async (locales: Map<string, Map<string, string>>, default_lang: string) => {
// Associate each lang with the number of locale it has
let locales_size = new Map<string, number>();
locales.forEach((locales_data, lang) => {
locales_size.set(lang, locales_data.size);
});
// Sort the map
locales_size = new Map([...locales_size.entries()]
.sort((a, b) => b[1] - a[1]));
// Sort the map
locales_size = new Map([...locales_size.entries()].sort((a, b) => b[1] - a[1]));
// Check if default lang is 100%
const [max_size_name] = locales_size.keys();
const [max_size] = locales_size.values();
const default_lang_size = locales_size.get(default_lang) ?? 0;
if (max_size > default_lang_size) {
// Throw error because in this case we are sure than the security
// explained in getLocale isn't true.
// However, it is possible that this condition is true
// and the security is poor, but it's better than nothing.
throw new Error(
`The default locale (${default_lang} = ${default_lang_size}) isn't complete `
+ `(${max_size_name} = ${max_size}).`
);
}
// Check if default lang is 100%
const [max_size_name] = locales_size.keys();
const [max_size] = locales_size.values();
const default_lang_size = locales_size.get(default_lang) ?? 0;
if (max_size > default_lang_size) {
// Throw error because in this case we are sure than the security
// explained in getLocale isn't true.
// However, it is possible that this condition is true
// and the security is poor, but it's better than nothing.
throw new Error(
`The default locale (${default_lang} = ${default_lang_size}) isn't complete ` +
`(${max_size_name} = ${max_size}).`
);
}
// Remove the default language as it is used as a reference
locales_size.delete(default_lang);
// Remove the default language as it is used as a reference
locales_size.delete(default_lang);
// Displays the percentages according to the default language
// lower is bigger
const bar_size = 4;
locales_size.forEach((size, lang) => {
const percentage = (size / max_size) * 100;
// Colored bar part
const blocks = ' '.repeat(Math.floor(percentage / bar_size));
// Blank bar part
const blank = ' '.repeat(Math.ceil((100 - percentage) / bar_size));
const color = () => {
switch (true) {
case percentage <= 25:
// Red
return '\x1b[41m';
case percentage <= 50:
// Mangeta
return '\x1b[45m';
case percentage <= 75:
// Cyan
return '\x1b[46m';
case percentage <= 100:
// Green
return '\x1b[42m';
default:
return '';
}
};
const padding = ' '.repeat(lang.length === 5 ? 1 : 4);
// Displays the percentages according to the default language
// lower is bigger
const bar_size = 4;
locales_size.forEach((size, lang) => {
const percentage = (size / max_size) * 100;
// Colored bar part
const blocks = " ".repeat(Math.floor(percentage / bar_size));
// Blank bar part
const blank = " ".repeat(Math.ceil((100 - percentage) / bar_size));
const color = () => {
switch (true) {
case percentage <= 25:
// Red
return "\x1b[41m";
case percentage <= 50:
// Mangeta
return "\x1b[45m";
case percentage <= 75:
// Cyan
return "\x1b[46m";
case percentage <= 100:
// Green
return "\x1b[42m";
default:
return "";
}
};
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.
@ -7,8 +7,8 @@ import { GuildMember } from 'discord.js';
* @returns String
*/
export const logStart = (name: string, status: boolean) => {
// TODO Handle precision about the error if status is false
return `> ${name}\t${status === true ? '✅' : '❌'}`;
// TODO Handle precision about the error if status is false
return `> ${name}\t${status === true ? "✅" : "❌"}`;
};
/**
@ -17,15 +17,15 @@ export const logStart = (name: string, status: boolean) => {
* @returns string
*/
export const getFilename = (path: string) => {
const path_list = path.split('/');
const path_list = path.split("/");
// Check if filename exist
const filename_with_ext = path_list.pop();
if (filename_with_ext === undefined) {
throw new Error(`Filename error: don't exist in ${path}`);
}
// Check if filename exist
const filename_with_ext = path_list.pop();
if (filename_with_ext === undefined) {
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
*/
export const removeExtension = (filename: string) => {
const array = filename.split('.');
array.pop();
const array = filename.split(".");
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
*/
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
*/
export const isImage = (filename: string) => {
return Boolean(getExtension(filename)?.match(
/jpg|jpeg|png|webp|gif/
));
return Boolean(getExtension(filename)?.match(/jpg|jpeg|png|webp|gif/));
};
/**
@ -68,14 +66,14 @@ export const isImage = (filename: string) => {
* @returns string
*/
export const userWithNickname = (member: GuildMember) => {
if (!member) {
return undefined;
}
if (member.nickname) {
return `${member.nickname} (${member.user.tag})`;
} else {
return member.user.tag;
}
if (!member) {
return undefined;
}
if (member.nickname) {
return `${member.nickname} (${member.user.tag})`;
} else {
return member.user.tag;
}
};
/**
@ -84,20 +82,23 @@ export const userWithNickname = (member: GuildMember) => {
* @returns Formatted text
*/
export const cleanCodeBlock = (text: string) => {
text = `\`${text.trim()}\``;
text = `\`${text.trim()}\``;
// Keep mentions
text = text.replace(/(<@\d+>)/g, function(mention: string) {
return `\`${mention}\``;
});
// Keep mentions
text = text.replace(/(<@\d+>)/g, function (mention: string) {
return `\`${mention}\``;
});
// Keep links
text = text.replace(/(http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)/g, function(url: string) {
return `\`${url}\``;
});
// Keep links
text = text.replace(
/(http[s]?:\/\/(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)/g,
function (url: string) {
return `\`${url}\``;
}
);
// Fix issues
text = text.replace('``', '');
// Fix issues
text = text.replace("``", "");
return text;
return text;
};

View file

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

View file

@ -5,21 +5,17 @@
* @param date Date
* @returns String
*/
export const showDate = (
tz: string,
locale: Map<string, unknown>,
date: Date
) => {
return date.toLocaleString(tz).replace(' ', ` ${locale.get('u_time_at')} `);
export const showDate = (tz: string, locale: Map<string, unknown>, date: Date) => {
return date.toLocaleString(tz).replace(" ", ` ${locale.get("u_time_at")} `);
};
enum TimeSecond {
Year = 31536000,
Week = 604800,
Day = 86400,
Hour = 3600,
Minute = 60,
Second = 1
Year = 31536000,
Week = 604800,
Day = 86400,
Hour = 3600,
Minute = 60,
Second = 1,
}
/**
@ -28,18 +24,26 @@ enum TimeSecond {
* @returns time in seconds
*/
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;
Object.entries(data).forEach(([key, value]) => {
if (value) {
res += +value * TimeSecond[key as keyof typeof TimeSecond];
}
});
let res = 0;
Object.entries(data).forEach(([key, value]) => {
if (value) {
res += +value * TimeSecond[key as keyof typeof TimeSecond];
}
});
return res;
return res;
};
/**
@ -48,7 +52,7 @@ export const strToSeconds = (time: string) => {
* @returns Delta between the time and now
*/
export const timeDeltaToString = (time: number) => {
const now = Date.now();
// TODO adapt the output and not always parse the time as seconds
return `${strToSeconds(`${(now - time) / 1000}`)} secs`;
const now = Date.now();
// TODO adapt the output and not always parse the time as seconds
return `${strToSeconds(`${(now - time) / 1000}`)} secs`;
};

View file

@ -11,10 +11,10 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* 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": [
"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. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "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. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
// "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. */
// "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. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "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. */
/* JavaScript Support */
@ -51,7 +51,7 @@
// "emitDeclarationOnly": true, /* Only output d.ts files and not 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. */
"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. */
// "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. */
@ -73,12 +73,12 @@
/* Interop Constraints */
// "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. */
"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. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* 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. */
// "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. */
@ -100,10 +100,7 @@
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": [
"./**/*.ts",
"./src/locales/*.json"
]
"include": ["./**/*.ts", "./src/locales/*.json"]
}