@open-discord-bots/framework 0.0.1 → 0.0.2

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.
Files changed (103) hide show
  1. package/LICENSE.md +713 -0
  2. package/README.md +104 -0
  3. package/dist/api/api.d.ts +26 -0
  4. package/dist/api/api.js +44 -0
  5. package/dist/api/main.d.ts +133 -0
  6. package/dist/api/main.js +87 -0
  7. package/dist/api/modules/action.d.ts +34 -0
  8. package/dist/api/modules/action.js +58 -0
  9. package/dist/api/modules/base.d.ts +329 -0
  10. package/dist/api/modules/base.js +804 -0
  11. package/dist/api/modules/builder.d.ts +647 -0
  12. package/dist/api/modules/builder.js +1441 -0
  13. package/dist/api/modules/checker.d.ts +648 -0
  14. package/dist/api/modules/checker.js +1324 -0
  15. package/dist/api/modules/client.d.ts +768 -0
  16. package/dist/api/modules/client.js +1859 -0
  17. package/dist/api/modules/code.d.ts +33 -0
  18. package/dist/api/modules/code.js +57 -0
  19. package/dist/api/modules/config.d.ts +70 -0
  20. package/dist/api/modules/config.js +206 -0
  21. package/dist/api/modules/console.d.ts +305 -0
  22. package/dist/api/modules/console.js +598 -0
  23. package/dist/api/modules/cooldown.d.ts +138 -0
  24. package/dist/api/modules/cooldown.js +359 -0
  25. package/dist/api/modules/database.d.ts +135 -0
  26. package/dist/api/modules/database.js +271 -0
  27. package/dist/api/modules/event.d.ts +43 -0
  28. package/dist/api/modules/event.js +100 -0
  29. package/dist/api/modules/flag.d.ts +40 -0
  30. package/dist/api/modules/flag.js +72 -0
  31. package/dist/api/modules/fuse.d.ts +218 -0
  32. package/dist/api/modules/fuse.js +123 -0
  33. package/dist/api/modules/helpmenu.d.ts +106 -0
  34. package/dist/api/modules/helpmenu.js +167 -0
  35. package/dist/api/modules/language.d.ts +85 -0
  36. package/dist/api/modules/language.js +195 -0
  37. package/dist/api/modules/permission.d.ts +121 -0
  38. package/dist/api/modules/permission.js +314 -0
  39. package/dist/api/modules/plugin.d.ts +128 -0
  40. package/dist/api/modules/plugin.js +168 -0
  41. package/dist/api/modules/post.d.ts +44 -0
  42. package/dist/api/modules/post.js +92 -0
  43. package/dist/api/modules/progressbar.d.ts +108 -0
  44. package/dist/api/modules/progressbar.js +233 -0
  45. package/dist/api/modules/responder.d.ts +506 -0
  46. package/dist/api/modules/responder.js +1468 -0
  47. package/dist/api/modules/session.d.ts +58 -0
  48. package/dist/api/modules/session.js +171 -0
  49. package/dist/api/modules/startscreen.d.ts +165 -0
  50. package/dist/api/modules/startscreen.js +293 -0
  51. package/dist/api/modules/stat.d.ts +142 -0
  52. package/dist/api/modules/stat.js +293 -0
  53. package/dist/api/modules/verifybar.d.ts +54 -0
  54. package/dist/api/modules/verifybar.js +60 -0
  55. package/dist/api/modules/worker.d.ts +41 -0
  56. package/dist/api/modules/worker.js +93 -0
  57. package/dist/api/utils.d.ts +61 -0
  58. package/dist/api/utils.js +254 -0
  59. package/dist/index.d.ts +4 -1
  60. package/dist/index.js +40 -0
  61. package/dist/startup/dump.d.ts +14 -0
  62. package/dist/startup/dump.js +79 -0
  63. package/dist/startup/errorHandling.d.ts +2 -0
  64. package/dist/startup/errorHandling.js +43 -0
  65. package/dist/startup/pluginLauncher.d.ts +2 -0
  66. package/dist/startup/pluginLauncher.js +202 -0
  67. package/package.json +9 -3
  68. package/src/api/api.ts +29 -0
  69. package/src/api/main.ts +189 -0
  70. package/src/api/modules/action.ts +58 -0
  71. package/src/api/modules/base.ts +811 -0
  72. package/src/api/modules/builder.ts +1554 -0
  73. package/src/api/modules/checker.ts +1549 -0
  74. package/src/api/modules/client.ts +2247 -0
  75. package/src/api/modules/code.ts +58 -0
  76. package/src/api/modules/config.ts +159 -0
  77. package/src/api/modules/console.ts +665 -0
  78. package/src/api/modules/cooldown.ts +348 -0
  79. package/src/api/modules/database.ts +278 -0
  80. package/src/api/modules/event.ts +99 -0
  81. package/src/api/modules/flag.ts +73 -0
  82. package/src/api/modules/fuse.ts +348 -0
  83. package/src/api/modules/helpmenu.ts +216 -0
  84. package/src/api/modules/language.ts +201 -0
  85. package/src/api/modules/permission.ts +340 -0
  86. package/src/api/modules/plugin.ts +242 -0
  87. package/src/api/modules/post.ts +90 -0
  88. package/src/api/modules/progressbar.ts +232 -0
  89. package/src/api/modules/responder.ts +1420 -0
  90. package/src/api/modules/session.ts +155 -0
  91. package/src/api/modules/startscreen.ts +320 -0
  92. package/src/api/modules/stat.ts +313 -0
  93. package/src/api/modules/verifybar.ts +61 -0
  94. package/src/api/modules/worker.ts +93 -0
  95. package/src/api/utils.ts +206 -0
  96. package/src/cli/cli.ts +151 -0
  97. package/src/cli/editConfig.ts +943 -0
  98. package/src/index.ts +6 -1
  99. package/src/startup/compilation.ts +186 -0
  100. package/src/startup/dump.ts +45 -0
  101. package/src/startup/errorHandling.ts +38 -0
  102. package/src/startup/pluginLauncher.ts +261 -0
  103. package/LICENSE +0 -21
