fbp-language 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -2
- package/dist/cli.js +1 -1
- package/dist/compiler.js +125 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Puis, dans le `package.json` de ton projet :
|
|
|
23
23
|
"start": "fbp run lancer"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"fbp-language": "^1.1.
|
|
26
|
+
"fbp-language": "^1.1.6"
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
```
|
|
@@ -129,7 +129,7 @@ npm test
|
|
|
129
129
|
Les fichiers d'essai internes se trouvent dans `tests/fixtures`. Ils ne font pas partie du paquet publié.
|
|
130
130
|
|
|
131
131
|
|
|
132
|
-
## Informations Discord dynamiques (v1.1.
|
|
132
|
+
## Informations Discord dynamiques (v1.1.6)
|
|
133
133
|
|
|
134
134
|
Les champs d'un embed peuvent récupérer les informations accessibles au moment où la commande est utilisée. Exemple :
|
|
135
135
|
|
|
@@ -149,3 +149,54 @@ discordChampDynamique("serveur-info", "Avatar", "discordAvatar")
|
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
Sources disponibles : `discordServeur.nom`, `discordServeur.id`, `discordServeur.creation`, `discordMembres.nombre`, `discordProfil.nom`, `discordProfil.id`, `discordProfil.avatar`, `discordRoles.nombre`, `discordSalons.nombre`, `discordStatistiques.membres`, `discordStatistiques.roles`, `discordStatistiques.salons`, `discordLatence` et `discordAvatar`. `discordOptionUtilisateur(nomCommande, nomOption, description)` ajoute un sélecteur facultatif de membre à une commande embed. Les données accessibles dépendent des permissions et du cache Discord du bot.
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
## Options des commandes slash (v1.1.6)
|
|
155
|
+
|
|
156
|
+
Les commandes slash peuvent maintenant recevoir des options texte, nombre, entier ou booléen. Le dernier argument indique si l'option est obligatoire ; il est facultatif et vaut `false` par défaut.
|
|
157
|
+
|
|
158
|
+
```fbp
|
|
159
|
+
discordConnexion()
|
|
160
|
+
discordCommande("bonjour", "Salut {{prenom}} !")
|
|
161
|
+
discordOptionTexte("bonjour", "prenom", "Ton prénom", true)
|
|
162
|
+
discordOptionEntier("bonjour", "age", "Ton âge")
|
|
163
|
+
discordOptionNombre("bonjour", "note", "Une valeur décimale")
|
|
164
|
+
discordOptionBooleen("bonjour", "confirme", "Confirmer ?")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Dans une réponse texte, `{{prenom}}` est remplacé par la valeur saisie. Pour les embeds, les champs dynamiques peuvent lire une option avec `discordChampDynamique("commande", "Champ", "option.prenom")` (ou `discordOption.prenom`). Les options absentes ne sont pas remplacées et une option obligatoire inaccessible produit une réponse d'erreur.
|
|
168
|
+
|
|
169
|
+
Fonctions disponibles :
|
|
170
|
+
|
|
171
|
+
- `discordOptionTexte(commande, nom, description, obligatoire)`
|
|
172
|
+
- `discordOptionNombre(commande, nom, description, obligatoire)`
|
|
173
|
+
- `discordOptionEntier(commande, nom, description, obligatoire)`
|
|
174
|
+
- `discordOptionBooleen(commande, nom, description, obligatoire)`
|
|
175
|
+
- `discordOptionUtilisateur(commande, nom, description)` pour le sélecteur de membre existant des commandes embed.
|
|
176
|
+
|
|
177
|
+
Les noms d'options doivent respecter les règles Discord (minuscules, chiffres, tirets et underscores, 1 à 32 caractères). Une commande peut avoir jusqu'à 25 options. Les types d'options sont déclarés lors de l'enregistrement des commandes ; redémarre le bot après avoir modifié leur définition. Le bot a besoin du scope `applications.commands` et de l'accès au serveur concerné. Aucun intent privilégié supplémentaire n'est requis pour ces options.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Choix prédéfinis des options (v1.1.6)
|
|
181
|
+
|
|
182
|
+
Pour limiter les réponses possibles d'une option, ajoute des choix après avoir déclaré l'option :
|
|
183
|
+
|
|
184
|
+
```fbp
|
|
185
|
+
discordConnexion()
|
|
186
|
+
discordCommande("couleur", "Couleur choisie : {{couleur}}")
|
|
187
|
+
discordOptionTexte("couleur", "couleur", "Choisis une couleur", true)
|
|
188
|
+
discordChoixTexte("couleur", "couleur", "Bleu", "bleu")
|
|
189
|
+
discordChoixTexte("couleur", "couleur", "Rouge", "rouge")
|
|
190
|
+
|
|
191
|
+
discordCommande("difficulte", "Niveau choisi : {{niveau}}")
|
|
192
|
+
discordOptionEntier("difficulte", "niveau", "Choisis un niveau", true)
|
|
193
|
+
discordChoixEntier("difficulte", "niveau", "Facile", 1)
|
|
194
|
+
discordChoixEntier("difficulte", "niveau", "Normal", 2)
|
|
195
|
+
discordChoixEntier("difficulte", "niveau", "Difficile", 3)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- `discordChoixTexte(commande, option, nomChoix, valeur)` ajoute un choix à une option texte.
|
|
199
|
+
- `discordChoixEntier(commande, option, nomChoix, valeur)` ajoute un choix à une option entière.
|
|
200
|
+
- Déclare toujours l'option avant ses choix.
|
|
201
|
+
- Une option peut avoir jusqu'à 25 choix. Les noms de choix sont limités à 100 caractères.
|
|
202
|
+
- Les choix sont présentés par Discord sous forme de liste et la valeur choisie est récupérable comme les autres options.
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const compiler_1 = require("./compiler");
|
|
10
10
|
const errors_1 = require("./errors");
|
|
11
|
-
const VERSION = "1.1.
|
|
11
|
+
const VERSION = "1.1.6";
|
|
12
12
|
function help() {
|
|
13
13
|
console.log(`FBP — French Bot Programming
|
|
14
14
|
|
package/dist/compiler.js
CHANGED
|
@@ -112,7 +112,7 @@ class FbpCompiler {
|
|
|
112
112
|
constructor(options = {}) {
|
|
113
113
|
this.output = options.onOutput ?? (value => console.log(value));
|
|
114
114
|
}
|
|
115
|
-
resolveDiscordValue(source, interaction, selectedUser, selectedMember) {
|
|
115
|
+
resolveDiscordValue(source, interaction, selectedUser, selectedMember, optionValues) {
|
|
116
116
|
const guild = interaction.guild;
|
|
117
117
|
const values = {
|
|
118
118
|
"discordServeur.nom": guild?.name ?? "Serveur indisponible",
|
|
@@ -130,6 +130,9 @@ class FbpCompiler {
|
|
|
130
130
|
"discordLatence": String(Math.max(0, Math.round(interaction.client.ws.ping))) + " ms",
|
|
131
131
|
"discordAvatar": selectedUser?.displayAvatarURL({ size: 256 }) ?? interaction.user.displayAvatarURL({ size: 256 })
|
|
132
132
|
};
|
|
133
|
+
const optionMatch = source.trim().match(/^(?:discordOption|option)\.([a-z0-9_-]{1,32})$/);
|
|
134
|
+
if (optionMatch)
|
|
135
|
+
return optionValues.get(optionMatch[1]) ?? "Option indisponible : " + optionMatch[1];
|
|
133
136
|
return source.trim() in values ? values[source.trim()] : "Source inconnue : " + source.trim();
|
|
134
137
|
}
|
|
135
138
|
runFile(filePath) {
|
|
@@ -217,6 +220,33 @@ class FbpCompiler {
|
|
|
217
220
|
.setDescription(response.userOption.description)
|
|
218
221
|
.setRequired(false));
|
|
219
222
|
}
|
|
223
|
+
for (const option of response.options ?? []) {
|
|
224
|
+
const add = {
|
|
225
|
+
texte: () => builder.addStringOption(item => {
|
|
226
|
+
item.setName(option.name).setDescription(option.description).setRequired(option.required);
|
|
227
|
+
for (const choice of option.choices ?? [])
|
|
228
|
+
if (typeof choice.value === "string")
|
|
229
|
+
item.addChoices({ name: choice.name, value: choice.value });
|
|
230
|
+
return item;
|
|
231
|
+
}),
|
|
232
|
+
nombre: () => builder.addNumberOption(item => {
|
|
233
|
+
item.setName(option.name).setDescription(option.description).setRequired(option.required);
|
|
234
|
+
for (const choice of option.choices ?? [])
|
|
235
|
+
if (typeof choice.value === "number")
|
|
236
|
+
item.addChoices({ name: choice.name, value: choice.value });
|
|
237
|
+
return item;
|
|
238
|
+
}),
|
|
239
|
+
entier: () => builder.addIntegerOption(item => {
|
|
240
|
+
item.setName(option.name).setDescription(option.description).setRequired(option.required);
|
|
241
|
+
for (const choice of option.choices ?? [])
|
|
242
|
+
if (typeof choice.value === "number")
|
|
243
|
+
item.addChoices({ name: choice.name, value: choice.value });
|
|
244
|
+
return item;
|
|
245
|
+
}),
|
|
246
|
+
booleen: () => builder.addBooleanOption(item => item.setName(option.name).setDescription(option.description).setRequired(option.required))
|
|
247
|
+
}[option.type];
|
|
248
|
+
add();
|
|
249
|
+
}
|
|
220
250
|
return builder.toJSON();
|
|
221
251
|
});
|
|
222
252
|
const manager = client.application?.commands;
|
|
@@ -244,8 +274,33 @@ class FbpCompiler {
|
|
|
244
274
|
if (response === undefined)
|
|
245
275
|
return;
|
|
246
276
|
try {
|
|
277
|
+
const optionValues = new Map();
|
|
278
|
+
for (const option of response.options ?? []) {
|
|
279
|
+
let value = null;
|
|
280
|
+
if (option.type === "texte")
|
|
281
|
+
value = interaction.options.getString(option.name);
|
|
282
|
+
else if (option.type === "nombre") {
|
|
283
|
+
const number = interaction.options.getNumber(option.name);
|
|
284
|
+
value = number === null ? null : String(number);
|
|
285
|
+
}
|
|
286
|
+
else if (option.type === "entier") {
|
|
287
|
+
const number = interaction.options.getInteger(option.name);
|
|
288
|
+
value = number === null ? null : String(number);
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
const boolean = interaction.options.getBoolean(option.name);
|
|
292
|
+
value = boolean === null ? null : String(boolean);
|
|
293
|
+
}
|
|
294
|
+
if (value === null && option.required) {
|
|
295
|
+
await interaction.reply({ content: "Option obligatoire indisponible : " + option.name, ephemeral: true });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (value !== null)
|
|
299
|
+
optionValues.set(option.name, value);
|
|
300
|
+
}
|
|
247
301
|
if (response.type === "text") {
|
|
248
|
-
|
|
302
|
+
const content = response.content.replace(/\{\{([a-z0-9_-]{1,32})\}\}/g, (match, name) => optionValues.has(name) ? optionValues.get(name) : match);
|
|
303
|
+
await interaction.reply(content);
|
|
249
304
|
}
|
|
250
305
|
else {
|
|
251
306
|
const embed = new discord_js_1.EmbedBuilder()
|
|
@@ -255,9 +310,11 @@ class FbpCompiler {
|
|
|
255
310
|
const guild = interaction.guild;
|
|
256
311
|
const selectedUser = response.userOption ? interaction.options.getUser(response.userOption.name) : null;
|
|
257
312
|
const selectedMember = selectedUser && guild ? await guild.members.fetch(selectedUser.id).catch(() => null) : null;
|
|
313
|
+
if (response.userOption && selectedUser)
|
|
314
|
+
optionValues.set(response.userOption.name, selectedMember?.displayName ?? selectedUser.username);
|
|
258
315
|
const fields = response.fields.map(field => ({
|
|
259
316
|
name: field.name,
|
|
260
|
-
value: field.dynamic ? this.resolveDiscordValue(field.value, interaction, selectedUser, selectedMember) : field.value,
|
|
317
|
+
value: field.dynamic ? this.resolveDiscordValue(field.value, interaction, selectedUser, selectedMember, optionValues) : field.value.replace(/\{\{([a-z0-9_-]{1,32})\}\}/g, (match, name) => optionValues.get(name) ?? match),
|
|
261
318
|
inline: false
|
|
262
319
|
}));
|
|
263
320
|
if (fields.length)
|
|
@@ -266,7 +323,11 @@ class FbpCompiler {
|
|
|
266
323
|
}
|
|
267
324
|
}
|
|
268
325
|
catch (error) {
|
|
326
|
+
const message = "Une erreur est survenue pendant l'exécution de cette commande.";
|
|
269
327
|
this.output("Erreur Discord : " + (error instanceof Error ? error.message : String(error)));
|
|
328
|
+
if (!interaction.replied && !interaction.deferred) {
|
|
329
|
+
await interaction.reply({ content: message, ephemeral: true }).catch(() => undefined);
|
|
330
|
+
}
|
|
270
331
|
}
|
|
271
332
|
});
|
|
272
333
|
void client.login(token).catch((error) => this.output("Connexion Discord impossible : " + (error instanceof Error ? error.message : String(error))));
|
|
@@ -282,7 +343,7 @@ class FbpCompiler {
|
|
|
282
343
|
if (!/^[a-z0-9_-]{1,32}$/.test(commandName)) {
|
|
283
344
|
throw new errors_1.FbpError("Le nom d'une commande slash doit contenir 1 à 32 caractères : lettres minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
284
345
|
}
|
|
285
|
-
this.discordCommands.set(commandName, { type: "text", content: values[1] });
|
|
346
|
+
this.discordCommands.set(commandName, { type: "text", content: values[1], options: [] });
|
|
286
347
|
return;
|
|
287
348
|
}
|
|
288
349
|
if (name === "discordCommandeEmbed") {
|
|
@@ -296,7 +357,7 @@ class FbpCompiler {
|
|
|
296
357
|
throw new errors_1.FbpError("Le nom d'une commande slash doit contenir 1 à 32 caractères : lettres minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
297
358
|
if (!/^#[0-9a-fA-F]{6}$/.test(color))
|
|
298
359
|
throw new errors_1.FbpError("La couleur d'un embed doit être au format hexadécimal, par exemple #5865F2.", undefined, line);
|
|
299
|
-
this.discordCommands.set(commandName, { type: "embed", title, description, color, fields: [] });
|
|
360
|
+
this.discordCommands.set(commandName, { type: "embed", title, description, color, fields: [], options: [] });
|
|
300
361
|
return;
|
|
301
362
|
}
|
|
302
363
|
if (name === "discordChampDynamique") {
|
|
@@ -314,6 +375,60 @@ class FbpCompiler {
|
|
|
314
375
|
command.fields.push({ name: fieldName, value: source, dynamic: true });
|
|
315
376
|
return;
|
|
316
377
|
}
|
|
378
|
+
if (name === "discordChoixTexte" || name === "discordChoixEntier") {
|
|
379
|
+
if (values.length !== 4 || values.some(value => typeof value !== "string" && typeof value !== "number")) {
|
|
380
|
+
throw new errors_1.FbpError("Utilisation : discordChoixTexte(nomCommande, nomOption, nomChoix, valeur) ou discordChoixEntier(...).", undefined, line);
|
|
381
|
+
}
|
|
382
|
+
const [commandName, optionName, choiceName, choiceValue] = values;
|
|
383
|
+
if (typeof commandName !== "string" || typeof optionName !== "string" || typeof choiceName !== "string") {
|
|
384
|
+
throw new errors_1.FbpError("Le nom de la commande, de l'option et du choix doivent être du texte.", undefined, line);
|
|
385
|
+
}
|
|
386
|
+
const command = this.discordCommands.get(commandName);
|
|
387
|
+
if (!command)
|
|
388
|
+
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
389
|
+
const option = (command.options ?? []).find(item => item.name === optionName);
|
|
390
|
+
const expectedType = name === "discordChoixTexte" ? "texte" : "entier";
|
|
391
|
+
if (!option || option.type !== expectedType)
|
|
392
|
+
throw new errors_1.FbpError("L'option « " + optionName + " » n'existe pas ou n'a pas le bon type.", undefined, line);
|
|
393
|
+
if (!choiceName.trim() || choiceName.length > 100)
|
|
394
|
+
throw new errors_1.FbpError("Le nom d'un choix doit contenir de 1 à 100 caractères.", undefined, line);
|
|
395
|
+
if (expectedType === "texte" && typeof choiceValue !== "string")
|
|
396
|
+
throw new errors_1.FbpError("La valeur d'un choix texte doit être du texte.", undefined, line);
|
|
397
|
+
if (expectedType === "entier" && (typeof choiceValue !== "number" || !Number.isInteger(choiceValue)))
|
|
398
|
+
throw new errors_1.FbpError("La valeur d'un choix entier doit être un nombre entier.", undefined, line);
|
|
399
|
+
const choices = option.choices ?? (option.choices = []);
|
|
400
|
+
if (choices.some(choice => choice.name === choiceName || choice.value === choiceValue))
|
|
401
|
+
throw new errors_1.FbpError("Ce nom ou cette valeur de choix existe déjà.", undefined, line);
|
|
402
|
+
if (choices.length >= 25)
|
|
403
|
+
throw new errors_1.FbpError("Une option Discord ne peut pas contenir plus de 25 choix.", undefined, line);
|
|
404
|
+
choices.push({ name: choiceName, value: choiceValue });
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (name === "discordOptionTexte" || name === "discordOptionNombre" || name === "discordOptionEntier" || name === "discordOptionBooleen") {
|
|
408
|
+
if (values.length < 3 || values.length > 4 || values.some(value => typeof value !== "string" && typeof value !== "boolean")) {
|
|
409
|
+
throw new errors_1.FbpError("Utilisation : discordOptionTexte/Nombre/Entier/Booleen(nomCommande, nomOption, description, obligatoire).", undefined, line);
|
|
410
|
+
}
|
|
411
|
+
const [commandName, optionName, description] = values;
|
|
412
|
+
const required = values.length === 4 ? values[3] : false;
|
|
413
|
+
if (typeof commandName !== "string" || typeof optionName !== "string" || typeof description !== "string" || typeof required !== "boolean") {
|
|
414
|
+
throw new errors_1.FbpError("Les trois premiers arguments doivent être du texte et le quatrième, facultatif, doit être true ou false.", undefined, line);
|
|
415
|
+
}
|
|
416
|
+
const command = this.discordCommands.get(commandName);
|
|
417
|
+
if (!command)
|
|
418
|
+
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
419
|
+
if (!/^[a-z0-9_-]{1,32}$/.test(optionName))
|
|
420
|
+
throw new errors_1.FbpError("Le nom de l'option doit contenir 1 à 32 caractères minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
421
|
+
if (!description.trim() || description.length > 100)
|
|
422
|
+
throw new errors_1.FbpError("La description de l'option doit contenir 1 à 100 caractères.", undefined, line);
|
|
423
|
+
const options = command.options ?? (command.options = []);
|
|
424
|
+
if (options.some(option => option.name === optionName) || (command.type === "embed" && command.userOption?.name === optionName))
|
|
425
|
+
throw new errors_1.FbpError("L'option « " + optionName + " » existe déjà pour cette commande.", undefined, line);
|
|
426
|
+
if (options.length + (command.type === "embed" && command.userOption ? 1 : 0) >= 25)
|
|
427
|
+
throw new errors_1.FbpError("Une commande slash Discord ne peut pas contenir plus de 25 options.", undefined, line);
|
|
428
|
+
const type = name === "discordOptionTexte" ? "texte" : name === "discordOptionNombre" ? "nombre" : name === "discordOptionEntier" ? "entier" : "booleen";
|
|
429
|
+
options.push({ name: optionName, description, type, required });
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
317
432
|
if (name === "discordOptionUtilisateur") {
|
|
318
433
|
if (values.length !== 3 || values.some(value => typeof value !== "string")) {
|
|
319
434
|
throw new errors_1.FbpError("Utilisation : discordOptionUtilisateur(nomCommande, nomOption, description).", undefined, line);
|
|
@@ -326,6 +441,11 @@ class FbpCompiler {
|
|
|
326
441
|
throw new errors_1.FbpError("Le nom de l'option doit contenir 1 à 32 caractères minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
327
442
|
if (!description.trim() || description.length > 100)
|
|
328
443
|
throw new errors_1.FbpError("La description de l'option doit contenir 1 à 100 caractères.", undefined, line);
|
|
444
|
+
const options = command.options ?? (command.options = []);
|
|
445
|
+
if (options.some(option => option.name === optionName) || command.userOption?.name === optionName)
|
|
446
|
+
throw new errors_1.FbpError("L'option « " + optionName + " » existe déjà pour cette commande.", undefined, line);
|
|
447
|
+
if (options.length + 1 > 25)
|
|
448
|
+
throw new errors_1.FbpError("Une commande slash Discord ne peut pas contenir plus de 25 options.", undefined, line);
|
|
329
449
|
command.userOption = { name: optionName, description };
|
|
330
450
|
return;
|
|
331
451
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fbp-language",
|
|
3
3
|
"displayName": "FBP - French Bot Programming",
|
|
4
|
-
"description": "Langage FBP avec
|
|
5
|
-
"version": "1.1.
|
|
4
|
+
"description": "Langage FBP avec options slash Discord, choix prédéfinis et embeds dynamiques.",
|
|
5
|
+
"version": "1.1.6",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"main": "dist/compiler.js",
|
|
8
8
|
"bin": {
|