fbp-language 1.1.7 → 1.2.0
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/assets/fbp-logo.svg +8 -0
- package/extension.js +167 -0
- package/fbp-language-1.2.0.vsix +0 -0
- package/language-configuration.json +21 -0
- package/package.json +32 -47
- package/syntaxes/fbp.tmLanguage.json +49 -0
- package/themes/fbp-icon-theme.json +11 -0
- package/README.md +0 -223
- package/dist/cli.js +0 -100
- package/dist/compiler.js +0 -508
- package/dist/errors.js +0 -24
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 128">
|
|
2
|
+
<title>FBP file icon</title>
|
|
3
|
+
<g fill="#718DB5" font-family="Arial, Helvetica, sans-serif" font-size="88" font-weight="600" text-anchor="middle">
|
|
4
|
+
<text x="48" y="94">{</text>
|
|
5
|
+
<text x="128" y="94">B</text>
|
|
6
|
+
<text x="208" y="94">}</text>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
package/extension.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const vscode = require("vscode");
|
|
2
|
+
|
|
3
|
+
const builtins = [
|
|
4
|
+
["affiche", "Affiche une ou plusieurs valeurs dans le terminal."],
|
|
5
|
+
["ping", "Vérifie que le moteur FBP répond."],
|
|
6
|
+
["discordConnexion", "Connecte le bot à Discord."],
|
|
7
|
+
["discordCommande", "Déclare une commande slash avec une réponse texte."],
|
|
8
|
+
["discordCommandeEmbed", "Déclare une commande slash avec un embed."],
|
|
9
|
+
["discordChamp", "Ajoute un champ fixe à un embed."],
|
|
10
|
+
["discordChampDynamique", "Ajoute un champ alimenté par une source Discord."],
|
|
11
|
+
["discordOptionTexte", "Ajoute une option texte à une commande slash."],
|
|
12
|
+
["discordOptionNombre", "Ajoute une option numérique."],
|
|
13
|
+
["discordOptionEntier", "Ajoute une option entière."],
|
|
14
|
+
["discordOptionBooleen", "Ajoute une option vrai/faux."],
|
|
15
|
+
["discordOptionSalon", "Ajoute un sélecteur de salon."],
|
|
16
|
+
["discordOptionUtilisateur", "Ajoute un sélecteur de membre à un embed."],
|
|
17
|
+
["discordChoixTexte", "Ajoute un choix à une option texte."],
|
|
18
|
+
["discordChoixEntier", "Ajoute un choix à une option entière."],
|
|
19
|
+
["discordOptionNombreLimites", "Définit les limites d'une option numérique."],
|
|
20
|
+
["discordOptionEntierLimites", "Définit les limites d'une option entière."]
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const keywords = [
|
|
24
|
+
["import", "Importe des fonctions depuis un autre fichier FBP."],
|
|
25
|
+
["de", "Introduit le chemin d'un import."],
|
|
26
|
+
["true", "Valeur booléenne vraie."],
|
|
27
|
+
["false", "Valeur booléenne fausse."]
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function provideCompletions(document, position) {
|
|
31
|
+
const linePrefix = document.lineAt(position).text.slice(0, position.character);
|
|
32
|
+
const items = [];
|
|
33
|
+
for (const [name, detail] of builtins) {
|
|
34
|
+
const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Function);
|
|
35
|
+
item.detail = "Fonction FBP intégrée";
|
|
36
|
+
item.documentation = new vscode.MarkdownString(detail);
|
|
37
|
+
item.insertText = new vscode.SnippetString(name + "($0)");
|
|
38
|
+
item.sortText = "0-" + name;
|
|
39
|
+
items.push(item);
|
|
40
|
+
}
|
|
41
|
+
for (const [name, detail] of keywords) {
|
|
42
|
+
const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Keyword);
|
|
43
|
+
item.detail = "Mot-clé FBP";
|
|
44
|
+
item.documentation = detail;
|
|
45
|
+
item.sortText = "1-" + name;
|
|
46
|
+
items.push(item);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const declaredFunctions = /(?:^|\n)\s*([\p{L}_][\p{L}\p{N}_]*)\s*\(\s*\)\s*\{/gu;
|
|
50
|
+
for (const match of document.getText().matchAll(declaredFunctions)) {
|
|
51
|
+
const name = match[1];
|
|
52
|
+
if (builtins.some(([builtin]) => builtin === name)) continue;
|
|
53
|
+
const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Function);
|
|
54
|
+
item.detail = "Fonction définie dans ce fichier";
|
|
55
|
+
item.insertText = new vscode.SnippetString(name + "($0)");
|
|
56
|
+
items.push(item);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const variables = /(?:^|\n)\s*([\p{L}_][\p{L}\p{N}_]*)\s*=\s*[^=]/gu;
|
|
60
|
+
for (const match of document.getText().matchAll(variables)) {
|
|
61
|
+
const name = match[1];
|
|
62
|
+
const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Variable);
|
|
63
|
+
item.detail = "Variable FBP";
|
|
64
|
+
item.sortText = "2-" + name;
|
|
65
|
+
items.push(item);
|
|
66
|
+
}
|
|
67
|
+
return items;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateDocument(document, collection) {
|
|
71
|
+
if (document.languageId !== "fbp") return;
|
|
72
|
+
const diagnostics = [];
|
|
73
|
+
const lines = document.getText().split(/\r?\n/);
|
|
74
|
+
const stack = [];
|
|
75
|
+
const functionNames = new Set();
|
|
76
|
+
for (const raw of lines) {
|
|
77
|
+
const match = raw.trim().match(/^([\\p{L}_][\\p{L}\\p{N}_]*)\\s*\\(\\s*\\)\\s*\\{$/u);
|
|
78
|
+
if (match) functionNames.add(match[1]);
|
|
79
|
+
}
|
|
80
|
+
const builtInNames = new Set(builtins.map(([name]) => name));
|
|
81
|
+
const declaration = /^([\p{L}_][\p{L}\p{N}_]*)\s*\(\s*\)\s*\{$/u;
|
|
82
|
+
const assignment = /^([\p{L}_][\p{L}\p{N}_]*)\s*=\s*(.+)$/u;
|
|
83
|
+
const call = /^([\p{L}_][\p{L}\p{N}_]*)\s*\((.*)\)\s*;?$/u;
|
|
84
|
+
const importLine = /^import\s*\{\s*([^}]+)\s*\}\s*de\s*(["'])(.+?)\2\s*$/u;
|
|
85
|
+
const identifier = /^[\p{L}_][\p{L}\p{N}_]*$/u;
|
|
86
|
+
|
|
87
|
+
function add(lineIndex, text, message, severity = vscode.DiagnosticSeverity.Error) {
|
|
88
|
+
const start = Math.max(0, lines[lineIndex].indexOf(text));
|
|
89
|
+
const end = start + Math.max(1, text.length);
|
|
90
|
+
const diagnostic = new vscode.Diagnostic(
|
|
91
|
+
new vscode.Range(lineIndex, start, lineIndex, Math.min(lines[lineIndex].length, end)),
|
|
92
|
+
message,
|
|
93
|
+
severity
|
|
94
|
+
);
|
|
95
|
+
diagnostic.source = "FBP";
|
|
96
|
+
diagnostics.push(diagnostic);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
lines.forEach((raw, index) => {
|
|
100
|
+
const line = raw.trim();
|
|
101
|
+
if (!line || line.startsWith("//") || line.startsWith("#")) return;
|
|
102
|
+
if (importLine.test(line)) return;
|
|
103
|
+
|
|
104
|
+
const fn = line.match(declaration);
|
|
105
|
+
if (fn) {
|
|
106
|
+
if (functionNames.has(fn[1])) add(index, fn[1], "Cette fonction est déjà définie.");
|
|
107
|
+
functionNames.add(fn[1]);
|
|
108
|
+
stack.push({ name: fn[1], line: index });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (line === "}") {
|
|
113
|
+
if (!stack.length) add(index, "}", "Accolade fermante sans fonction correspondante.");
|
|
114
|
+
else stack.pop();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const assign = line.match(assignment);
|
|
119
|
+
if (assign) {
|
|
120
|
+
if (!assign[2].trim()) add(index, assign[1], "Une valeur est attendue après le signe =.");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const invocation = line.match(call);
|
|
125
|
+
if (invocation) {
|
|
126
|
+
if (!builtInNames.has(invocation[1]) && !functionNames.has(invocation[1])) {
|
|
127
|
+
add(index, invocation[1], "Fonction inconnue ou non déclarée : " + invocation[1] + ".", vscode.DiagnosticSeverity.Warning);
|
|
128
|
+
}
|
|
129
|
+
if (invocation[2].trim() && /(^|,)\s*(,|$)/.test(invocation[2])) {
|
|
130
|
+
add(index, invocation[2], "Argument vide dans l'appel de fonction.");
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
add(index, line, "Syntaxe FBP non reconnue. Vérifie les parenthèses, les accolades et le signe =.");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
for (const open of stack) {
|
|
139
|
+
add(open.line, lines[open.line].trim(), "La fonction « " + open.name + " » n'est pas fermée par }.");
|
|
140
|
+
}
|
|
141
|
+
collection.set(document.uri, diagnostics);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function activate(context) {
|
|
145
|
+
const diagnostics = vscode.languages.createDiagnosticCollection("fbp");
|
|
146
|
+
context.subscriptions.push(diagnostics);
|
|
147
|
+
|
|
148
|
+
context.subscriptions.push(
|
|
149
|
+
vscode.languages.registerCompletionItemProvider(
|
|
150
|
+
{ language: "fbp", scheme: "file" },
|
|
151
|
+
{ provideCompletionItems: provideCompletions },
|
|
152
|
+
".", " "
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
context.subscriptions.push(
|
|
157
|
+
vscode.workspace.onDidOpenTextDocument(document => validateDocument(document, diagnostics)),
|
|
158
|
+
vscode.workspace.onDidChangeTextDocument(event => validateDocument(event.document, diagnostics)),
|
|
159
|
+
vscode.workspace.onDidCloseTextDocument(document => diagnostics.delete(document.uri))
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
for (const document of vscode.workspace.textDocuments) validateDocument(document, diagnostics);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function deactivate() {}
|
|
166
|
+
|
|
167
|
+
module.exports = { activate, deactivate };
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"brackets": [
|
|
3
|
+
["{", "}"],
|
|
4
|
+
["(", ")"],
|
|
5
|
+
["[", "]"]
|
|
6
|
+
],
|
|
7
|
+
"autoClosingPairs": [
|
|
8
|
+
["{", "}"],
|
|
9
|
+
["(", ")"],
|
|
10
|
+
["[", "]"],
|
|
11
|
+
["\"", "\""],
|
|
12
|
+
["'", "'"]
|
|
13
|
+
],
|
|
14
|
+
"surroundingPairs": [
|
|
15
|
+
["{", "}"],
|
|
16
|
+
["(", ")"],
|
|
17
|
+
["[", "]"],
|
|
18
|
+
["\"", "\""],
|
|
19
|
+
["'", "'"]
|
|
20
|
+
]
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,53 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fbp-language",
|
|
3
|
-
"displayName": "FBP
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "1.
|
|
6
|
-
"
|
|
7
|
-
"main": "dist/compiler.js",
|
|
8
|
-
"bin": {
|
|
9
|
-
"fbp": "dist/cli.js"
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
"dist/",
|
|
13
|
-
"README.md"
|
|
14
|
-
],
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "https://github.com/GabyY-1/French-Bot-Programming.git"
|
|
18
|
-
},
|
|
19
|
-
"homepage": "https://github.com/GabyY-1/French-Bot-Programming#readme",
|
|
20
|
-
"bugs": {
|
|
21
|
-
"url": "https://github.com/GabyY-1/French-Bot-Programming/issues"
|
|
22
|
-
},
|
|
23
|
-
"keywords": [
|
|
24
|
-
"fbp",
|
|
25
|
-
"language",
|
|
26
|
-
"interpreter",
|
|
27
|
-
"french",
|
|
28
|
-
"cli",
|
|
29
|
-
"discord",
|
|
30
|
-
"slash-commands",
|
|
31
|
-
"bot"
|
|
32
|
-
],
|
|
33
|
-
"scripts": {
|
|
34
|
-
"build": "tsc -p tsconfig.json",
|
|
35
|
-
"prepare": "npm run build",
|
|
36
|
-
"prepublishOnly": "npm test",
|
|
37
|
-
"start": "node dist/cli.js",
|
|
38
|
-
"dev": "ts-node src/cli.ts",
|
|
39
|
-
"test": "npm run build && node --test tests/*.test.cjs"
|
|
40
|
-
},
|
|
3
|
+
"displayName": "FBP Language Support",
|
|
4
|
+
"description": "Coloration syntaxique, autocomplétion, détection des erreurs et thème d'icônes FBP.",
|
|
5
|
+
"version": "1.2.0",
|
|
6
|
+
"publisher": "gabyy1",
|
|
41
7
|
"engines": {
|
|
42
|
-
"
|
|
43
|
-
},
|
|
44
|
-
"devDependencies": {
|
|
45
|
-
"@types/node": "^22.10.0",
|
|
46
|
-
"ts-node": "^10.9.2",
|
|
47
|
-
"typescript": "^5.7.2"
|
|
8
|
+
"vscode": "^1.80.0"
|
|
48
9
|
},
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
10
|
+
"main": "./extension.js",
|
|
11
|
+
"categories": ["Programming Languages", "Themes"],
|
|
12
|
+
"extensionKind": ["workspace"],
|
|
13
|
+
"activationEvents": ["onLanguage:fbp"],
|
|
14
|
+
"contributes": {
|
|
15
|
+
"languages": [
|
|
16
|
+
{
|
|
17
|
+
"id": "fbp",
|
|
18
|
+
"aliases": ["FBP", "fbp"],
|
|
19
|
+
"extensions": [".fbp"],
|
|
20
|
+
"configuration": "./language-configuration.json"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"grammars": [
|
|
24
|
+
{
|
|
25
|
+
"language": "fbp",
|
|
26
|
+
"scopeName": "source.fbp",
|
|
27
|
+
"path": "./syntaxes/fbp.tmLanguage.json"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"iconThemes": [
|
|
31
|
+
{
|
|
32
|
+
"id": "fbp-file-icons",
|
|
33
|
+
"label": "FBP File Icons",
|
|
34
|
+
"path": "./themes/fbp-icon-theme.json"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
52
37
|
}
|
|
53
38
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scopeName": "source.fbp",
|
|
3
|
+
"name": "FBP",
|
|
4
|
+
"patterns": [
|
|
5
|
+
{ "include": "#comments" },
|
|
6
|
+
{ "include": "#strings" },
|
|
7
|
+
{ "include": "#numbers" },
|
|
8
|
+
{ "include": "#keywords" },
|
|
9
|
+
{ "include": "#booleans" },
|
|
10
|
+
{ "include": "#function-declarations" },
|
|
11
|
+
{ "include": "#function-calls" },
|
|
12
|
+
{ "include": "#assignments" },
|
|
13
|
+
{ "include": "#operators" },
|
|
14
|
+
{ "include": "#punctuation" }
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"comments": {
|
|
18
|
+
"patterns": [
|
|
19
|
+
{ "name": "comment.line.double-slash.fbp", "match": "//.*$" },
|
|
20
|
+
{ "name": "comment.line.number-sign.fbp", "match": "#.*$" }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"strings": {
|
|
24
|
+
"name": "string.quoted.double.fbp",
|
|
25
|
+
"begin": "\"",
|
|
26
|
+
"beginCaptures": { "0": { "name": "punctuation.definition.string.begin.fbp" } },
|
|
27
|
+
"end": "\"",
|
|
28
|
+
"endCaptures": { "0": { "name": "punctuation.definition.string.end.fbp" } },
|
|
29
|
+
"patterns": [{ "name": "constant.character.escape.fbp", "match": "\\\\." }]
|
|
30
|
+
},
|
|
31
|
+
"numbers": { "name": "constant.numeric.fbp", "match": "\\b\\d+(?:\\.\\d+)?\\b" },
|
|
32
|
+
"keywords": { "name": "keyword.control.fbp", "match": "\\b(?:import|de)\\b" },
|
|
33
|
+
"booleans": { "name": "constant.language.boolean.fbp", "match": "\\b(?:true|false)\\b" },
|
|
34
|
+
"function-declarations": {
|
|
35
|
+
"match": "\\b([\\p{L}_][\\p{L}\\p{N}_]*)\\s*(?=\\(\\s*\\)\\s*\\{)",
|
|
36
|
+
"captures": { "1": { "name": "entity.name.function.fbp" } }
|
|
37
|
+
},
|
|
38
|
+
"function-calls": {
|
|
39
|
+
"match": "\\b([\\p{L}_][\\p{L}\\p{N}_]*)\\s*(?=\\()",
|
|
40
|
+
"captures": { "1": { "name": "support.function.fbp" } }
|
|
41
|
+
},
|
|
42
|
+
"assignments": {
|
|
43
|
+
"match": "\\b([\\p{L}_][\\p{L}\\p{N}_]*)\\s*(?==)",
|
|
44
|
+
"captures": { "1": { "name": "variable.other.assignment.fbp" } }
|
|
45
|
+
},
|
|
46
|
+
"operators": { "name": "keyword.operator.fbp", "match": "(?:=|\\+|\\-|\\*|/|==|!=|<=|>=)" },
|
|
47
|
+
"punctuation": { "name": "punctuation.section.fbp", "match": "[{}()\\[\\],.]" }
|
|
48
|
+
}
|
|
49
|
+
}
|
package/README.md
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
# FBP — French Bot Programming
|
|
2
|
-
|
|
3
|
-
FBP est un langage interprété par Node.js. Les fichiers source utilisent l'extension `.fbp`. Le paquet fournit le moteur et la commande `fbp`.
|
|
4
|
-
|
|
5
|
-
## Prérequis
|
|
6
|
-
|
|
7
|
-
- Node.js 20 ou plus récent
|
|
8
|
-
- npm
|
|
9
|
-
|
|
10
|
-
## Installation depuis npm
|
|
11
|
-
|
|
12
|
-
Quand le paquet sera publié sur npm :
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
npm install fbp-language
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Puis, dans le `package.json` de ton projet :
|
|
19
|
-
|
|
20
|
-
```json
|
|
21
|
-
{
|
|
22
|
-
"scripts": {
|
|
23
|
-
"start": "fbp run lancer"
|
|
24
|
-
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"fbp-language": "^1.1.6"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Lance le script avec `npm start`. npm rend la commande `fbp` disponible dans les scripts du projet.
|
|
32
|
-
|
|
33
|
-
## Créer un projet FBP
|
|
34
|
-
|
|
35
|
-
Crée un fichier `commandes.fbp` à la racine de ton projet. Malgré son extension, ce fichier est actuellement du JSON :
|
|
36
|
-
|
|
37
|
-
```json
|
|
38
|
-
{
|
|
39
|
-
"lancer": "scripts/main.fbp"
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Crée ensuite le fichier `scripts/main.fbp` :
|
|
44
|
-
|
|
45
|
-
```fbp
|
|
46
|
-
nom = "Mon projet"
|
|
47
|
-
|
|
48
|
-
lance() {
|
|
49
|
-
affiche("FBP fonctionne !")
|
|
50
|
-
affiche("Projet :", nom)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
lance()
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Les chemins déclarés dans `commandes.fbp` sont relatifs à la racine du projet.
|
|
57
|
-
|
|
58
|
-
## Commandes CLI
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
fbp help
|
|
62
|
-
fbp version
|
|
63
|
-
fbp info
|
|
64
|
-
fbp ping
|
|
65
|
-
fbp run lancer
|
|
66
|
-
fbp run-file scripts/main.fbp
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
- `fbp run <nom>` exécute la commande correspondante dans le fichier `commandes.fbp` du dossier courant.
|
|
70
|
-
- `fbp run-file <fichier>` exécute directement un fichier FBP.
|
|
71
|
-
- `fbp ping` vérifie que le CLI répond ; il ne se connecte pas à Discord.
|
|
72
|
-
|
|
73
|
-
## Syntaxe actuelle
|
|
74
|
-
|
|
75
|
-
```fbp
|
|
76
|
-
nom = "Gab"
|
|
77
|
-
niveau = 15
|
|
78
|
-
actif = true
|
|
79
|
-
|
|
80
|
-
lance() {
|
|
81
|
-
affiche("Bonjour", nom)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
lance()
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
- Types reconnus : texte, nombre et booléen.
|
|
88
|
-
- Les fonctions ne prennent pas encore de paramètres.
|
|
89
|
-
- `affiche(...)` écrit dans le terminal.
|
|
90
|
-
- `import { fonction } de "./fichier.fbp"` importe des fonctions.
|
|
91
|
-
- Les commentaires commencent par `//` ou `#`.
|
|
92
|
-
|
|
93
|
-
## Créer un bot Discord
|
|
94
|
-
|
|
95
|
-
Installe FBP dans le projet du bot, puis crée un fichier `.env` à la racine :
|
|
96
|
-
|
|
97
|
-
```env
|
|
98
|
-
DISCORD_TOKEN=ton_token_discord
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Ne partage jamais ce fichier et ajoute `.env` à `.gitignore`.
|
|
102
|
-
|
|
103
|
-
Dans `scripts/main.fbp` :
|
|
104
|
-
|
|
105
|
-
```fbp
|
|
106
|
-
discordConnexion()
|
|
107
|
-
discordCommande("ping", "Pong !")
|
|
108
|
-
discordCommande("bonjour", "Salut !")
|
|
109
|
-
discordCommandeEmbed("serveur-info", "Informations du serveur", "Voici les informations disponibles.", "#5865F2")
|
|
110
|
-
discordChamp("serveur-info", "Nom", "Mon serveur")
|
|
111
|
-
discordChamp("serveur-info", "Membres", "42")
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Associe `lancer` à `scripts/main.fbp` dans `commandes.fbp`, puis exécute `npx fbp run lancer`. Dans le portail développeur Discord, vérifie que le bot est invité sur ton serveur avec les scopes `bot` et `applications.commands`. Seul l'intent serveur (`Guilds`) est nécessaire.
|
|
115
|
-
|
|
116
|
-
FBP enregistre les commandes déclarées comme commandes slash globales de l'application Discord. Leur apparition peut prendre un peu de temps après le premier enregistrement. Les noms doivent contenir de 1 à 32 caractères parmi les lettres minuscules, chiffres, tirets et underscores.
|
|
117
|
-
|
|
118
|
-
## Limites actuelles
|
|
119
|
-
|
|
120
|
-
FBP est encore un interpréteur de base. Les paramètres de fonction, les conditions, les boucles, les valeurs de retour et les expressions arithmétiques ne sont pas encore implémentés. La connexion Discord utilise un token stocké dans `.env`. Les commandes slash peuvent répondre avec un texte fixe ou un embed défini avec `discordCommandeEmbed(nom, titre, description, couleur)`. Ajoute des champs avec `discordChamp(nomCommande, nomChamp, valeur)`. Les valeurs des embeds sont actuellement fixes : la récupération dynamique des informations du serveur, les options, paramètres, permissions avancées et conditions ne sont pas encore implémentés.
|
|
121
|
-
|
|
122
|
-
## Développement du dépôt
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
npm install
|
|
126
|
-
npm test
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Les fichiers d'essai internes se trouvent dans `tests/fixtures`. Ils ne font pas partie du paquet publié.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
## Informations Discord dynamiques (v1.1.6)
|
|
133
|
-
|
|
134
|
-
Les champs d'un embed peuvent récupérer les informations accessibles au moment où la commande est utilisée. Exemple :
|
|
135
|
-
|
|
136
|
-
```fbp
|
|
137
|
-
discordConnexion()
|
|
138
|
-
discordCommandeEmbed("serveur-info", "Informations du serveur", "Données actuelles", "#5865F2")
|
|
139
|
-
discordChampDynamique("serveur-info", "Nom", "discordServeur.nom")
|
|
140
|
-
discordChampDynamique("serveur-info", "Identifiant", "discordServeur.id")
|
|
141
|
-
discordChampDynamique("serveur-info", "Création", "discordServeur.creation")
|
|
142
|
-
discordChampDynamique("serveur-info", "Membres", "discordMembres.nombre")
|
|
143
|
-
discordChampDynamique("serveur-info", "Rôles", "discordRoles.nombre")
|
|
144
|
-
discordChampDynamique("serveur-info", "Salons", "discordSalons.nombre")
|
|
145
|
-
discordChampDynamique("serveur-info", "Latence", "discordLatence")
|
|
146
|
-
discordOptionUtilisateur("serveur-info", "membre", "Membre à afficher")
|
|
147
|
-
discordChampDynamique("serveur-info", "Profil", "discordProfil.nom")
|
|
148
|
-
discordChampDynamique("serveur-info", "Avatar", "discordAvatar")
|
|
149
|
-
```
|
|
150
|
-
|
|
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.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
## Limites numériques des options (v1.1.7)
|
|
206
|
-
|
|
207
|
-
Les options slash de type nombre et entier peuvent imposer une valeur minimale et maximale. Déclare l'option d'abord, puis ses limites :
|
|
208
|
-
|
|
209
|
-
```fbp
|
|
210
|
-
discordConnexion()
|
|
211
|
-
discordCommande("age", "Âge sélectionné : {{age}}")
|
|
212
|
-
discordOptionEntier("age", "age", "Choisis un âge", true)
|
|
213
|
-
discordOptionEntierLimites("age", "age", 13, 99)
|
|
214
|
-
|
|
215
|
-
discordCommande("note", "Note sélectionnée : {{note}}")
|
|
216
|
-
discordOptionNombre("note", "note", "Choisis une note", true)
|
|
217
|
-
discordOptionNombreLimites("note", "note", 0, 20)
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
- `discordOptionEntierLimites(commande, option, minimum, maximum)` accepte uniquement des bornes entières.
|
|
221
|
-
- `discordOptionNombreLimites(commande, option, minimum, maximum)` accepte des bornes numériques, y compris décimales.
|
|
222
|
-
- Le minimum doit être inférieur ou égal au maximum.
|
|
223
|
-
- Les limites s'appliquent à l'interface Discord. Une option avec des choix prédéfinis ne peut pas recevoir de limites.
|
package/dist/cli.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
-
};
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const compiler_1 = require("./compiler");
|
|
10
|
-
const errors_1 = require("./errors");
|
|
11
|
-
const VERSION = "1.1.7";
|
|
12
|
-
function help() {
|
|
13
|
-
console.log(`FBP — French Bot Programming
|
|
14
|
-
|
|
15
|
-
Utilisation :
|
|
16
|
-
fbp help Affiche cette aide
|
|
17
|
-
fbp version Affiche la version de FBP
|
|
18
|
-
fbp info Affiche les informations du moteur
|
|
19
|
-
fbp ping Vérifie que le CLI répond
|
|
20
|
-
fbp run <commande> Exécute une commande de commandes.fbp
|
|
21
|
-
fbp run-file <fichier> Exécute directement un fichier .fbp`);
|
|
22
|
-
}
|
|
23
|
-
function ping() {
|
|
24
|
-
console.log("FBP : opérationnel");
|
|
25
|
-
console.log(`Version : ${VERSION}`);
|
|
26
|
-
console.log(`Node.js : ${process.version}`);
|
|
27
|
-
console.log("Bot : aucune connexion externe n'est configurée par le CLI seul.");
|
|
28
|
-
}
|
|
29
|
-
function runCommand(name) {
|
|
30
|
-
const configPath = node_path_1.default.resolve("commandes.fbp");
|
|
31
|
-
if (!node_fs_1.default.existsSync(configPath)) {
|
|
32
|
-
throw new errors_1.FbpError("Fichier commandes.fbp introuvable à la racine du projet. Crée ce fichier pour définir tes commandes.");
|
|
33
|
-
}
|
|
34
|
-
let commands;
|
|
35
|
-
try {
|
|
36
|
-
commands = JSON.parse(node_fs_1.default.readFileSync(configPath, "utf8"));
|
|
37
|
-
}
|
|
38
|
-
catch {
|
|
39
|
-
throw new errors_1.FbpError("commandes.fbp doit contenir un objet JSON valide associant chaque commande à un fichier .fbp.");
|
|
40
|
-
}
|
|
41
|
-
if (!commands || typeof commands !== "object" || Array.isArray(commands)) {
|
|
42
|
-
throw new errors_1.FbpError("commandes.fbp doit être un objet de commandes.");
|
|
43
|
-
}
|
|
44
|
-
const target = commands[name];
|
|
45
|
-
if (typeof target !== "string" || !target.trim()) {
|
|
46
|
-
const available = Object.keys(commands);
|
|
47
|
-
throw new errors_1.FbpError(`Commande « ${name} » inconnue.${available.length ? ` Commandes disponibles : ${available.join(", ")}.` : " Aucune commande n'est définie."}`);
|
|
48
|
-
}
|
|
49
|
-
new compiler_1.FbpCompiler().runFile(node_path_1.default.resolve(node_path_1.default.dirname(configPath), target));
|
|
50
|
-
}
|
|
51
|
-
function main() {
|
|
52
|
-
const [command, ...args] = process.argv.slice(2);
|
|
53
|
-
try {
|
|
54
|
-
switch (command) {
|
|
55
|
-
case "help":
|
|
56
|
-
case "--help":
|
|
57
|
-
case "-h":
|
|
58
|
-
case undefined:
|
|
59
|
-
help();
|
|
60
|
-
break;
|
|
61
|
-
case "version":
|
|
62
|
-
case "--version":
|
|
63
|
-
case "-v":
|
|
64
|
-
console.log(`FBP ${VERSION}`);
|
|
65
|
-
break;
|
|
66
|
-
case "info":
|
|
67
|
-
console.log("FBP — French Bot Programming");
|
|
68
|
-
console.log("Moteur : TypeScript / Node.js");
|
|
69
|
-
console.log("Fichiers source : .fbp");
|
|
70
|
-
break;
|
|
71
|
-
case "ping":
|
|
72
|
-
ping();
|
|
73
|
-
break;
|
|
74
|
-
case "run":
|
|
75
|
-
if (!args[0])
|
|
76
|
-
throw new errors_1.FbpError("Indique le nom de la commande. Exemple : fbp run lancer");
|
|
77
|
-
runCommand(args[0]);
|
|
78
|
-
break;
|
|
79
|
-
case "run-file":
|
|
80
|
-
if (!args[0])
|
|
81
|
-
throw new errors_1.FbpError("Indique un fichier. Exemple : fbp run-file bot/main.fbp");
|
|
82
|
-
new compiler_1.FbpCompiler().runFile(args[0]);
|
|
83
|
-
break;
|
|
84
|
-
default:
|
|
85
|
-
throw new errors_1.FbpError(`Commande CLI inconnue : « ${command} ». Utilise « fbp help » pour afficher l'aide.`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch (error) {
|
|
89
|
-
if (error instanceof errors_1.FbpError) {
|
|
90
|
-
console.error(error.format());
|
|
91
|
-
process.exitCode = 1;
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
console.error("Erreur FBP : une erreur inattendue est survenue.");
|
|
95
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
96
|
-
process.exitCode = 1;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
main();
|
package/dist/compiler.js
DELETED
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.FbpCompiler = void 0;
|
|
7
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
-
const errors_1 = require("./errors");
|
|
10
|
-
const discord_js_1 = require("discord.js");
|
|
11
|
-
const dotenv_1 = __importDefault(require("dotenv"));
|
|
12
|
-
function valueType(value) {
|
|
13
|
-
const v = value.trim();
|
|
14
|
-
if (/^"(?:[^"\\]|\\.)*"$/.test(v) || /^'(?:[^'\\]|\\.)*'$/.test(v))
|
|
15
|
-
return "texte";
|
|
16
|
-
if (/^-?\d+(?:\.\d+)?$/.test(v))
|
|
17
|
-
return "nombre";
|
|
18
|
-
if (v === "true" || v === "false")
|
|
19
|
-
return "booleen";
|
|
20
|
-
return "inconnu";
|
|
21
|
-
}
|
|
22
|
-
function splitArguments(raw) {
|
|
23
|
-
const result = [];
|
|
24
|
-
let current = "";
|
|
25
|
-
let quote = "";
|
|
26
|
-
let depth = 0;
|
|
27
|
-
for (const char of raw) {
|
|
28
|
-
if (quote) {
|
|
29
|
-
current += char;
|
|
30
|
-
if (char === quote)
|
|
31
|
-
quote = "";
|
|
32
|
-
}
|
|
33
|
-
else if (char === '"' || char === "'") {
|
|
34
|
-
quote = char;
|
|
35
|
-
current += char;
|
|
36
|
-
}
|
|
37
|
-
else if (char === "(") {
|
|
38
|
-
depth++;
|
|
39
|
-
current += char;
|
|
40
|
-
}
|
|
41
|
-
else if (char === ")") {
|
|
42
|
-
depth--;
|
|
43
|
-
current += char;
|
|
44
|
-
}
|
|
45
|
-
else if (char === "," && depth === 0) {
|
|
46
|
-
if (current.trim())
|
|
47
|
-
result.push(current.trim());
|
|
48
|
-
current = "";
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
current += char;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (current.trim())
|
|
55
|
-
result.push(current.trim());
|
|
56
|
-
return result;
|
|
57
|
-
}
|
|
58
|
-
function parseFile(file, source) {
|
|
59
|
-
const lines = source.split(/\r?\n/);
|
|
60
|
-
const root = [];
|
|
61
|
-
const stack = [];
|
|
62
|
-
const target = () => stack.length ? stack[stack.length - 1].body : root;
|
|
63
|
-
for (let index = 0; index < lines.length; index++) {
|
|
64
|
-
const raw = lines[index];
|
|
65
|
-
const line = raw.trim();
|
|
66
|
-
const lineNo = index + 1;
|
|
67
|
-
if (!line || line.startsWith("//") || line.startsWith("#"))
|
|
68
|
-
continue;
|
|
69
|
-
const importMatch = line.match(/^import\s*\{\s*([^}]+)\s*\}\s*de\s*(["'])(.+?)\2\s*$/);
|
|
70
|
-
if (importMatch) {
|
|
71
|
-
target().push({ kind: "import", names: importMatch[1].split(",").map(x => x.trim()), source: importMatch[3], line: lineNo });
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
const functionMatch = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*\(\s*\)\s*\{$/u);
|
|
75
|
-
if (functionMatch) {
|
|
76
|
-
const entry = { name: functionMatch[1], line: lineNo, body: [] };
|
|
77
|
-
target().push({ kind: "function", ...entry });
|
|
78
|
-
stack.push(entry);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
if (line === "}") {
|
|
82
|
-
if (!stack.length)
|
|
83
|
-
throw new errors_1.FbpError("Accolade fermante sans fonction correspondante.", file, lineNo);
|
|
84
|
-
stack.pop();
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
const assignment = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*=\s*(.+)$/u);
|
|
88
|
-
if (assignment) {
|
|
89
|
-
target().push({ kind: "assign", name: assignment[1], value: assignment[2].trim(), line: lineNo });
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
const call = line.match(/^([\p{L}_][\p{L}\p{N}_]*)\s*\((.*)\)\s*;?$/u);
|
|
93
|
-
if (call) {
|
|
94
|
-
target().push({ kind: "call", name: call[1], args: splitArguments(call[2]), line: lineNo });
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
throw new errors_1.FbpError(`Syntaxe non reconnue : « ${line} ». Vérifie les parenthèses, les accolades et le signe =.`, file, lineNo);
|
|
98
|
-
}
|
|
99
|
-
if (stack.length) {
|
|
100
|
-
const open = stack[stack.length - 1];
|
|
101
|
-
throw new errors_1.FbpError(`La fonction « ${open.name} » n'est pas fermée par }.`, file, open.line);
|
|
102
|
-
}
|
|
103
|
-
return root;
|
|
104
|
-
}
|
|
105
|
-
class FbpCompiler {
|
|
106
|
-
variables = new Map();
|
|
107
|
-
functions = new Map();
|
|
108
|
-
imports = new Map();
|
|
109
|
-
output;
|
|
110
|
-
discordClient;
|
|
111
|
-
discordCommands = new Map();
|
|
112
|
-
constructor(options = {}) {
|
|
113
|
-
this.output = options.onOutput ?? (value => console.log(value));
|
|
114
|
-
}
|
|
115
|
-
resolveDiscordValue(source, interaction, selectedUser, selectedMember, optionValues) {
|
|
116
|
-
const guild = interaction.guild;
|
|
117
|
-
const values = {
|
|
118
|
-
"discordServeur.nom": guild?.name ?? "Serveur indisponible",
|
|
119
|
-
"discordServeur.id": guild?.id ?? "Indisponible",
|
|
120
|
-
"discordServeur.creation": guild ? "<t:" + Math.floor(guild.createdTimestamp / 1000) + ":D>" : "Indisponible",
|
|
121
|
-
"discordMembres.nombre": guild ? String(guild.memberCount) : "Indisponible",
|
|
122
|
-
"discordProfil.nom": selectedMember?.displayName ?? selectedUser?.username ?? "Sélectionne un membre",
|
|
123
|
-
"discordProfil.id": selectedUser?.id ?? "Sélectionne un membre",
|
|
124
|
-
"discordProfil.avatar": selectedUser?.displayAvatarURL({ size: 256 }) ?? "Sélectionne un membre",
|
|
125
|
-
"discordRoles.nombre": guild ? String(guild.roles.cache.size) : "Indisponible",
|
|
126
|
-
"discordSalons.nombre": guild ? String(guild.channels.cache.size) : "Indisponible",
|
|
127
|
-
"discordStatistiques.membres": guild ? String(guild.memberCount) : "Indisponible",
|
|
128
|
-
"discordStatistiques.roles": guild ? String(guild.roles.cache.size) : "Indisponible",
|
|
129
|
-
"discordStatistiques.salons": guild ? String(guild.channels.cache.size) : "Indisponible",
|
|
130
|
-
"discordLatence": String(Math.max(0, Math.round(interaction.client.ws.ping))) + " ms",
|
|
131
|
-
"discordAvatar": selectedUser?.displayAvatarURL({ size: 256 }) ?? interaction.user.displayAvatarURL({ size: 256 })
|
|
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];
|
|
136
|
-
return source.trim() in values ? values[source.trim()] : "Source inconnue : " + source.trim();
|
|
137
|
-
}
|
|
138
|
-
runFile(filePath) {
|
|
139
|
-
const absolute = node_path_1.default.resolve(filePath);
|
|
140
|
-
if (!node_fs_1.default.existsSync(absolute))
|
|
141
|
-
throw new errors_1.FbpError(`Fichier introuvable : ${absolute}`);
|
|
142
|
-
if (!node_fs_1.default.statSync(absolute).isFile())
|
|
143
|
-
throw new errors_1.FbpError(`Le chemin indiqué n'est pas un fichier : ${absolute}`);
|
|
144
|
-
this.execute(parseFile(absolute, node_fs_1.default.readFileSync(absolute, "utf8")), node_path_1.default.dirname(absolute));
|
|
145
|
-
}
|
|
146
|
-
execute(statements, baseDir) {
|
|
147
|
-
for (const statement of statements) {
|
|
148
|
-
if (statement.kind === "import") {
|
|
149
|
-
const targetFile = node_path_1.default.resolve(baseDir, statement.source);
|
|
150
|
-
if (!node_fs_1.default.existsSync(targetFile))
|
|
151
|
-
throw new errors_1.FbpError(`Fichier importé introuvable : ${statement.source}`);
|
|
152
|
-
const nested = parseFile(targetFile, node_fs_1.default.readFileSync(targetFile, "utf8"));
|
|
153
|
-
for (const item of nested) {
|
|
154
|
-
if (item.kind === "function")
|
|
155
|
-
this.functions.set(item.name, item);
|
|
156
|
-
}
|
|
157
|
-
for (const name of statement.names) {
|
|
158
|
-
if (!this.functions.has(name))
|
|
159
|
-
throw new errors_1.FbpError(`La fonction « ${name} » n'existe pas dans ${statement.source}.`);
|
|
160
|
-
this.imports.set(name, targetFile);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else if (statement.kind === "function") {
|
|
164
|
-
if (this.functions.has(statement.name))
|
|
165
|
-
throw new errors_1.FbpError(`La fonction « ${statement.name} » est définie plusieurs fois.`, undefined, statement.line);
|
|
166
|
-
this.functions.set(statement.name, statement);
|
|
167
|
-
}
|
|
168
|
-
else if (statement.kind === "assign") {
|
|
169
|
-
const type = valueType(statement.value);
|
|
170
|
-
const previous = this.variables.get(statement.name);
|
|
171
|
-
if (previous && type !== "inconnu" && previous.type !== "inconnu" && previous.type !== type) {
|
|
172
|
-
throw new errors_1.FbpError(`Type incompatible pour « ${statement.name} » : attendu ${previous.type}, reçu ${type}.`, undefined, statement.line);
|
|
173
|
-
}
|
|
174
|
-
const value = this.evaluate(statement.value, statement.line);
|
|
175
|
-
this.variables.set(statement.name, { type: previous?.type ?? type, value });
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
this.call(statement.name, statement.args, statement.line, baseDir);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
evaluate(expression, line) {
|
|
183
|
-
const value = expression.trim();
|
|
184
|
-
if (value === "true")
|
|
185
|
-
return true;
|
|
186
|
-
if (value === "false")
|
|
187
|
-
return false;
|
|
188
|
-
if (/^-?\d+(?:\.\d+)?$/.test(value))
|
|
189
|
-
return Number(value);
|
|
190
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
191
|
-
return value.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\(["'\\])/g, "$1");
|
|
192
|
-
}
|
|
193
|
-
if (this.variables.has(value))
|
|
194
|
-
return this.variables.get(value).value;
|
|
195
|
-
throw new errors_1.FbpError(`Valeur inconnue « ${value} ». Utilise un texte, un nombre, true, false ou une variable déclarée.`, undefined, line);
|
|
196
|
-
}
|
|
197
|
-
call(name, args, line, baseDir) {
|
|
198
|
-
const values = args.map(arg => this.evaluate(arg, line));
|
|
199
|
-
if (name === "affiche") {
|
|
200
|
-
this.output(values.map(value => value === undefined ? "" : String(value)).join(" "));
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (name === "discordConnexion") {
|
|
204
|
-
if (this.discordClient)
|
|
205
|
-
throw new errors_1.FbpError("La connexion Discord est déjà initialisée.", undefined, line);
|
|
206
|
-
dotenv_1.default.config();
|
|
207
|
-
const token = process.env.DISCORD_TOKEN;
|
|
208
|
-
if (!token)
|
|
209
|
-
throw new errors_1.FbpError("Token Discord manquant. Ajoute DISCORD_TOKEN dans le fichier .env.", undefined, line);
|
|
210
|
-
const client = new discord_js_1.Client({ intents: [discord_js_1.GatewayIntentBits.Guilds] });
|
|
211
|
-
this.discordClient = client;
|
|
212
|
-
client.once("ready", async () => {
|
|
213
|
-
try {
|
|
214
|
-
const commands = [...this.discordCommands.entries()].map(([commandName, response]) => {
|
|
215
|
-
const builder = new discord_js_1.SlashCommandBuilder()
|
|
216
|
-
.setName(commandName)
|
|
217
|
-
.setDescription(`Commande ${commandName} du bot FBP`);
|
|
218
|
-
if (response.type === "embed" && response.userOption) {
|
|
219
|
-
builder.addUserOption(option => option.setName(response.userOption.name)
|
|
220
|
-
.setDescription(response.userOption.description)
|
|
221
|
-
.setRequired(false));
|
|
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
|
-
if (option.minValue !== undefined)
|
|
235
|
-
item.setMinValue(option.minValue);
|
|
236
|
-
if (option.maxValue !== undefined)
|
|
237
|
-
item.setMaxValue(option.maxValue);
|
|
238
|
-
for (const choice of option.choices ?? [])
|
|
239
|
-
if (typeof choice.value === "number")
|
|
240
|
-
item.addChoices({ name: choice.name, value: choice.value });
|
|
241
|
-
return item;
|
|
242
|
-
}),
|
|
243
|
-
entier: () => builder.addIntegerOption(item => {
|
|
244
|
-
item.setName(option.name).setDescription(option.description).setRequired(option.required);
|
|
245
|
-
if (option.minValue !== undefined)
|
|
246
|
-
item.setMinValue(option.minValue);
|
|
247
|
-
if (option.maxValue !== undefined)
|
|
248
|
-
item.setMaxValue(option.maxValue);
|
|
249
|
-
for (const choice of option.choices ?? [])
|
|
250
|
-
if (typeof choice.value === "number")
|
|
251
|
-
item.addChoices({ name: choice.name, value: choice.value });
|
|
252
|
-
return item;
|
|
253
|
-
}),
|
|
254
|
-
booleen: () => builder.addBooleanOption(item => item.setName(option.name).setDescription(option.description).setRequired(option.required))
|
|
255
|
-
}[option.type];
|
|
256
|
-
add();
|
|
257
|
-
}
|
|
258
|
-
return builder.toJSON();
|
|
259
|
-
});
|
|
260
|
-
const manager = client.application?.commands;
|
|
261
|
-
if (!manager)
|
|
262
|
-
throw new Error("L'application Discord n'est pas disponible.");
|
|
263
|
-
const existing = await manager.fetch();
|
|
264
|
-
for (const command of commands) {
|
|
265
|
-
const registered = existing.find(item => item.name === command.name);
|
|
266
|
-
if (registered)
|
|
267
|
-
await manager.edit(registered.id, command);
|
|
268
|
-
else
|
|
269
|
-
await manager.create(command);
|
|
270
|
-
}
|
|
271
|
-
this.output("Connecté à Discord en tant que " + (client.user?.tag ?? "bot") + ".");
|
|
272
|
-
this.output("Commandes slash FBP enregistrées : " + commands.length + ".");
|
|
273
|
-
}
|
|
274
|
-
catch (error) {
|
|
275
|
-
this.output("Enregistrement des commandes slash impossible : " + (error instanceof Error ? error.message : String(error)));
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
client.on("interactionCreate", async (interaction) => {
|
|
279
|
-
if (!interaction.isChatInputCommand())
|
|
280
|
-
return;
|
|
281
|
-
const response = this.discordCommands.get(interaction.commandName);
|
|
282
|
-
if (response === undefined)
|
|
283
|
-
return;
|
|
284
|
-
try {
|
|
285
|
-
const optionValues = new Map();
|
|
286
|
-
for (const option of response.options ?? []) {
|
|
287
|
-
let value = null;
|
|
288
|
-
if (option.type === "texte")
|
|
289
|
-
value = interaction.options.getString(option.name);
|
|
290
|
-
else if (option.type === "nombre") {
|
|
291
|
-
const number = interaction.options.getNumber(option.name);
|
|
292
|
-
value = number === null ? null : String(number);
|
|
293
|
-
}
|
|
294
|
-
else if (option.type === "entier") {
|
|
295
|
-
const number = interaction.options.getInteger(option.name);
|
|
296
|
-
value = number === null ? null : String(number);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
const boolean = interaction.options.getBoolean(option.name);
|
|
300
|
-
value = boolean === null ? null : String(boolean);
|
|
301
|
-
}
|
|
302
|
-
if (value === null && option.required) {
|
|
303
|
-
await interaction.reply({ content: "Option obligatoire indisponible : " + option.name, ephemeral: true });
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
if (value !== null)
|
|
307
|
-
optionValues.set(option.name, value);
|
|
308
|
-
}
|
|
309
|
-
if (response.type === "text") {
|
|
310
|
-
const content = response.content.replace(/\{\{([a-z0-9_-]{1,32})\}\}/g, (match, name) => optionValues.has(name) ? optionValues.get(name) : match);
|
|
311
|
-
await interaction.reply(content);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
const embed = new discord_js_1.EmbedBuilder()
|
|
315
|
-
.setTitle(response.title)
|
|
316
|
-
.setDescription(response.description)
|
|
317
|
-
.setColor(Number.parseInt(response.color.slice(1), 16));
|
|
318
|
-
const guild = interaction.guild;
|
|
319
|
-
const selectedUser = response.userOption ? interaction.options.getUser(response.userOption.name) : null;
|
|
320
|
-
const selectedMember = selectedUser && guild ? await guild.members.fetch(selectedUser.id).catch(() => null) : null;
|
|
321
|
-
if (response.userOption && selectedUser)
|
|
322
|
-
optionValues.set(response.userOption.name, selectedMember?.displayName ?? selectedUser.username);
|
|
323
|
-
const fields = response.fields.map(field => ({
|
|
324
|
-
name: field.name,
|
|
325
|
-
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),
|
|
326
|
-
inline: false
|
|
327
|
-
}));
|
|
328
|
-
if (fields.length)
|
|
329
|
-
embed.addFields(fields);
|
|
330
|
-
await interaction.reply({ embeds: [embed] });
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
const message = "Une erreur est survenue pendant l'exécution de cette commande.";
|
|
335
|
-
this.output("Erreur Discord : " + (error instanceof Error ? error.message : String(error)));
|
|
336
|
-
if (!interaction.replied && !interaction.deferred) {
|
|
337
|
-
await interaction.reply({ content: message, ephemeral: true }).catch(() => undefined);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
void client.login(token).catch((error) => this.output("Connexion Discord impossible : " + (error instanceof Error ? error.message : String(error))));
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (name === "discordCommande") {
|
|
345
|
-
if (!this.discordClient)
|
|
346
|
-
throw new errors_1.FbpError("Appelle discordConnexion() avant de déclarer une commande.", undefined, line);
|
|
347
|
-
if (values.length !== 2 || typeof values[0] !== "string" || typeof values[1] !== "string") {
|
|
348
|
-
throw new errors_1.FbpError("Utilisation : discordCommande(\"ping\", \"Pong !\").", undefined, line);
|
|
349
|
-
}
|
|
350
|
-
const commandName = values[0];
|
|
351
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(commandName)) {
|
|
352
|
-
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);
|
|
353
|
-
}
|
|
354
|
-
this.discordCommands.set(commandName, { type: "text", content: values[1], options: [] });
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (name === "discordCommandeEmbed") {
|
|
358
|
-
if (!this.discordClient)
|
|
359
|
-
throw new errors_1.FbpError("Appelle discordConnexion() avant de déclarer une commande.", undefined, line);
|
|
360
|
-
if (values.length !== 4 || values.some(value => typeof value !== "string")) {
|
|
361
|
-
throw new errors_1.FbpError("Utilisation : discordCommandeEmbed(nom, titre, description, couleur).", undefined, line);
|
|
362
|
-
}
|
|
363
|
-
const [commandName, title, description, color] = values;
|
|
364
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(commandName))
|
|
365
|
-
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);
|
|
366
|
-
if (!/^#[0-9a-fA-F]{6}$/.test(color))
|
|
367
|
-
throw new errors_1.FbpError("La couleur d'un embed doit être au format hexadécimal, par exemple #5865F2.", undefined, line);
|
|
368
|
-
this.discordCommands.set(commandName, { type: "embed", title, description, color, fields: [], options: [] });
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
if (name === "discordChampDynamique") {
|
|
372
|
-
if (values.length !== 3 || values.some(value => typeof value !== "string")) {
|
|
373
|
-
throw new errors_1.FbpError("Utilisation : discordChampDynamique(nomCommande, nomChamp, source).", undefined, line);
|
|
374
|
-
}
|
|
375
|
-
const [commandName, fieldName, source] = values;
|
|
376
|
-
const command = this.discordCommands.get(commandName);
|
|
377
|
-
if (!command || command.type !== "embed")
|
|
378
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas un embed déclaré.", undefined, line);
|
|
379
|
-
if (command.fields.length >= 25)
|
|
380
|
-
throw new errors_1.FbpError("Un embed Discord ne peut pas contenir plus de 25 champs.", undefined, line);
|
|
381
|
-
if (!fieldName.trim() || !source.trim() || fieldName.length > 256 || source.length > 1024)
|
|
382
|
-
throw new errors_1.FbpError("Un champ dynamique doit avoir un nom et une source valides.", undefined, line);
|
|
383
|
-
command.fields.push({ name: fieldName, value: source, dynamic: true });
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
if (name === "discordChoixTexte" || name === "discordChoixEntier") {
|
|
387
|
-
if (values.length !== 4 || values.some(value => typeof value !== "string" && typeof value !== "number")) {
|
|
388
|
-
throw new errors_1.FbpError("Utilisation : discordChoixTexte(nomCommande, nomOption, nomChoix, valeur) ou discordChoixEntier(...).", undefined, line);
|
|
389
|
-
}
|
|
390
|
-
const [commandName, optionName, choiceName, choiceValue] = values;
|
|
391
|
-
if (typeof commandName !== "string" || typeof optionName !== "string" || typeof choiceName !== "string") {
|
|
392
|
-
throw new errors_1.FbpError("Le nom de la commande, de l'option et du choix doivent être du texte.", undefined, line);
|
|
393
|
-
}
|
|
394
|
-
const command = this.discordCommands.get(commandName);
|
|
395
|
-
if (!command)
|
|
396
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
397
|
-
const option = (command.options ?? []).find(item => item.name === optionName);
|
|
398
|
-
const expectedType = name === "discordChoixTexte" ? "texte" : "entier";
|
|
399
|
-
if (!option || option.type !== expectedType)
|
|
400
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » n'existe pas ou n'a pas le bon type.", undefined, line);
|
|
401
|
-
if (!choiceName.trim() || choiceName.length > 100)
|
|
402
|
-
throw new errors_1.FbpError("Le nom d'un choix doit contenir de 1 à 100 caractères.", undefined, line);
|
|
403
|
-
if (expectedType === "texte" && typeof choiceValue !== "string")
|
|
404
|
-
throw new errors_1.FbpError("La valeur d'un choix texte doit être du texte.", undefined, line);
|
|
405
|
-
if (expectedType === "entier" && (typeof choiceValue !== "number" || !Number.isInteger(choiceValue)))
|
|
406
|
-
throw new errors_1.FbpError("La valeur d'un choix entier doit être un nombre entier.", undefined, line);
|
|
407
|
-
if (option.minValue !== undefined || option.maxValue !== undefined)
|
|
408
|
-
throw new errors_1.FbpError("Une option avec des limites numériques ne peut pas recevoir de choix prédéfinis.", undefined, line);
|
|
409
|
-
const choices = option.choices ?? (option.choices = []);
|
|
410
|
-
if (choices.some(choice => choice.name === choiceName || choice.value === choiceValue))
|
|
411
|
-
throw new errors_1.FbpError("Ce nom ou cette valeur de choix existe déjà.", undefined, line);
|
|
412
|
-
if (choices.length >= 25)
|
|
413
|
-
throw new errors_1.FbpError("Une option Discord ne peut pas contenir plus de 25 choix.", undefined, line);
|
|
414
|
-
choices.push({ name: choiceName, value: choiceValue });
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (name === "discordOptionNombreLimites" || name === "discordOptionEntierLimites") {
|
|
418
|
-
if (values.length !== 4 || typeof values[0] !== "string" || typeof values[1] !== "string" || typeof values[2] !== "number" || typeof values[3] !== "number") {
|
|
419
|
-
throw new errors_1.FbpError("Utilisation : discordOptionNombreLimites(commande, option, minimum, maximum) ou discordOptionEntierLimites(...).", undefined, line);
|
|
420
|
-
}
|
|
421
|
-
const [commandName, optionName, minimum, maximum] = values;
|
|
422
|
-
const command = this.discordCommands.get(commandName);
|
|
423
|
-
if (!command)
|
|
424
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
425
|
-
const expectedType = name === "discordOptionNombreLimites" ? "nombre" : "entier";
|
|
426
|
-
const option = (command.options ?? []).find(item => item.name === optionName);
|
|
427
|
-
if (!option || option.type !== expectedType)
|
|
428
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » n'existe pas ou n'a pas le type numérique attendu.", undefined, line);
|
|
429
|
-
if (!Number.isFinite(minimum) || !Number.isFinite(maximum) || minimum > maximum || (expectedType === "entier" && (!Number.isInteger(minimum) || !Number.isInteger(maximum)))) {
|
|
430
|
-
throw new errors_1.FbpError("Les limites doivent être des nombres valides, minimum inférieur ou égal au maximum ; elles doivent être entières pour une option entière.", undefined, line);
|
|
431
|
-
}
|
|
432
|
-
if (option.choices?.length)
|
|
433
|
-
throw new errors_1.FbpError("Une option avec des choix prédéfinis ne peut pas recevoir de limites numériques.", undefined, line);
|
|
434
|
-
option.minValue = minimum;
|
|
435
|
-
option.maxValue = maximum;
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
if (name === "discordOptionTexte" || name === "discordOptionNombre" || name === "discordOptionEntier" || name === "discordOptionBooleen") {
|
|
439
|
-
if (values.length < 3 || values.length > 4 || values.some(value => typeof value !== "string" && typeof value !== "boolean")) {
|
|
440
|
-
throw new errors_1.FbpError("Utilisation : discordOptionTexte/Nombre/Entier/Booleen(nomCommande, nomOption, description, obligatoire).", undefined, line);
|
|
441
|
-
}
|
|
442
|
-
const [commandName, optionName, description] = values;
|
|
443
|
-
const required = values.length === 4 ? values[3] : false;
|
|
444
|
-
if (typeof commandName !== "string" || typeof optionName !== "string" || typeof description !== "string" || typeof required !== "boolean") {
|
|
445
|
-
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);
|
|
446
|
-
}
|
|
447
|
-
const command = this.discordCommands.get(commandName);
|
|
448
|
-
if (!command)
|
|
449
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas déclarée.", undefined, line);
|
|
450
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(optionName))
|
|
451
|
-
throw new errors_1.FbpError("Le nom de l'option doit contenir 1 à 32 caractères minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
452
|
-
if (!description.trim() || description.length > 100)
|
|
453
|
-
throw new errors_1.FbpError("La description de l'option doit contenir 1 à 100 caractères.", undefined, line);
|
|
454
|
-
const options = command.options ?? (command.options = []);
|
|
455
|
-
if (options.some(option => option.name === optionName) || (command.type === "embed" && command.userOption?.name === optionName))
|
|
456
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » existe déjà pour cette commande.", undefined, line);
|
|
457
|
-
if (options.length + (command.type === "embed" && command.userOption ? 1 : 0) >= 25)
|
|
458
|
-
throw new errors_1.FbpError("Une commande slash Discord ne peut pas contenir plus de 25 options.", undefined, line);
|
|
459
|
-
const type = name === "discordOptionTexte" ? "texte" : name === "discordOptionNombre" ? "nombre" : name === "discordOptionEntier" ? "entier" : "booleen";
|
|
460
|
-
options.push({ name: optionName, description, type, required });
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
if (name === "discordOptionUtilisateur") {
|
|
464
|
-
if (values.length !== 3 || values.some(value => typeof value !== "string")) {
|
|
465
|
-
throw new errors_1.FbpError("Utilisation : discordOptionUtilisateur(nomCommande, nomOption, description).", undefined, line);
|
|
466
|
-
}
|
|
467
|
-
const [commandName, optionName, description] = values;
|
|
468
|
-
const command = this.discordCommands.get(commandName);
|
|
469
|
-
if (!command || command.type !== "embed")
|
|
470
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas un embed déclaré.", undefined, line);
|
|
471
|
-
if (!/^[a-z0-9_-]{1,32}$/.test(optionName))
|
|
472
|
-
throw new errors_1.FbpError("Le nom de l'option doit contenir 1 à 32 caractères minuscules, chiffres, tirets ou underscores.", undefined, line);
|
|
473
|
-
if (!description.trim() || description.length > 100)
|
|
474
|
-
throw new errors_1.FbpError("La description de l'option doit contenir 1 à 100 caractères.", undefined, line);
|
|
475
|
-
const options = command.options ?? (command.options = []);
|
|
476
|
-
if (options.some(option => option.name === optionName) || command.userOption?.name === optionName)
|
|
477
|
-
throw new errors_1.FbpError("L'option « " + optionName + " » existe déjà pour cette commande.", undefined, line);
|
|
478
|
-
if (options.length + 1 > 25)
|
|
479
|
-
throw new errors_1.FbpError("Une commande slash Discord ne peut pas contenir plus de 25 options.", undefined, line);
|
|
480
|
-
command.userOption = { name: optionName, description };
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
if (name === "discordChamp") {
|
|
484
|
-
if (values.length !== 3 || values.some(value => typeof value !== "string")) {
|
|
485
|
-
throw new errors_1.FbpError("Utilisation : discordChamp(nomCommande, nomChamp, valeur).", undefined, line);
|
|
486
|
-
}
|
|
487
|
-
const [commandName, fieldName, fieldValue] = values;
|
|
488
|
-
const command = this.discordCommands.get(commandName);
|
|
489
|
-
if (!command || command.type !== "embed")
|
|
490
|
-
throw new errors_1.FbpError("La commande « " + commandName + " » n'est pas un embed déclaré.", undefined, line);
|
|
491
|
-
if (command.fields.length >= 25)
|
|
492
|
-
throw new errors_1.FbpError("Un embed Discord ne peut pas contenir plus de 25 champs.", undefined, line);
|
|
493
|
-
if (!fieldName.trim() || !fieldValue.trim() || fieldName.length > 256 || fieldValue.length > 1024)
|
|
494
|
-
throw new errors_1.FbpError("Un champ doit avoir un nom (1 à 256 caractères) et une valeur (1 à 1024 caractères).", undefined, line);
|
|
495
|
-
command.fields.push({ name: fieldName, value: fieldValue });
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
if (name === "ping") {
|
|
499
|
-
this.output("FBP fonctionne. Cette commande ne vérifie pas à elle seule une connexion Discord.");
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
const fn = this.functions.get(name);
|
|
503
|
-
if (!fn)
|
|
504
|
-
throw new errors_1.FbpError(`Fonction « ${name} » inconnue. Vérifie son nom ou son import.`, undefined, line);
|
|
505
|
-
this.execute(fn.body, this.imports.has(name) ? node_path_1.default.dirname(this.imports.get(name)) : baseDir);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
exports.FbpCompiler = FbpCompiler;
|
package/dist/errors.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.FbpError = void 0;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
class FbpError extends Error {
|
|
9
|
-
file;
|
|
10
|
-
line;
|
|
11
|
-
constructor(message, file, line) {
|
|
12
|
-
super(message);
|
|
13
|
-
this.file = file;
|
|
14
|
-
this.line = line;
|
|
15
|
-
this.name = "FbpError";
|
|
16
|
-
}
|
|
17
|
-
format() {
|
|
18
|
-
const location = this.file
|
|
19
|
-
? `${node_path_1.default.relative(process.cwd(), this.file)}${this.line ? `:${this.line}` : ""}: `
|
|
20
|
-
: "";
|
|
21
|
-
return `Erreur FBP : ${location}${this.message}`;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
exports.FbpError = FbpError;
|