@@ -0,0 +1,1549 @@
1
+ ///////////////////////////////////////
2
+ //CONFIG CHECKER MODULE
3
+ ///////////////////////////////////////
4
+ import { ODDiscordIdType, ODId, ODManager, ODManagerData, ODValidId, ODValidJsonType } from "./base"
5
+ import { ODConfig } from "./config"
6
+ import { ODLanguageManager } from "./language"
7
+ import { ODDebugger } from "./console"
8
+
9
+ /**## ODCheckerResult `interface`
10
+ * This interface is the result from a config checker check() function.
11
+ */
12
+ export interface ODCheckerResult {
13
+ valid:boolean
14
+ messages:ODCheckerMessage[]
15
+ }
16
+
17
+ /**## ODCheckerManager `class`
18
+ * This is an Open Discord checker manager.
19
+ *
20
+ * It manages all config checkers in the bot and allows plugins to access config checkers from Open Discord & other plugins!
21
+ *
22
+ * You can use this class to get/add a config checker (`ODChecker`) in your plugin!
23
+ */
24
+ export class ODCheckerManager extends ODManager<ODChecker> {
25
+ /**The global temporary storage shared between all config checkers. */
26
+ storage: ODCheckerStorage
27
+ /**The class responsible for rendering the config checker report. */
28
+ renderer: ODCheckerRenderer
29
+ /**The class responsible for translating the config checker report. */
30
+ translation: ODCheckerTranslationRegister
31
+ /**Final functions are global functions executed just before the report is created. */
32
+ functions: ODCheckerFunctionManager
33
+ /**A variable containing the last result returned from `checkAll()` */
34
+ lastResult: ODCheckerResult|null = null
35
+
36
+ constructor(debug:ODDebugger, storage:ODCheckerStorage, renderer:ODCheckerRenderer, translation:ODCheckerTranslationRegister, functions:ODCheckerFunctionManager){
37
+ super(debug,"config checker")
38
+ this.storage = storage
39
+ this.renderer = renderer
40
+ this.translation = translation
41
+ this.functions = functions
42
+ }
43
+ /**Check all config checkers registered in this manager.*/
44
+ checkAll(sort:boolean): ODCheckerResult {
45
+ this.storage.reset()
46
+
47
+ let isValid = true
48
+ const final: ODCheckerMessage[] = []
49
+
50
+ const checkers = this.getAll()
51
+ checkers.sort((a,b) => b.priority-a.priority)
52
+
53
+ checkers.forEach((checker) => {
54
+ const res = checker.check()
55
+ final.push(...res.messages)
56
+
57
+ if (!res.valid) isValid = false
58
+ })
59
+
60
+ this.functions.getAll().forEach((func) => {
61
+ const res = func.func(this,this.functions)
62
+ final.push(...res.messages)
63
+
64
+ if (!res.valid) isValid = false
65
+ })
66
+
67
+ //sort messages => (info, warning, error)
68
+ if (sort) final.sort((a,b) => {
69
+ const typeA = (a.type == "error") ? 2 : (a.type == "warning") ? 1 : 0
70
+ const typeB = (b.type == "error") ? 2 : (b.type == "warning") ? 1 : 0
71
+
72
+ return typeA-typeB
73
+ })
74
+
75
+ this.lastResult = {
76
+ valid:isValid,
77
+ messages:final
78
+ }
79
+
80
+ return {
81
+ valid:isValid,
82
+ messages:final
83
+ }
84
+ }
85
+ /**Create temporary and unlisted `ODConfig`, `ODChecker` & `ODCheckerStorage` classes. This will help you use a `ODCheckerStructure` validator without officially registering it in `opendiscord.checkers`. */
86
+ createTemporaryCheckerEnvironment(){
87
+ return new ODChecker("opendiscord:temporary-environment",new ODCheckerStorage(),0,new ODConfig("opendiscord:temporary-environment",{}),new ODCheckerStructure("opendiscord:temporary-environment",{}))
88
+ }
89
+ }
90
+
91
+ /**## ODCheckerStorage `class`
92
+ * This is an Open Discord checker storage.
93
+ *
94
+ * It stores temporary data to share between config checkers!
95
+ * (e.g. The `messages.json` needs to access the `"id"` from `options.json`)
96
+ *
97
+ *
98
+ * You can use this class when you create your own config checker implementation! (not required for using the built-in config checker)
99
+ */
100
+ export class ODCheckerStorage {
101
+ /**This is the array that stores all the data. ❌ **(don't edit unless really needed!)***/
102
+ storage: {source:ODId, key:string, value:any}[] = []
103
+
104
+ /**Get data from the database (`source` => id of `ODChecker`) */
105
+ get(source:ODValidId, key:string): any|null {
106
+ const result = this.storage.find(d => (d.source.value == new ODId(source).value) && (d.key == key))
107
+ return (result) ? result.value : null
108
+ }
109
+ /**Add data to the database (`source` => id of `ODChecker`). This function also overwrites existing data!*/
110
+ set(source:ODValidId, key:string, value:any){
111
+ const index = this.storage.findIndex(d => (d.source.value == new ODId(source).value) && (d.key == key))
112
+ if (index > -1){
113
+ //overwrite
114
+ this.storage[index] = {
115
+ source:new ODId(source),
116
+ key,value
117
+ }
118
+ return true
119
+ }else{
120
+ this.storage.push({
121
+ source:new ODId(source),
122
+ key,value
123
+ })
124
+ return false
125
+ }
126
+ }
127
+ /**Delete data from the database (`source` => id of `ODChecker`) */
128
+ delete(source:ODValidId, key:string){
129
+ const index = this.storage.findIndex(d => (d.source.value == new ODId(source).value) && (d.key == key))
130
+ if (index > -1){
131
+ //delete
132
+ this.storage.splice(index,1)
133
+ return true
134
+ }else return false
135
+ }
136
+
137
+ /**Reset the entire database */
138
+ reset(){
139
+ this.storage = []
140
+ }
141
+ }
142
+
143
+ /**## ODCheckerRenderer `class`
144
+ * This is an Open Discord checker renderer.
145
+ *
146
+ * It's responsible for rendering the config checker result in the console.
147
+ * This class doesn't provide any components! You need to create them by extending this class
148
+ *
149
+ * You can use this class if you want to change how the config checker looks!
150
+ */
151
+ export class ODCheckerRenderer {
152
+ /**Get all components */
153
+ getComponents(compact:boolean, renderEmpty:boolean, translation:ODCheckerTranslationRegister, data:ODCheckerResult): string[] {
154
+ return []
155
+ }
156
+ /**Render all components */
157
+ render(components:string[]){
158
+ if (components.length < 1) return
159
+ console.log("\n")
160
+ components.forEach((c) => {
161
+ console.log(c)
162
+ })
163
+ console.log("\n")
164
+ }
165
+ }
166
+
167
+ /**## ODCheckerTranslationRegister `class`
168
+ * This is an Open Discord checker translation register.
169
+ *
170
+ * It's used to store & manage the translation for each message from the config checker!
171
+ * Most translations are stored by message id, but there are some exceptions like the additional text on the checker report.
172
+ *
173
+ * You can use this class if you want to translate your config checker messages! **This is optional & isn't required for the checker to work!**
174
+ */
175
+ export class ODCheckerTranslationRegister {
176
+ /**This is the array that stores all the data. ❌ **(don't edit unless really needed!)***/
177
+ #translations: {type:"message"|"other", id:string, translation:string}[] = []
178
+
179
+ /**Get the translation from a config checker message/sentence */
180
+ get(type:"message"|"other", id:string): string|null {
181
+ const result = this.#translations.find(d => (d.id == id) && (d.type == type))
182
+ return (result) ? result.translation : null
183
+ }
184
+ /**Set the translation for a config checker message/sentence. This function also overwrites existing translations!*/
185
+ set(type:"message"|"other", id:string, translation:string){
186
+ const index = this.#translations.findIndex(d => (d.id == id) && (d.type == type))
187
+ if (index > -1){
188
+ //overwrite
189
+ this.#translations[index] = {type,id,translation}
190
+ return true
191
+ }else{
192
+ this.#translations.push({type,id,translation})
193
+ return false
194
+ }
195
+ }
196
+ /**Delete the translation for a config checker message/sentence. */
197
+ delete(type:"message"|"other", id:string){
198
+ const index = this.#translations.findIndex(d => (d.id == id) && (d.type == type))
199
+ if (index > -1){
200
+ //delete
201
+ this.#translations.splice(index,1)
202
+ return true
203
+ }else return false
204
+ }
205
+
206
+ /**Get all translations */
207
+ getAll(){
208
+ return this.#translations
209
+ }
210
+
211
+ /**Insert the translation params into the text. */
212
+ insertTranslationParams(text:string, translationParams:string[]){
213
+ translationParams.forEach((value,index) => {
214
+ text = text.replace(`{${index}}`,value)
215
+ })
216
+ return text
217
+ }
218
+ /**A shortcut to copy translations from the `ODLanguageManager` to `ODCheckerTranslationRegister` */
219
+ quickTranslate(manager:ODLanguageManager, translationId:string, type:"other"|"message", id:string){
220
+ const translation = manager.getTranslation(translationId)
221
+ if (translation) this.set(type,id,translation)
222
+ }
223
+ }
224
+
225
+ /**## ODCheckerFunctionCallback `type`
226
+ * This is the function used in the `ODCheckerFunction` class.
227
+ */
228
+ export type ODCheckerFunctionCallback = (manager:ODCheckerManager, functions:ODCheckerFunctionManager) => ODCheckerResult
229
+
230
+ /**## ODCheckerFunction `class`
231
+ * This is an Open Discord config checker function.
232
+ *
233
+ * It is a global function that will be executed after all config checkers. It can do additional checks for invalid/missing configurations.
234
+ * It's mostly used for things that need to be checked globally!
235
+ */
236
+ export class ODCheckerFunction extends ODManagerData {
237
+ /**The function which will be executed globally after all config checkers. */
238
+ func: ODCheckerFunctionCallback
239
+
240
+ constructor(id:ODValidId, func:ODCheckerFunctionCallback){
241
+ super(id)
242
+ this.func = func
243
+ }
244
+ }
245
+
246
+ /**## ODCheckerFunctionManager `class`
247
+ * This is an Open Discord config checker function manager.
248
+ *
249
+ * It manages all `ODCheckerFunction`'s and it has some extra shortcuts for frequently used methods.
250
+ */
251
+ export class ODCheckerFunctionManager extends ODManager<ODCheckerFunction> {
252
+ constructor(debug:ODDebugger){
253
+ super(debug,"config checker function")
254
+ }
255
+
256
+ /**A shortcut to create a warning, info or error message */
257
+ createMessage(checkerId:ODValidId, id:ODValidId, filepath:string, type:"info"|"warning"|"error", message:string, locationTrace:ODCheckerLocationTrace, docs:string|null, translationParams:string[], locationId:ODId, locationDocs:string|null): ODCheckerMessage {
258
+ return {
259
+ checkerId:new ODId(checkerId),
260
+ messageId:new ODId(id),
261
+ locationId,
262
+
263
+ type,message,
264
+ path:this.locationTraceToString(locationTrace),
265
+ filepath,
266
+ translationParams,
267
+
268
+ messageDocs:docs,
269
+ locationDocs
270
+ }
271
+ }
272
+ /**Create a string from the location trace (path)*/
273
+ locationTraceToString(trace:ODCheckerLocationTrace){
274
+ const final: ODCheckerLocationTrace = []
275
+ trace.forEach((t) => {
276
+ if (typeof t == "number"){
277
+ final.push(`:${t}`)
278
+ }else{
279
+ final.push(`."${t}"`)
280
+ }
281
+ })
282
+ return final.join("").substring(1)
283
+ }
284
+ /**De-reference the locationTrace array. Use this before adding a value to the array*/
285
+ locationTraceDeref(trace:ODCheckerLocationTrace): ODCheckerLocationTrace {
286
+ return JSON.parse(JSON.stringify(trace))
287
+ }
288
+ }
289
+
290
+ /**## ODCheckerLocationTrace `type`
291
+ * This type is an array of strings & numbers which represents the location trace from the config checker.
292
+ * It's used to generate a path to the error (e.g. `"abc"."efg".1."something"`)
293
+ */
294
+ export type ODCheckerLocationTrace = (string|number)[]
295
+
296
+ /**## ODCheckerOptions `interface`
297
+ * This interface contains all optional properties to customise in the `ODChecker` class.
298
+ */
299
+ export interface ODCheckerOptions {
300
+ /**The name of this config in the Interactive Setup CLI. */
301
+ cliDisplayName?:string
302
+ /**The description of this config in the Interactive Setup CLI. */
303
+ cliDisplayDescription?:string
304
+ }
305
+
306
+ /**## ODChecker `class`
307
+ * This is an Open Discord config checker.
308
+ *
309
+ * It checks a specific config file for invalid/missing configurations. This data can then be used to show to the user what's wrong!
310
+ * You can check for example if a string is longer/shorter than a certain amount of characters & more!
311
+ *
312
+ * You can use this class when you create your own custom config file & you want to check it for syntax errors.
313
+ */
314
+ export class ODChecker extends ODManagerData {
315
+ /**The storage of this checker (reference for `ODCheckerManager.storage`) */
316
+ storage: ODCheckerStorage
317
+ /**The higher the priority, the faster it gets checked! */
318
+ priority: number
319
+ /**The config file that needs to be checked */
320
+ config: ODConfig
321
+ /**The structure of the config file */
322
+ structure: ODCheckerStructure
323
+ /**Temporary storage for all error messages from the check() method (not recommended to use) */
324
+ messages: ODCheckerMessage[] = []
325
+ /**Temporary storage for the quit status from the check() method (not recommended to use) */
326
+ quit: boolean = false
327
+ /**All additional properties of this config checker. */
328
+ options: ODCheckerOptions
329
+
330
+ constructor(id:ODValidId, storage: ODCheckerStorage, priority:number, config:ODConfig, structure:ODCheckerStructure, options?:ODCheckerOptions){
331
+ super(id)
332
+ this.storage = storage
333
+ this.priority = priority
334
+ this.config = config
335
+ this.structure = structure
336
+ this.options = options ?? {}
337
+ }
338
+
339
+ /**Get a human-readable number string. */
340
+ #ordinalNumber(num:number){
341
+ const i = Math.abs(Math.round(num))
342
+ const cent = i % 100
343
+ if (cent >= 10 && cent <= 20) return i+'th'
344
+ const dec = i % 10
345
+ if (dec === 1) return i+'st'
346
+ if (dec === 2) return i+'nd'
347
+ if (dec === 3) return i+'rd'
348
+ return i+'th'
349
+ }
350
+ /**Run this checker. Returns all errors*/
351
+ check(): ODCheckerResult {
352
+ this.messages = []
353
+ this.quit = false
354
+
355
+ this.structure.check(this,this.config.data,[])
356
+ return {
357
+ valid:!this.quit,
358
+ messages:this.messages
359
+ }
360
+ }
361
+ /**Create a string from the location trace/path in a human readable format. */
362
+ locationTraceToString(trace:ODCheckerLocationTrace){
363
+ const final: ODCheckerLocationTrace = []
364
+ trace.forEach((t) => {
365
+ if (typeof t == "number"){
366
+ final.push(`:(${this.#ordinalNumber(t+1)})`)
367
+ }else{
368
+ final.push(`."${t}"`)
369
+ }
370
+ })
371
+ return final.join("").substring(1)
372
+ }
373
+ /**De-reference the locationTrace array. Use this before adding a value to the array*/
374
+ locationTraceDeref(trace:ODCheckerLocationTrace): ODCheckerLocationTrace {
375
+ return JSON.parse(JSON.stringify(trace))
376
+ }
377
+
378
+ /**A shortcut to create a warning, info or error message */
379
+ createMessage(id:ODValidId, type:"info"|"warning"|"error", message:string, locationTrace:ODCheckerLocationTrace, docs:string|null, translationParams:string[], locationId:ODId, locationDocs:string|null){
380
+ if (type == "error") this.quit = true
381
+ this.messages.push({
382
+ checkerId:this.id,
383
+ messageId:new ODId(id),
384
+ locationId,
385
+
386
+ type,message,
387
+ path:this.locationTraceToString(locationTrace),
388
+ filepath:this.config.path,
389
+ translationParams,
390
+
391
+ messageDocs:docs,
392
+ locationDocs
393
+ })
394
+ }
395
+ }
396
+
397
+ /**## ODCheckerMessage `interface`
398
+ * This interface is an object which has all variables required for a config checker message!
399
+ */
400
+ export interface ODCheckerMessage {
401
+ checkerId:ODId,
402
+ messageId:ODId,
403
+ locationId:ODId,
404
+
405
+ type:"info"|"warning"|"error",
406
+ message:string,
407
+ path:string,
408
+ filepath:string,
409
+ translationParams:string[],
410
+
411
+ messageDocs:string|null,
412
+ locationDocs:string|null
413
+ }
414
+
415
+ /**## ODCheckerStructureOptions `interface`
416
+ * This interface has the basic options for the `ODCheckerStructure`!
417
+ */
418
+ export interface ODCheckerStructureOptions {
419
+ /**Add a custom checker function. Returns `true` when valid. */
420
+ custom?:(checker:ODChecker, value:ODValidJsonType, locationTrace:ODCheckerLocationTrace, locationId:ODId, locationDocs:string|null) => boolean,
421
+ /**Set the url to the documentation of this variable. */
422
+ docs?:string,
423
+ /**The name of this config in the Interactive Setup CLI. */
424
+ cliDisplayName?:string
425
+ /**The description of this config in the Interactive Setup CLI. */
426
+ cliDisplayDescription?:string
427
+ /**Hide the description of this config in the Interactive Setup CLI parent view/list. */
428
+ cliHideDescriptionInParent?:boolean
429
+ /**The default value of this variable when creating it in the Interactive Setup CLI. When not specified, the user will be asked to insert a value. */
430
+ cliInitDefaultValue?:ODValidJsonType
431
+ }
432
+
433
+ /**## ODCheckerStructure `class`
434
+ * This is an Open Discord config checker structure.
435
+ *
436
+ * This class will check for a single variable in a config file, customise it in the settings!
437
+ * If you want prebuilt checkers (for strings, booleans, numbers, ...), check the other `ODCheckerStructure`'s!
438
+ *
439
+ * **Not recommended to use!** It's recommended to extend from another `ODConfigCheckerStructure` class!
440
+ */
441
+ export class ODCheckerStructure {
442
+ /**The id of this checker structure */
443
+ id: ODId
444
+ /**The options for this checker structure */
445
+ options: ODCheckerStructureOptions
446
+
447
+ constructor(id:ODValidId, options:ODCheckerStructureOptions){
448
+ this.id = new ODId(id)
449
+ this.options = options
450
+ }
451
+
452
+ /**Check a variable if it matches all settings in this checker. This function is automatically executed by Open Discord! */
453
+ check(checker:ODChecker, value:ODValidJsonType, locationTrace:ODCheckerLocationTrace): boolean {
454
+ if (typeof this.options.custom != "undefined"){
455
+ return this.options.custom(checker,value,locationTrace,this.id,(this.options.docs ?? null))
456
+ }else return true
457
+ }
458
+ }
459
+
460
+ /**## ODCheckerObjectStructureOptions `interface`
461
+ * This interface has the options for `ODCheckerObjectStructure`!
462
+ */
463
+ export interface ODCheckerObjectStructureOptions extends ODCheckerStructureOptions {
464
+ /**Add a checker for a property in an object (can also be optional) */
465
+ children:{key:string, priority?:number, optional?:boolean, cliHideInEditMode?:boolean, checker:ODCheckerStructure}[],
466
+ /**A list of keys to skip when creating this object with the Interactive Setup CLI. The default value of these properties will be used instead. */
467
+ cliInitSkipKeys?:string[],
468
+ /**The key of a (primitive) property in this object to show the value of in the Interactive Setup CLI when listed in an array. */
469
+ cliDisplayKeyInParentArray?:string,
470
+ /**A list of additional (primitive) property keys in this object to show the value of in the Interactive Setup CLI when listed in an array. */
471
+ cliDisplayAdditionalKeysInParentArray?:string[]
472
+ }
473
+
474
+ /**## ODCheckerObjectStructure `class`
475
+ * This is an Open Discord config checker structure.
476
+ *
477
+ * This class will check for an object variable in a config file, customise it in the settings!
478
+ * A checker for the children can be set in the settings.
479
+ */
480
+ export class ODCheckerObjectStructure extends ODCheckerStructure {
481
+ declare options: ODCheckerObjectStructureOptions
482
+
483
+ constructor(id:ODValidId, options:ODCheckerObjectStructureOptions){
484
+ super(id,options)
485
+ }
486
+
487
+ check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean {
488
+ const lt = checker.locationTraceDeref(locationTrace)
489
+
490
+ //check type & options
491
+ if (typeof value != "object"){
492
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null))
493
+ return false
494
+ }
495
+
496
+ //sort children
497
+ if (typeof this.options.children == "undefined") return super.check(checker,value,locationTrace)
498
+ const sortedChildren = this.options.children.sort((a,b) => {
499
+ if ((a.priority ?? 0) < (b.priority ?? 0)) return -1
500
+ else if ((a.priority ?? 0) > (b.priority ?? 0)) return 1
501
+ else return 0
502
+ })
503
+
504
+ //check children
505
+ let localQuit = false
506
+ sortedChildren.forEach((child) => {
507
+ const localLt = checker.locationTraceDeref(lt)
508
+ localLt.push(child.key)
509
+
510
+ if (typeof value[child.key] == "undefined"){
511
+ if (!child.optional){
512
+ localQuit = true
513
+ checker.createMessage("opendiscord:property-missing","error",`The property "${child.key}" is mising from this object!`,lt,null,[`"${child.key}"`],this.id,(this.options.docs ?? null))
514
+ }else{
515
+ checker.createMessage("opendiscord:property-optional","info",`The property "${child.key}" is optional in this object!`,lt,null,[`"${child.key}"`],this.id,(this.options.docs ?? null))
516
+ }
517
+ }else if (!child.checker.check(checker,value[child.key],localLt)) localQuit = true
518
+ })
519
+
520
+ //do local quit or check custom function
521
+ if (localQuit) return false
522
+ else return super.check(checker,value,locationTrace)
523
+ }
524
+ }
525
+
526
+ /**## ODCheckerStringStructureOptions `interface`
527
+ * This interface has the options for `ODCheckerStringStructure`!
528
+ */
529
+ export interface ODCheckerStringStructureOptions extends ODCheckerStructureOptions {
530
+ /**The minimum length of this string */
531
+ minLength?:number,
532
+ /**The maximum length of this string */
533
+ maxLength?:number,
534
+ /**Set the required length of this string */
535
+ length?:number,
536
+ /**This string needs to start with ... */
537
+ startsWith?:string,
538
+ /**This string needs to end with ... */
539
+ endsWith?:string,
540
+ /**This string needs to contain ... */
541
+ contains?:string,
542
+ /**This string is not allowed to contain ... */
543
+ invertedContains?:string,
544
+ /**You need to choose between ... */
545
+ choices?:string[],
546
+ /**This string needs to be in lowercase. */
547
+ lowercaseOnly?:boolean,
548
+ /**This string needs to be in uppercase. */
549
+ uppercaseOnly?:boolean,
550
+ /**This string shouldn't contain any special characters (allowed: A-Z, a-z, 0-9, space, a few punctuation marks, ...). */
551
+ noSpecialCharacters?:boolean,
552
+ /**Do not allow any spaces in this string. */
553
+ withoutSpaces?:boolean,
554
+ /**Give a warning when a sentence doesn't start with a capital letter. Or require every word to start with a capital letter. (Ignores numbers, unicode characters, ...) */
555
+ capitalLetterWarning?:false|"sentence"|"word"
556
+ /**Give a warning when a sentence doesn't end with a punctuation letter (.,?!) */
557
+ punctuationWarning?:boolean
558
+ /**The string needs to match this regex */
559
+ regex?:RegExp,
560
+ /**Provide an optional list for autocomplete when using the Interactive Setup CLI. Defaults to the `choices` option. */
561
+ cliAutocompleteList?:string[],
562
+ /**Dynamically provide a list for autocomplete items when using the Interactive Setup CLI. */
563
+ cliAutocompleteFunc?:() => Promise<string[]|null>
564
+ }
565
+
566
+ /**## ODCheckerStringStructure `class`
567
+ * This is an Open Discord config checker structure.
568
+ *
569
+ * This class will check for a string variable in a config file, customise it in the settings!
570
+ */
571
+ export class ODCheckerStringStructure extends ODCheckerStructure {
572
+ declare options: ODCheckerStringStructureOptions
573
+
574
+ constructor(id:ODValidId, options:ODCheckerStringStructureOptions){
575
+ super(id,options)
576
+ }
577
+
578
+ check(checker:ODChecker, value:string, locationTrace:ODCheckerLocationTrace): boolean {
579
+ const lt = checker.locationTraceDeref(locationTrace)
580
+
581
+ //check type & options
582
+ if (typeof value != "string"){
583
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: string!",lt,null,["string"],this.id,(this.options.docs ?? null))
584
+ return false
585
+ }else if (typeof this.options.minLength != "undefined" && value.length < this.options.minLength){
586
+ checker.createMessage("opendiscord:string-too-short","error",`This string can't be shorter than ${this.options.minLength} characters!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null))
587
+ return false
588
+ }else if (typeof this.options.maxLength != "undefined" && value.length > this.options.maxLength){
589
+ checker.createMessage("opendiscord:string-too-long","error",`This string can't be longer than ${this.options.maxLength} characters!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null))
590
+ return false
591
+ }else if (typeof this.options.length != "undefined" && value.length !== this.options.length){
592
+ checker.createMessage("opendiscord:string-length-invalid","error",`This string needs to be ${this.options.length} characters long!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null))
593
+ return false
594
+ }else if (typeof this.options.startsWith != "undefined" && !value.startsWith(this.options.startsWith)){
595
+ checker.createMessage("opendiscord:string-starts-with","error",`This string needs to start with "${this.options.startsWith}"!`,lt,null,[`"${this.options.startsWith}"`],this.id,(this.options.docs ?? null))
596
+ return false
597
+ }else if (typeof this.options.endsWith != "undefined" && !value.endsWith(this.options.endsWith)){
598
+ checker.createMessage("opendiscord:string-ends-with","error",`This string needs to end with "${this.options.endsWith}"!`,lt,null,[`"${this.options.endsWith}"`],this.id,(this.options.docs ?? null))
599
+ return false
600
+ }else if (typeof this.options.contains != "undefined" && !value.includes(this.options.contains)){
601
+ checker.createMessage("opendiscord:string-contains","error",`This string needs to contain "${this.options.contains}"!`,lt,null,[`"${this.options.contains}"`],this.id,(this.options.docs ?? null))
602
+ return false
603
+ }else if (typeof this.options.invertedContains != "undefined" && value.includes(this.options.invertedContains)){
604
+ checker.createMessage("opendiscord:string-inverted-contains","error",`This string is not allowed to contain "${this.options.invertedContains}"!`,lt,null,[`"${this.options.invertedContains}"`],this.id,(this.options.docs ?? null))
605
+ return false
606
+ }else if (typeof this.options.choices != "undefined" && !this.options.choices.includes(value)){
607
+ checker.createMessage("opendiscord:string-choices","error",`This string can only be one of the following values: "${this.options.choices.join(`", "`)}"!`,lt,null,[`"${this.options.choices.join(`", "`)}"`],this.id,(this.options.docs ?? null))
608
+ return false
609
+ }else if (this.options.lowercaseOnly && value !== value.toLowerCase()){
610
+ checker.createMessage("opendiscord:string-lowercase","error",`This string must be written in lowercase only!`,lt,null,[],this.id,(this.options.docs ?? null))
611
+ return false
612
+ }else if (this.options.uppercaseOnly && value !== value.toUpperCase()){
613
+ checker.createMessage("opendiscord:string-uppercase","error",`This string must be written in uppercase only!`,lt,null,[],this.id,(this.options.docs ?? null))
614
+ return false
615
+ }else if (this.options.noSpecialCharacters && !/^[A-Za-z0-9 ]*$/.test(value)){
616
+ checker.createMessage("opendiscord:string-special-characters","error",`This string is not allowed to contain any special characters! (a-z, 0-9 & space only)`,lt,null,[],this.id,(this.options.docs ?? null))
617
+ return false
618
+ }else if (this.options.withoutSpaces && value.includes(" ")){
619
+ checker.createMessage("opendiscord:string-no-spaces","error",`This string is not allowed to contain spaces!`,lt,null,[],this.id,(this.options.docs ?? null))
620
+ return false
621
+ }else if (typeof this.options.regex != "undefined" && !this.options.regex.test(value)){
622
+ checker.createMessage("opendiscord:string-regex","error","This string is invalid!",lt,null,[],this.id,(this.options.docs ?? null))
623
+ return false
624
+ }else{
625
+ //warnings
626
+ if ((this.options.capitalLetterWarning == "word" && !value.split(" ").every((word) => word.length == 0 || /^[^a-z].*/.test(word)))) checker.createMessage("opendiscord:string-capital-word","warning",`It's recommended that each word in this string starts with a capital letter!`,lt,null,[],this.id,(this.options.docs ?? null))
627
+ if ((this.options.capitalLetterWarning == "sentence" && !value.split(/ *[.?!] */).every((sentence) => sentence.length == 0 || /^[^a-z].*/.test(sentence)))) checker.createMessage("opendiscord:string-capital-sentence","warning",`It looks like some sentences in this string don't start with a capital letter!`,lt,null,[],this.id,(this.options.docs ?? null))
628
+ if (this.options.punctuationWarning && value.length > 0 && (!value.endsWith(".") && !value.endsWith("?") && !value.endsWith("!") && !value.endsWith("'") && !value.endsWith('"') && !value.endsWith(",") && !value.endsWith(";") && !value.endsWith(":") && !value.endsWith("="))) checker.createMessage("opendiscord:string-punctuation","warning",`It looks like the sentence in this string doesn't end with a punctuation mark!`,lt,null,[],this.id,(this.options.docs ?? null))
629
+
630
+ return super.check(checker,value,locationTrace)
631
+ }
632
+ }
633
+ }
634
+
635
+ /**## ODCheckerNumberStructureOptions `interface`
636
+ * This interface has the options for `ODCheckerNumberStructure`!
637
+ */
638
+ export interface ODCheckerNumberStructureOptions extends ODCheckerStructureOptions {
639
+ /**Is `NaN` (not a number) allowed? (`false` by default) */
640
+ nanAllowed?:boolean
641
+ /**The minimum length of this number */
642
+ minLength?:number,
643
+ /**The maximum length of this number */
644
+ maxLength?:number,
645
+ /**Set the required length of this number */
646
+ length?:number,
647
+ /**The minimum value of this number */
648
+ min?:number,
649
+ /**The maximum value of this number */
650
+ max?:number,
651
+ /**This number is required to match the value */
652
+ is?:number,
653
+ /**Only allow a multiple of ... starting at `this.offset` or 0 */
654
+ step?:number,
655
+ /**The offset for the step function. */
656
+ offset?:number,
657
+ /**This number needs to start with ... */
658
+ startsWith?:string,
659
+ /**This number needs to end with ... */
660
+ endsWith?:string,
661
+ /**This number needs to contain ... */
662
+ contains?:string,
663
+ /**This number is not allowed to contain ... */
664
+ invertedContains?:string,
665
+ /**You need to choose between ... */
666
+ choices?:number[],
667
+ /**Are numbers with a decimal value allowed? */
668
+ floatAllowed?:boolean,
669
+ /**Are negative numbers allowed (without zero) */
670
+ negativeAllowed?:boolean,
671
+ /**Are positive numers allowed (without zero) */
672
+ positiveAllowed?:boolean,
673
+ /**Is zero allowed? */
674
+ zeroAllowed?:boolean
675
+ }
676
+
677
+ /**## ODCheckerNumberStructure `class`
678
+ * This is an Open Discord config checker structure.
679
+ *
680
+ * This class will check for a number variable in a config file, customise it in the settings!
681
+ */
682
+ export class ODCheckerNumberStructure extends ODCheckerStructure {
683
+ declare options: ODCheckerNumberStructureOptions
684
+
685
+ constructor(id:ODValidId, options:ODCheckerNumberStructureOptions){
686
+ super(id,options)
687
+ }
688
+
689
+ check(checker:ODChecker, value:number, locationTrace:ODCheckerLocationTrace): boolean {
690
+ const lt = checker.locationTraceDeref(locationTrace)
691
+
692
+ //offset for step
693
+ const stepOffset = (typeof this.options.offset != "undefined") ? this.options.offset : 0
694
+
695
+ //check type & options
696
+ if (typeof value != "number"){
697
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: number!",lt,null,["number"],this.id,(this.options.docs ?? null))
698
+ return false
699
+ }else if (!this.options.nanAllowed && isNaN(value)){
700
+ checker.createMessage("opendiscord:number-nan","error",`This number can't be NaN (Not A Number)!`,lt,null,[],this.id,(this.options.docs ?? null))
701
+ return false
702
+ }else if (typeof this.options.minLength != "undefined" && value.toString().length < this.options.minLength){
703
+ checker.createMessage("opendiscord:number-too-short","error",`This number can't be shorter than ${this.options.minLength} characters!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null))
704
+ return false
705
+ }else if (typeof this.options.maxLength != "undefined" && value.toString().length > this.options.maxLength){
706
+ checker.createMessage("opendiscord:number-too-long","error",`This number can't be longer than ${this.options.maxLength} characters!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null))
707
+ return false
708
+ }else if (typeof this.options.length != "undefined" && value.toString().length !== this.options.length){
709
+ checker.createMessage("opendiscord:number-length-invalid","error",`This number needs to be ${this.options.length} characters long!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null))
710
+ return false
711
+ }else if (typeof this.options.min != "undefined" && value < this.options.min){
712
+ checker.createMessage("opendiscord:number-too-small","error",`This number needs to be at least ${this.options.min}!`,lt,null,[this.options.min.toString()],this.id,(this.options.docs ?? null))
713
+ return false
714
+ }else if (typeof this.options.max != "undefined" && value > this.options.max){
715
+ checker.createMessage("opendiscord:number-too-large","error",`This number needs to be at most ${this.options.max}!`,lt,null,[this.options.max.toString()],this.id,(this.options.docs ?? null))
716
+ return false
717
+ }else if (typeof this.options.is != "undefined" && value == this.options.is){
718
+ checker.createMessage("opendiscord:number-not-equal","error",`This number needs to be ${this.options.is}!`,lt,null,[this.options.is.toString()],this.id,(this.options.docs ?? null))
719
+ return false
720
+ }else if (typeof this.options.step != "undefined" && ((value - stepOffset) % this.options.step) !== 0){
721
+ if (stepOffset > 0) checker.createMessage("opendiscord:number-step-offset","error",`This number needs to be a multiple of ${this.options.step} starting with ${stepOffset}!`,lt,null,[this.options.step.toString(),stepOffset.toString()],this.id,(this.options.docs ?? null))
722
+ else checker.createMessage("opendiscord:number-step","error",`This number needs to be a multiple of ${this.options.step}!`,lt,null,[this.options.step.toString()],this.id,(this.options.docs ?? null))
723
+ return false
724
+ }else if (typeof this.options.startsWith != "undefined" && !value.toString().startsWith(this.options.startsWith)){
725
+ checker.createMessage("opendiscord:number-starts-with","error",`This number needs to start with "${this.options.startsWith}"!`,lt,null,[`"${this.options.startsWith}"`],this.id,(this.options.docs ?? null))
726
+ return false
727
+ }else if (typeof this.options.endsWith != "undefined" && !value.toString().endsWith(this.options.endsWith)){
728
+ checker.createMessage("opendiscord:number-ends-with","error",`This number needs to end with "${this.options.endsWith}"!`,lt,null,[`"${this.options.endsWith}"`],this.id,(this.options.docs ?? null))
729
+ return false
730
+ }else if (typeof this.options.contains != "undefined" && !value.toString().includes(this.options.contains)){
731
+ checker.createMessage("opendiscord:number-contains","error",`This number needs to contain "${this.options.contains}"!`,lt,null,[`"${this.options.contains}"`],this.id,(this.options.docs ?? null))
732
+ return false
733
+ }else if (typeof this.options.invertedContains != "undefined" && value.toString().includes(this.options.invertedContains)){
734
+ checker.createMessage("opendiscord:number-inverted-contains","error",`This number is not allowed to contain "${this.options.invertedContains}"!`,lt,null,[`"${this.options.invertedContains}"`],this.id,(this.options.docs ?? null))
735
+ return false
736
+ }else if (typeof this.options.choices != "undefined" && !this.options.choices.includes(value)){
737
+ checker.createMessage("opendiscord:number-choices","error",`This number can only be one of the following values: "${this.options.choices.join(`", "`)}"!`,lt,null,[`"${this.options.choices.join(`", "`)}"`],this.id,(this.options.docs ?? null))
738
+ return false
739
+ }else if (typeof this.options.floatAllowed != "undefined" && !this.options.floatAllowed && (value % 1) !== 0){
740
+ checker.createMessage("opendiscord:number-float","error","This number can't be a decimal!",lt,null,[],this.id,(this.options.docs ?? null))
741
+ return false
742
+ }else if (typeof this.options.negativeAllowed != "undefined" && !this.options.negativeAllowed && value < 0){
743
+ checker.createMessage("opendiscord:number-negative","error","This number can't be negative!",lt,null,[],this.id,(this.options.docs ?? null))
744
+ return false
745
+ }else if (typeof this.options.positiveAllowed != "undefined" && !this.options.positiveAllowed && value > 0){
746
+ checker.createMessage("opendiscord:number-positive","error","This number can't be positive!",lt,null,[],this.id,(this.options.docs ?? null))
747
+ return false
748
+ }else if (typeof this.options.zeroAllowed != "undefined" && !this.options.zeroAllowed && value === 0){
749
+ checker.createMessage("opendiscord:number-zero","error","This number can't be zero!",lt,null,[],this.id,(this.options.docs ?? null))
750
+ return false
751
+ }else return super.check(checker,value,locationTrace)
752
+ }
753
+ }
754
+
755
+ /**## ODCheckerBooleanStructureOptions `interface`
756
+ * This interface has the options for `ODCheckerBooleanStructure`!
757
+ */
758
+ export interface ODCheckerBooleanStructureOptions extends ODCheckerStructureOptions {
759
+ /**Is `true` allowed? */
760
+ trueAllowed?:boolean,
761
+ /**Is `false` allowed? */
762
+ falseAllowed?:boolean
763
+ }
764
+
765
+ /**## ODCheckerBooleanStructure `class`
766
+ * This is an Open Discord config checker structure.
767
+ *
768
+ * This class will check for a boolean variable in a config file, customise it in the settings!
769
+ */
770
+ export class ODCheckerBooleanStructure extends ODCheckerStructure {
771
+ declare options: ODCheckerBooleanStructureOptions
772
+
773
+ constructor(id:ODValidId, options:ODCheckerBooleanStructureOptions){
774
+ super(id,options)
775
+ }
776
+
777
+ check(checker:ODChecker, value:boolean, locationTrace:ODCheckerLocationTrace): boolean {
778
+ const lt = checker.locationTraceDeref(locationTrace)
779
+
780
+ //check type & options
781
+ if (typeof value != "boolean"){
782
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: boolean!",lt,null,["boolean"],this.id,(this.options.docs ?? null))
783
+ return false
784
+ }else if (typeof this.options.trueAllowed != "undefined" && !this.options.trueAllowed && value == true){
785
+ checker.createMessage("opendiscord:boolean-true","error","This boolean can't be true!",lt,null,[],this.id,(this.options.docs ?? null))
786
+ return false
787
+ }else if (typeof this.options.falseAllowed != "undefined" && !this.options.falseAllowed && value == false){
788
+ checker.createMessage("opendiscord:boolean-false","error","This boolean can't be false!",lt,null,[],this.id,(this.options.docs ?? null))
789
+ return false
790
+ }else return super.check(checker,value,locationTrace)
791
+ }
792
+ }
793
+
794
+ /**## ODCheckerArrayStructureOptions `interface`
795
+ * This interface has the options for `ODCheckerArrayStructure`!
796
+ */
797
+ export interface ODCheckerArrayStructureOptions extends ODCheckerStructureOptions {
798
+ /**The checker for all the properties in this array */
799
+ propertyChecker?:ODCheckerStructure,
800
+ /**Don't allow this array to be empty */
801
+ disableEmpty?:boolean,
802
+ /**This array is required to be empty */
803
+ emptyRequired?:boolean,
804
+ /**The minimum length of this array */
805
+ minLength?:number,
806
+ /**The maximum length of this array */
807
+ maxLength?:number,
808
+ /**The length of the array needs to be the same as this value */
809
+ length?:number,
810
+ /**Allow double values (only for `string`, `number` & `boolean`) */
811
+ allowDoubles?:boolean
812
+ /**Only allow these types in the array (for multi-type propertyCheckers) */
813
+ allowedTypes?:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[],
814
+ /**The name of the properties inside this array. Used in the GUI of the Interactive Setup CLI. */
815
+ cliDisplayPropertyName?:string
816
+ }
817
+
818
+ /**## ODCheckerArrayStructure `class`
819
+ * This is an Open Discord config checker structure.
820
+ *
821
+ * This class will check for an array variable in a config file, customise it in the settings!
822
+ */
823
+ export class ODCheckerArrayStructure extends ODCheckerStructure {
824
+ declare options: ODCheckerArrayStructureOptions
825
+
826
+ constructor(id:ODValidId, options:ODCheckerArrayStructureOptions){
827
+ super(id,options)
828
+ }
829
+
830
+ check(checker:ODChecker, value:Array<any>, locationTrace:ODCheckerLocationTrace): boolean {
831
+ const lt = checker.locationTraceDeref(locationTrace)
832
+
833
+ if (!Array.isArray(value)){
834
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: array!",lt,null,["array"],this.id,(this.options.docs ?? null))
835
+ return false
836
+ }else if (typeof this.options.disableEmpty != "undefined" && this.options.disableEmpty && value.length == 0){
837
+ checker.createMessage("opendiscord:array-empty-disabled","error","This array isn't allowed to be empty!",lt,null,[],this.id,(this.options.docs ?? null))
838
+ return false
839
+ }else if (typeof this.options.emptyRequired != "undefined" && this.options.emptyRequired && value.length != 0){
840
+ checker.createMessage("opendiscord:array-empty-required","error","This array is required to be empty!",lt,null,[],this.id,(this.options.docs ?? null))
841
+ return false
842
+ }else if (typeof this.options.minLength != "undefined" && value.length < this.options.minLength){
843
+ checker.createMessage("opendiscord:array-too-short","error",`This array needs to have a length of at least ${this.options.minLength}!`,lt,null,[this.options.minLength.toString()],this.id,(this.options.docs ?? null))
844
+ return false
845
+ }else if (typeof this.options.maxLength != "undefined" && value.length > this.options.maxLength){
846
+ checker.createMessage("opendiscord:array-too-long","error",`This array needs to have a length of at most ${this.options.maxLength}!`,lt,null,[this.options.maxLength.toString()],this.id,(this.options.docs ?? null))
847
+ return false
848
+ }else if (typeof this.options.length != "undefined" && value.length == this.options.length){
849
+ checker.createMessage("opendiscord:array-length-invalid","error",`This array needs to have a length of ${this.options.length}!`,lt,null,[this.options.length.toString()],this.id,(this.options.docs ?? null))
850
+ return false
851
+ }else if (typeof this.options.allowedTypes != "undefined" && !this.#arrayAllowedTypesCheck(value,this.options.allowedTypes)){
852
+ checker.createMessage("opendiscord:array-invalid-types","error",`This array can only contain the following types: ${this.options.allowedTypes.join(", ")}!`,lt,null,[this.options.allowedTypes.join(", ").toString()],this.id,(this.options.docs ?? null))
853
+ return false
854
+ }else if (typeof this.options.allowDoubles != "undefined" && !this.options.allowDoubles && this.#arrayHasDoubles(value)){
855
+ checker.createMessage("opendiscord:array-double","error","This array doesn't allow the same value twice!",lt,null,[],this.id,(this.options.docs ?? null))
856
+ return false
857
+ }else{
858
+ //check all properties
859
+ let localQuit = false
860
+ if (this.options.propertyChecker) value.forEach((property,index) => {
861
+ if (!this.options.propertyChecker) return
862
+
863
+ const localLt = checker.locationTraceDeref(lt)
864
+ localLt.push(index)
865
+
866
+ if (!this.options.propertyChecker.check(checker,property,localLt)) localQuit = true
867
+ })
868
+
869
+ //return false if invalid properties
870
+ if (localQuit){
871
+ checker.quit = true
872
+ return false
873
+ }else return super.check(checker,value,locationTrace)
874
+ }
875
+ }
876
+
877
+ /**Check this array for the allowed types */
878
+ #arrayAllowedTypesCheck(array:any[],allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]): boolean {
879
+ //return TRUE if ALL values are valid
880
+ return !array.some((value) => {
881
+ if (allowedTypes.includes("string") && typeof value == "string"){
882
+ return false //this value is valid
883
+ }else if (allowedTypes.includes("number") && typeof value == "number"){
884
+ return false //this value is valid
885
+ }else if (allowedTypes.includes("boolean") && typeof value == "boolean"){
886
+ return false //this value is valid
887
+ }else if (allowedTypes.includes("object") && typeof value == "object"){
888
+ return false //this value is valid
889
+ }else if (allowedTypes.includes("array") && Array.isArray(value)){
890
+ return false //this value is valid
891
+ }else if (allowedTypes.includes("null") && value === null){
892
+ return false //this value is valid
893
+ }else if (allowedTypes.includes("other")){
894
+ return false //this value is valid
895
+ }else{
896
+ return true //this value is invalid
897
+ }
898
+ })
899
+ }
900
+ /**Check this array for doubles */
901
+ #arrayHasDoubles(array:any[]): boolean {
902
+ const alreadyFound: string[] = []
903
+ let hasDoubles = false
904
+ array.forEach((value) => {
905
+ if (alreadyFound.includes(value)) hasDoubles = true
906
+ else alreadyFound.push(value)
907
+ })
908
+
909
+ return hasDoubles
910
+ }
911
+ }
912
+
913
+ /**## ODCheckerNullStructureOptions `interface`
914
+ * This interface has the options for `ODCheckerNullStructure`!
915
+ */
916
+ export interface ODCheckerNullStructureOptions extends ODCheckerStructureOptions {
917
+ /**Is the value allowed to be null */
918
+ nullAllowed?:boolean
919
+ }
920
+
921
+ /**## ODCheckerNullStructure `class`
922
+ * This is an Open Discord config checker structure.
923
+ *
924
+ * This class will check for a null variable in a config file, customise it in the settings!
925
+ */
926
+ export class ODCheckerNullStructure extends ODCheckerStructure {
927
+ declare options: ODCheckerNullStructureOptions
928
+
929
+ constructor(id:ODValidId, options:ODCheckerNullStructureOptions){
930
+ super(id,options)
931
+ }
932
+
933
+ check(checker:ODChecker, value:null, locationTrace:ODCheckerLocationTrace): boolean {
934
+ const lt = checker.locationTraceDeref(locationTrace)
935
+
936
+ //check type & options
937
+ if (typeof this.options.nullAllowed != "undefined" && !this.options.nullAllowed && value == null){
938
+ checker.createMessage("opendiscord:null-invalid","error","This property can't be null!",lt,null,[],this.id,(this.options.docs ?? null))
939
+ return false
940
+ }else if (value !== null){
941
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: null!",lt,null,["null"],this.id,(this.options.docs ?? null))
942
+ return false
943
+ }else return super.check(checker,value,locationTrace)
944
+ }
945
+ }
946
+
947
+ /**## ODCheckerTypeSwitchStructureOptions `interface`
948
+ * This interface has the options for `ODCheckerTypeSwitchStructure`!
949
+ */
950
+ export interface ODCheckerTypeSwitchStructureOptions extends ODCheckerStructureOptions {
951
+ /**A checker that will always run (replaces all other checkers) */
952
+ all?:ODCheckerStructure,
953
+ /**A checker when the property is a string */
954
+ string?:ODCheckerStringStructure,
955
+ /**A checker when the property is a number */
956
+ number?:ODCheckerNumberStructure,
957
+ /**A checker when the property is a boolean */
958
+ boolean?:ODCheckerBooleanStructure,
959
+ /**A checker when the property is null */
960
+ null?:ODCheckerNullStructure,
961
+ /**A checker when the property is an array */
962
+ array?:ODCheckerArrayStructure,
963
+ /**A checker when the property is an object */
964
+ object?:ODCheckerObjectStructure,
965
+ /**A checker when the property is something else */
966
+ other?:ODCheckerStructure,
967
+ /**A list of allowed types */
968
+ allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]
969
+ }
970
+
971
+ /**## ODCheckerTypeSwitchStructure `class`
972
+ * This is an Open Discord config checker structure.
973
+ *
974
+ * This class will switch checkers based on the type of the variable in a config file, customise it in the settings!
975
+ */
976
+ export class ODCheckerTypeSwitchStructure extends ODCheckerStructure {
977
+ declare options: ODCheckerTypeSwitchStructureOptions
978
+
979
+ constructor(id:ODValidId, options:ODCheckerTypeSwitchStructureOptions){
980
+ super(id,options)
981
+ }
982
+
983
+ check(checker:ODChecker, value:any, locationTrace:ODCheckerLocationTrace): boolean {
984
+ const lt = checker.locationTraceDeref(locationTrace)
985
+
986
+ if (this.options.all){
987
+ return this.options.all.check(checker,value,lt)
988
+
989
+ }else if (this.options.string && typeof value == "string"){
990
+ return this.options.string.check(checker,value,lt)
991
+
992
+ }else if (this.options.number && typeof value == "number"){
993
+ return this.options.number.check(checker,value,lt)
994
+
995
+ }else if (this.options.boolean && typeof value == "boolean"){
996
+ return this.options.boolean.check(checker,value,lt)
997
+
998
+ }else if (this.options.array && Array.isArray(value)){
999
+ return this.options.array.check(checker,value,lt)
1000
+
1001
+ }else if (this.options.null && value === null){
1002
+ return this.options.null.check(checker,value,lt)
1003
+
1004
+ }else if (this.options.object && typeof value == "object"){
1005
+ return this.options.object.check(checker,value,lt)
1006
+
1007
+ }else if (this.options.other){
1008
+ return this.options.other.check(checker,value,lt)
1009
+
1010
+ }else if (this.options.allowedTypes && this.options.allowedTypes.length > 0){
1011
+ checker.createMessage("opendiscord:switch-invalid-type","error",`This needs to be one of the following types: ${this.options.allowedTypes.join(", ")}!`,lt,null,[this.options.allowedTypes.join(", ")],this.id,(this.options.docs ?? null))
1012
+ return false
1013
+ }else return super.check(checker,value,locationTrace)
1014
+ }
1015
+ }
1016
+
1017
+ /**## ODCheckerObjectSwitchStructureOptions `interface`
1018
+ * This interface has the options for `ODCheckerObjectSwitchStructure`!
1019
+ */
1020
+ export interface ODCheckerObjectSwitchStructureOptions extends ODCheckerStructureOptions {
1021
+ /**An array of object checkers with their name, properties & priority. */
1022
+ objects:{
1023
+ /**The properties to match for this checker to be used. */
1024
+ properties:{key:string, value:boolean|string|number}[],
1025
+ /**The name for this object type (used in rendering) */
1026
+ name:string,
1027
+ /**The higher the priority, the earlier this checker will be tested. */
1028
+ priority:number,
1029
+ /**The object checker used once the properties have been matched. */
1030
+ checker:ODCheckerObjectStructure
1031
+ }[]
1032
+ }
1033
+
1034
+ /**## ODCheckerObjectSwitchStructure `class`
1035
+ * This is an Open Discord config checker structure.
1036
+ *
1037
+ * This class will switch object checkers based on a variable match in one of the objects, customise it in the settings!
1038
+ */
1039
+ export class ODCheckerObjectSwitchStructure extends ODCheckerStructure {
1040
+ declare options: ODCheckerObjectSwitchStructureOptions
1041
+
1042
+ constructor(id:ODValidId, options:ODCheckerObjectSwitchStructureOptions){
1043
+ super(id,options)
1044
+ }
1045
+
1046
+ check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean {
1047
+ const lt = checker.locationTraceDeref(locationTrace)
1048
+
1049
+ if (this.options.objects){
1050
+ //check type & options
1051
+ if (typeof value != "object"){
1052
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null))
1053
+ return false
1054
+ }
1055
+
1056
+ //sort objects
1057
+ const sortedObjects = this.options.objects.sort((a,b) => {
1058
+ if (a.priority < b.priority) return -1
1059
+ else if (a.priority > b.priority) return 1
1060
+ else return 0
1061
+ })
1062
+
1063
+
1064
+ //check objects
1065
+ let localQuit = false
1066
+ let didSelectObject = false
1067
+ sortedObjects.forEach((obj) => {
1068
+ if (!obj.properties.some((p) => value[p.key] !== p.value)){
1069
+ didSelectObject = true
1070
+ if (!obj.checker.check(checker,value,lt)) localQuit = true
1071
+ }
1072
+ })
1073
+
1074
+ //do local quit or check custom function
1075
+ if (!didSelectObject){
1076
+ checker.createMessage("opendiscord:object-switch-invalid-type","error",`This object needs to be one of the following types: ${this.options.objects.map((obj) => obj.name).join(", ")}!`,lt,null,[this.options.objects.map((obj) => obj.name).join(", ")],this.id,(this.options.docs ?? null))
1077
+ return false
1078
+ }else if (localQuit){
1079
+ return false
1080
+ }else return super.check(checker,value,locationTrace)
1081
+ }else return super.check(checker,value,locationTrace)
1082
+ }
1083
+ }
1084
+
1085
+ /**## ODCheckerEnabledObjectStructureOptions `interface`
1086
+ * This interface has the options for `ODCheckerEnabledObjectStructure`!
1087
+ */
1088
+ export interface ODCheckerEnabledObjectStructureOptions extends ODCheckerStructureOptions {
1089
+ /**The name of the property to match the `enabledValue`. */
1090
+ property:string,
1091
+ /**The value of the property to be enabled. (e.g. `true`) */
1092
+ enabledValue:boolean|string|number,
1093
+ /**The object checker to use once the property has been matched. */
1094
+ checker:ODCheckerObjectStructure
1095
+ }
1096
+
1097
+ /**## ODCheckerEnabledObjectStructure `class`
1098
+ * This is an Open Discord config checker structure.
1099
+ *
1100
+ * This class will enable an object checker based on a variable match in the object, customise it in the settings!
1101
+ */
1102
+ export class ODCheckerEnabledObjectStructure extends ODCheckerStructure {
1103
+ declare options: ODCheckerEnabledObjectStructureOptions
1104
+
1105
+ constructor(id:ODValidId, options:ODCheckerEnabledObjectStructureOptions){
1106
+ super(id,options)
1107
+ }
1108
+
1109
+ check(checker:ODChecker, value:object, locationTrace:ODCheckerLocationTrace): boolean {
1110
+ const lt = checker.locationTraceDeref(locationTrace)
1111
+
1112
+ if (typeof value != "object"){
1113
+ //value isn't an object
1114
+ checker.createMessage("opendiscord:invalid-type","error","This property needs to be the type: object!",lt,null,["object"],this.id,(this.options.docs ?? null))
1115
+ return false
1116
+
1117
+ }else if (this.options.property && typeof value[this.options.property] == "undefined"){
1118
+ //property doesn't exist
1119
+ checker.createMessage("opendiscord:property-missing","error",`The property "${this.options.property}" is mising from this object!`,lt,null,[`"${this.options.property}"`],this.id,(this.options.docs ?? null))
1120
+ return false
1121
+
1122
+ }else if (this.options.property && value[this.options.property] === (typeof this.options.enabledValue == "undefined" ? true : this.options.enabledValue)){
1123
+ //this object is enabled
1124
+ if (this.options.checker) return this.options.checker.check(checker,value,lt)
1125
+ else return super.check(checker,value,locationTrace)
1126
+
1127
+ }else{
1128
+ //this object is disabled
1129
+ if (this.options.property) checker.createMessage("opendiscord:object-disabled","info",`This object is disabled, enable it using "${this.options.property}"!`,lt,null,[`"${this.options.property}"`],this.id,(this.options.docs ?? null))
1130
+ return super.check(checker,value,locationTrace)
1131
+ }
1132
+ }
1133
+ }
1134
+
1135
+ /**## ODCheckerCustomStructure_DiscordId `class`
1136
+ * This is an Open Discord custom checker structure.
1137
+ *
1138
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1139
+ * You can compare it to a blueprint for a specific checker.
1140
+ *
1141
+ * **This custom checker is made for discord ids (channel, user, role, ...)**
1142
+ */
1143
+ export class ODCheckerCustomStructure_DiscordId extends ODCheckerStringStructure {
1144
+ /**The type of id (used in rendering) */
1145
+ readonly type: ODDiscordIdType
1146
+ /**Is this id allowed to be empty */
1147
+ readonly emptyAllowed: boolean
1148
+ /**Extra matches (value will also be valid when one of these options match) */
1149
+ readonly extraOptions: string[]
1150
+
1151
+ constructor(id:ODValidId, type:ODDiscordIdType, emptyAllowed:boolean, extraOptions:string[], options?:ODCheckerStringStructureOptions){
1152
+ //add premade custom structure checker
1153
+ const newOptions = options ?? {}
1154
+ newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
1155
+ const lt = checker.locationTraceDeref(locationTrace)
1156
+
1157
+ if (typeof value != "string") return false
1158
+ else if ((!emptyAllowed && value.length < 15) || value.length > 50 || !/^[0-9]*$/.test(value)){
1159
+ if (!(extraOptions.length > 0 && extraOptions.some((opt) => opt == value))){
1160
+ //value is not an id & not one of the extra options
1161
+ if (extraOptions.length > 0) checker.createMessage("opendiscord:discord-invalid-id-options","error",`This is an invalid discord ${type} id! You can also use one of these: ${extraOptions.join(", ")}!`,lt,null,[type,extraOptions.join(", ")],this.id,(this.options.docs ?? null))
1162
+ else checker.createMessage("opendiscord:discord-invalid-id","error",`This is an invalid discord ${type} id!`,lt,null,[type],this.id,(this.options.docs ?? null))
1163
+ return false
1164
+ }else return true
1165
+ }
1166
+ return true
1167
+ }
1168
+ super(id,newOptions)
1169
+ this.type = type
1170
+ this.emptyAllowed = emptyAllowed
1171
+ this.extraOptions = extraOptions
1172
+ }
1173
+ }
1174
+
1175
+ /**## ODCheckerCustomStructure_DiscordIdArray `class`
1176
+ * This is an Open Discord custom checker structure.
1177
+ *
1178
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1179
+ * You can compare it to a blueprint for a specific checker.
1180
+ *
1181
+ * **This custom checker is made for discord id arrays (channel, user, role, ...)**
1182
+ */
1183
+ export class ODCheckerCustomStructure_DiscordIdArray extends ODCheckerArrayStructure {
1184
+ /**The type of id (used in rendering) */
1185
+ readonly type: ODDiscordIdType
1186
+ /**Extra matches (value will also be valid when one of these options match) */
1187
+ readonly extraOptions: string[]
1188
+
1189
+ constructor(id:ODValidId, type:ODDiscordIdType, extraOptions:string[], options?:ODCheckerArrayStructureOptions, idOptions?:ODCheckerStringStructureOptions){
1190
+ //add premade custom structure checker
1191
+ const newOptions = options ?? {}
1192
+ newOptions.propertyChecker = new ODCheckerCustomStructure_DiscordId(id,type,false,extraOptions,idOptions)
1193
+ super(id,newOptions)
1194
+ this.type = type
1195
+ this.extraOptions = extraOptions
1196
+ }
1197
+ }
1198
+
1199
+ /**## ODCheckerCustomStructure_DiscordToken `class`
1200
+ * This is an Open Discord custom checker structure.
1201
+ *
1202
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1203
+ * You can compare it to a blueprint for a specific checker.
1204
+ *
1205
+ * **This custom checker is made for a discord (auth) token**
1206
+ */
1207
+ export class ODCheckerCustomStructure_DiscordToken extends ODCheckerStringStructure {
1208
+ constructor(id:ODValidId, options?:ODCheckerStringStructureOptions){
1209
+ //add premade custom structure checker
1210
+ const newOptions = options ?? {}
1211
+ newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
1212
+ const lt = checker.locationTraceDeref(locationTrace)
1213
+
1214
+ if (typeof value != "string" || !/^[A-Za-z0-9-_\.]+$/.test(value)){
1215
+ checker.createMessage("opendiscord:discord-invalid-token","error","This is an invalid discord token (syntactically)!",lt,null,[],this.id,(this.options.docs ?? null))
1216
+ return false
1217
+ }
1218
+ return true
1219
+ }
1220
+ super(id,newOptions)
1221
+ }
1222
+ }
1223
+
1224
+ /**## ODCheckerCustomStructure_DiscordToken `class`
1225
+ * This is an Open Discord custom checker structure.
1226
+ *
1227
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1228
+ * You can compare it to a blueprint for a specific checker.
1229
+ *
1230
+ * **This custom checker is made for a hex color**
1231
+ */
1232
+ export class ODCheckerCustomStructure_HexColor extends ODCheckerStringStructure {
1233
+ /**When enabled, you are also allowed to use `#fff` instead of `#ffffff` */
1234
+ readonly allowShortForm: boolean
1235
+ /**Allow this hex color to be empty. */
1236
+ readonly emptyAllowed: boolean
1237
+
1238
+ constructor(id:ODValidId, allowShortForm:boolean, emptyAllowed:boolean, options?:ODCheckerStringStructureOptions){
1239
+ //add premade custom structure checker
1240
+ const newOptions = options ?? {}
1241
+ newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
1242
+ const lt = checker.locationTraceDeref(locationTrace)
1243
+
1244
+ if (typeof value != "string") return false
1245
+ else if (emptyAllowed && value.length == 0){
1246
+ return true
1247
+ }else if ((!allowShortForm && !/^#[a-fA-F0-9]{6}$/.test(value)) || (allowShortForm && !/^#[a-fA-F0-9]{6}$/.test(value) && !/^#[a-fA-F0-9]{3}$/.test(value))){
1248
+ checker.createMessage("opendiscord:color-invalid","error","This is an invalid hex color!",lt,null,[],this.id,(this.options.docs ?? null))
1249
+ return false
1250
+ }else return true
1251
+ }
1252
+ super(id,newOptions)
1253
+ this.allowShortForm = allowShortForm
1254
+ this.emptyAllowed = emptyAllowed
1255
+ }
1256
+ }
1257
+
1258
+ /**## ODCheckerCustomStructure_EmojiString `class`
1259
+ * This is an Open Discord custom checker structure.
1260
+ *
1261
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1262
+ * You can compare it to a blueprint for a specific checker.
1263
+ *
1264
+ * **This custom checker is made for an emoji (string)**
1265
+ */
1266
+ export class ODCheckerCustomStructure_EmojiString extends ODCheckerStringStructure {
1267
+ /**The minimum amount of emojis required (0 to allow empty) */
1268
+ readonly minLength: number
1269
+ /**The maximum amount of emojis allowed */
1270
+ readonly maxLength: number
1271
+ /**Allow custom discord emoji ids (`<:12345678910:emoji_name>`) */
1272
+ readonly allowCustomDiscordEmoji: boolean
1273
+
1274
+ constructor(id:ODValidId, minLength:number, maxLength:number, allowCustomDiscordEmoji:boolean, options?:ODCheckerStringStructureOptions){
1275
+ //add premade custom structure checker
1276
+ const newOptions = options ?? {}
1277
+ newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
1278
+ const lt = checker.locationTraceDeref(locationTrace)
1279
+ if (typeof value != "string") return false
1280
+
1281
+ const discordEmojiSplitter = /(?:<a?:[^:]*:[0-9]+>)/g
1282
+ const splitted = value.split(discordEmojiSplitter)
1283
+ const discordEmojiAmount = splitted.length-1
1284
+ const unicodeEmojiAmount = [...new Intl.Segmenter().segment(splitted.join(""))].length
1285
+ const emojiAmount = discordEmojiAmount+unicodeEmojiAmount
1286
+
1287
+ if (emojiAmount < minLength){
1288
+ checker.createMessage("opendiscord:emoji-too-short","error",`This string needs to have at least ${minLength} emoji's!`,lt,null,[maxLength.toString()],this.id,(this.options.docs ?? null))
1289
+ return false
1290
+ }else if (emojiAmount > maxLength){
1291
+ checker.createMessage("opendiscord:emoji-too-long","error",`This string needs to have at most ${maxLength} emoji's!`,lt,null,[maxLength.toString()],this.id,(this.options.docs ?? null))
1292
+ return false
1293
+ }else if (!allowCustomDiscordEmoji && /<a?:[^:]*:[0-9]+>/.test(value)){
1294
+ checker.createMessage("opendiscord:emoji-custom","error",`This emoji can't be a custom discord emoji!`,lt,null,[],this.id,(this.options.docs ?? null))
1295
+ return false
1296
+ }else if (!/^(?:\p{Emoji}|\p{Emoji_Component}|(?:<a?:[^:]*:[0-9]+>))*$/u.test(value)){
1297
+ checker.createMessage("opendiscord:emoji-invalid","error","This is an invalid emoji!",lt,null,[],this.id,(this.options.docs ?? null))
1298
+ return false
1299
+ }
1300
+ return true
1301
+ }
1302
+ super(id,newOptions)
1303
+ this.minLength = minLength
1304
+ this.maxLength = maxLength
1305
+ this.allowCustomDiscordEmoji = allowCustomDiscordEmoji
1306
+ }
1307
+ }
1308
+
1309
+ /**## ODCheckerCustomStructureOptions_UrlString `interface`
1310
+ * This interface has the options for `ODCheckerCustomStructure_UrlString`!
1311
+ */
1312
+ export interface ODCheckerCustomStructureOptions_UrlString {
1313
+ /**Allow urls with `http://` instead of `https://` */
1314
+ allowHttp?:boolean
1315
+ /**Allowed hostnames (string or regex) => will match domain + subdomain */
1316
+ allowedHostnames?: (string|RegExp)[]
1317
+ /**Allowed extentions (string) => will match the end of the url (`.png`,`.svg`,...) */
1318
+ allowedExtensions?: string[]
1319
+ /**Allowed paths (string or regex) => will match path + extension (not domain + subdomain) */
1320
+ allowedPaths?: (string|RegExp)[],
1321
+ /**A regex that will be executed on the entire url (including search params, protcol, domain, ...) */
1322
+ regex?:RegExp
1323
+ }
1324
+
1325
+ /**## ODCheckerCustomStructure_UrlString `class`
1326
+ * This is an Open Discord custom checker structure.
1327
+ *
1328
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1329
+ * You can compare it to a blueprint for a specific checker.
1330
+ *
1331
+ * **This custom checker is made for a URL (string)**
1332
+ */
1333
+ export class ODCheckerCustomStructure_UrlString extends ODCheckerStringStructure {
1334
+ /**The settings for this url */
1335
+ readonly urlSettings: ODCheckerCustomStructureOptions_UrlString
1336
+ /**Is this url allowed to be empty? */
1337
+ readonly emptyAllowed: boolean
1338
+
1339
+ constructor(id:ODValidId, emptyAllowed:boolean, urlSettings:ODCheckerCustomStructureOptions_UrlString, options?:ODCheckerStringStructureOptions){
1340
+ //add premade custom structure checker
1341
+ const newOptions = options ?? {}
1342
+ newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
1343
+ const lt = checker.locationTraceDeref(locationTrace)
1344
+
1345
+ if (typeof value != "string") return false
1346
+ else if (emptyAllowed && value.length == 0){
1347
+ return true
1348
+ }else if (!this.#urlIsValid(value)){
1349
+ checker.createMessage("opendiscord:url-invalid","error","This url is invalid!",lt,null,[],this.id,(this.options.docs ?? null))
1350
+ return false
1351
+ }else if (typeof this.urlSettings.allowHttp != "undefined" && !this.urlSettings.allowHttp && !/^(https:\/\/)/.test(value)){
1352
+ checker.createMessage("opendiscord:url-invalid-http","error","This url can only use the https:// protocol!",lt,null,[],this.id,(this.options.docs ?? null))
1353
+ return false
1354
+ }else if (!/^(http(s)?:\/\/)/.test(value)){
1355
+ checker.createMessage("opendiscord:url-invalid-protocol","error","This url can only use the http:// & https:// protocols!",lt,null,[],this.id,(this.options.docs ?? null))
1356
+ return false
1357
+ }else if (typeof this.urlSettings.allowedHostnames != "undefined" && !this.#urlHasValidHostname(value,this.urlSettings.allowedHostnames)){
1358
+ checker.createMessage("opendiscord:url-invalid-hostname","error","This url has a disallowed hostname!",lt,null,[],this.id,(this.options.docs ?? null))
1359
+ return false
1360
+ }else if (typeof this.urlSettings.allowedExtensions != "undefined" && !this.#urlHasValidExtension(value,this.urlSettings.allowedExtensions)){
1361
+ checker.createMessage("opendiscord:url-invalid-extension","error",`This url has an invalid extension! Choose between: ${this.urlSettings.allowedExtensions.join(", ")}!"`,lt,null,[this.urlSettings.allowedExtensions.join(", ")],this.id,(this.options.docs ?? null))
1362
+ return false
1363
+ }else if (typeof this.urlSettings.allowedPaths != "undefined" && !this.#urlHasValidPath(value,this.urlSettings.allowedPaths)){
1364
+ checker.createMessage("opendiscord:url-invalid-path","error","This url has an invalid path!",lt,null,[],this.id,(this.options.docs ?? null))
1365
+ return false
1366
+ }else if (typeof this.urlSettings.regex != "undefined" && !this.urlSettings.regex.test(value)){
1367
+ checker.createMessage("opendiscord:url-invalid","error","This url is invalid!",lt,null,[],this.id,(this.options.docs ?? null))
1368
+ return false
1369
+ }else return true
1370
+ }
1371
+ super(id,newOptions)
1372
+ this.urlSettings = urlSettings
1373
+ this.emptyAllowed = emptyAllowed
1374
+ }
1375
+
1376
+ /**Check for the hostname */
1377
+ #urlHasValidHostname(url:string,hostnames:(string|RegExp)[]): boolean {
1378
+ try {
1379
+ const hostname = new URL(url).hostname
1380
+ return hostnames.some((rule) => {
1381
+ if (typeof rule == "string"){
1382
+ return rule == hostname
1383
+ }else{
1384
+ return rule.test(hostname)
1385
+ }
1386
+ })
1387
+
1388
+ }catch{
1389
+ return false
1390
+ }
1391
+ }
1392
+ /**Check for the extension */
1393
+ #urlHasValidExtension(url:string,extensions:string[]): boolean {
1394
+ try {
1395
+ const path = new URL(url).pathname
1396
+ return extensions.some((rule) => {
1397
+ return path.endsWith(rule)
1398
+ })
1399
+ }catch{
1400
+ return false
1401
+ }
1402
+ }
1403
+ /**Check for the path */
1404
+ #urlHasValidPath(url:string,paths:(string|RegExp)[]): boolean {
1405
+ try {
1406
+ const path = new URL(url).pathname
1407
+ return paths.some((rule) => {
1408
+ if (typeof rule == "string"){
1409
+ return rule == path
1410
+ }else{
1411
+ return rule.test(path)
1412
+ }
1413
+ })
1414
+ }catch{
1415
+ return false
1416
+ }
1417
+ }
1418
+ /**Do general syntax check on url */
1419
+ #urlIsValid(url:string){
1420
+ try {
1421
+ new URL(url)
1422
+ return true
1423
+ }catch{
1424
+ return false
1425
+ }
1426
+ }
1427
+ }
1428
+
1429
+ /**## ODCheckerCustomStructure_UniqueId `class`
1430
+ * This is an Open Discord custom checker structure.
1431
+ *
1432
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1433
+ * You can compare it to a blueprint for a specific checker.
1434
+ *
1435
+ * **This custom checker is made for a unique id (per source & scope)**
1436
+ */
1437
+ export class ODCheckerCustomStructure_UniqueId extends ODCheckerStringStructure {
1438
+ /**The source of this unique id (generally the plugin name or `opendiscord`) */
1439
+ readonly source: string
1440
+ /**The scope of this unique id (id needs to be unique in this scope) */
1441
+ readonly scope: string
1442
+
1443
+ constructor(id:ODValidId, source:string, scope:string, options?:ODCheckerStringStructureOptions){
1444
+ //add premade custom structure checker
1445
+ const newOptions = options ?? {}
1446
+ newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
1447
+ const lt = checker.locationTraceDeref(locationTrace)
1448
+
1449
+ if (typeof value != "string") return false
1450
+ const uniqueArray: string[] = (checker.storage.get(source,scope) === null) ? [] : checker.storage.get(source,scope)
1451
+ if (uniqueArray.includes(value)){
1452
+ //unique id already exists => throw error
1453
+ checker.createMessage("opendiscord:id-not-unique","error","This id isn't unique, use another id instead!",lt,null,[],this.id,(this.options.docs ?? null))
1454
+ return false
1455
+ }else{
1456
+ //unique id doesn't exists => add to list
1457
+ uniqueArray.push(value)
1458
+ checker.storage.set(source,scope,uniqueArray)
1459
+ return true
1460
+ }
1461
+ }
1462
+ super(id,newOptions)
1463
+ this.source = source
1464
+ this.scope = scope
1465
+ }
1466
+ }
1467
+
1468
+ /**## ODCheckerCustomStructure_UniqueIdArray `class`
1469
+ * This is an Open Discord custom checker structure.
1470
+ *
1471
+ * This class extends a primitive config checker & adds another layer of checking in the `custom` function.
1472
+ * You can compare it to a blueprint for a specific checker.
1473
+ *
1474
+ * **This custom checker is made for a unique id array (per source & scope)**
1475
+ */
1476
+ export class ODCheckerCustomStructure_UniqueIdArray extends ODCheckerArrayStructure {
1477
+ /**The source to read unique ids (generally the plugin name or `opendiscord`) */
1478
+ readonly source: string
1479
+ /**The scope to read unique ids (id needs to be unique in this scope) */
1480
+ readonly scope: string
1481
+ /**The scope to push unique ids when used in this array! */
1482
+ readonly usedScope: string|null
1483
+
1484
+ constructor(id:ODValidId, source:string, scope:string, usedScope?:string, options?:ODCheckerArrayStructureOptions, idOptions?:Omit<ODCheckerStringStructureOptions,"minLength"|"custom">){
1485
+ //add premade custom structure checker
1486
+ const newOptions = options ?? {}
1487
+ newOptions.propertyChecker = new ODCheckerStringStructure("opendiscord:unique-id",{...(idOptions ?? {}),minLength:1,custom:(checker,value,locationTrace,locationId,locationDocs) => {
1488
+ if (typeof value != "string") return false
1489
+ const localLt = checker.locationTraceDeref(locationTrace)
1490
+ localLt.pop()
1491
+
1492
+ const uniqueArray: string[] = checker.storage.get(source,scope) ?? []
1493
+ if (uniqueArray.includes(value)){
1494
+ //exists
1495
+ if (usedScope){
1496
+ const current: string[] = checker.storage.get(source,usedScope) ?? []
1497
+ current.push(value)
1498
+ checker.storage.set(source,usedScope,current)
1499
+ }
1500
+ return true
1501
+ }else{
1502
+ //doesn't exist
1503
+ checker.createMessage("opendiscord:id-non-existent","error",`The id "${value}" doesn't exist!`,localLt,null,[`"${value}"`],locationId,locationDocs)
1504
+ return false
1505
+ }
1506
+ }})
1507
+ super(id,newOptions)
1508
+ this.source = source
1509
+ this.scope = scope
1510
+ this.usedScope = usedScope ?? null
1511
+ }
1512
+ }
1513
+
1514
+ /*TEMPLATE!!!!
1515
+ export interface ODCheckerTemplateStructureOptions extends ODCheckerStructureOptions {
1516
+
1517
+ }
1518
+ export class ODCheckerTemplateStructure extends ODCheckerStructure {
1519
+ declare options: ODCheckerTemplateStructureOptions
1520
+
1521
+ constructor(id:ODValidId, options:ODCheckerTemplateStructureOptions){
1522
+ super(id,options)
1523
+ }
1524
+
1525
+ check(checker:ODChecker, value:any, locationTrace:ODCheckerLocationTrace): boolean {
1526
+ const lt = checker.locationTraceDeref(locationTrace)
1527
+
1528
+ return super.check(checker,value,locationTrace)
1529
+ }
1530
+ }
1531
+ */
1532
+ /*CUSTOM TEMPLATE!!!!
1533
+ export class ODCheckerCustomStructure_Template extends ODCheckerTemplateStructure {
1534
+ idk: string
1535
+
1536
+ constructor(id:ODValidId, idk:string, options?:ODCheckerStringStructureOptions){
1537
+ //add premade custom structure checker
1538
+ const newOptions = options ?? {}
1539
+ newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
1540
+ const lt = checker.locationTraceDeref(locationTrace)
1541
+
1542
+ //do custom check & push error message. Return true if correct
1543
+ return boolean
1544
+ }
1545
+ super(id,newOptions)
1546
+ this.idk = idk
1547
+ }
1548
+ }
1549
+ */