Merge branch 'dev-v4.1.0' into dev

This commit is contained in:
Jasper
2025-04-28 18:30:21 +02:00
committed by GitHub
21 changed files with 2094 additions and 362 deletions
+5 -5
View File
@@ -19,11 +19,11 @@
</p>
<p align="center">
Open Ticket is the most advanced & customisable discord ticket bot that you will ever find! You can customise up to 300+ variables! This includes <code>Html Transcripts</code>, <code>Advanced Plugins</code>, <code>Custom Embeds</code>, <code>Questions/Modals</code>, <code>Stats</code> & more!
You're also able to customise every little aspect of the bot! From embeds to transcripts. Open Ticket is also translated in more than <code>27 Languages</code>! If you need any help, feel free to join our <a href="https://discord.dj-dj.be">discord server</a>!
Open Ticket is the most advanced & customisable discord ticket bot available! You are able to customise up to 300+ settings and aspects! This includes <code>Html Transcripts</code>, <code>Advanced Plugins</code>, <code>Custom Embeds</code>, <code>Questions/Modals</code>, <code>Stats</code> & more!
The bot is translated in more than <code>27 Languages</code> and has been battle tested in large Discord servers! If you need any help, feel free to join our <a href="https://discord.dj-dj.be">discord server</a>!
</p>
<p align="center"><b>⭐️ Help us grow by giving a star! ⭐️</b></p>
<h3 align="center"><b>⭐️ Help us grow by giving a star! ⭐️</b></h3>
### 📌 Features
- **🦇 pterodactyl support** - Open Ticket works perfect on Pterodactyl based panels! [(Download official eggs)](.eggs/README.md)
@@ -68,10 +68,10 @@ A big thanks to all our sponsors! Without them, it wouldn't be possible to creat
<table>
<tr>
<td><img src="https://github.com/roppl3r.png" alt="Profile Picture" width="100px"></td>
<td><img src="https://github.com/guillee3.png" alt="Profile Picture" width="100px"></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/roppl3r"><b>roppl3r</b></a></td>
<td align="center"><a href="https://github.com/guillee3"><b>guillee3</b></a></td>
</tr>
</table>
+4 -4
View File
@@ -169,12 +169,12 @@
"rolesEmpty":"Geen enkele rollen zijn bijgewerkt!",
"autocloseLeave":"Dit ticket is automatisch gesloten omdat de maker de server verlaten is!",
"autocloseTimeout":"Dit ticket is automatisch gesloten omdat het inactief was voor meer dan `{0} uur`!",
"autocloseTimeout":"Dit ticket is automatisch gesloten omdat het meer dan `{0} uur` inactief was!",
"autodeleteLeave":"Dit ticket is automatisch verwijderd omdat de maker de server verlaten is!",
"autodeleteTimeout":"Dit ticket is automatisch verwijderd omdat het inactief was voor meer dan `{0} dagen`!",
"autocloseEnabled":"Autoclose is ingeschakeld in dit ticket!\nHet wordt gesloten wanneer het inactief is voor meer dan `{0} uur`!",
"autodeleteTimeout":"Dit ticket is automatisch verwijderd omdat het meer dan `{0} dagen` inactief was!",
"autocloseEnabled":"Autoclose is ingeschakeld in dit ticket!\nHet wordt gesloten na `{0} uur` inactiviteit!",
"autocloseDisabled":"Autoclose is uitgeschakeld in dit ticket!\nHet wordt niet meer automatisch gesloten!",
"autodeleteEnabled":"Autodelete is ingeschakeld in dit ticket!\nHet wordt verwijderd wanneer het inactief is voor meer dan `{0} dagen`!",
"autodeleteEnabled":"Autodelete is ingeschakeld in dit ticket!\nHet wordt verwijderd na `{0} dagen` inactiviteit!",
"autodeleteDisabled":"Autodelete is uitgeschakeld in dit ticket!\nHet wordt niet meer automatisch verwijderd!",
"ticketMessageLimit":"Je kan maar {0} ticket(s) op het zelfde moment aanmaken!",
+7 -3
View File
@@ -14,9 +14,11 @@
"scripts": {
"build": "node index.js --compile-only",
"start": "node index.js",
"setup": "node index.js --cli",
"startnc": "node index.js --no-compile",
"test": "node index.js --dev-config --dev-database",
"testnc": "node index.js --no-compile --dev-config --dev-database",
"test": "node index.js --dev-config --dev-database --soft-plugins",
"testsetup": "node index.js --cli --dev-config --dev-database --soft-plugins",
"testnc": "node index.js --no-compile --dev-config --dev-database --soft-plugins",
"docs": "npx typedoc --options .docs/typedoc-config.json && node .docs/createDocs.js"
},
"type": "commonjs",
@@ -24,9 +26,11 @@
"dependencies": {
"@discordjs/rest": "^2.4.2",
"@types/node": "^22.5.0",
"@types/terminal-kit": "^2.5.7",
"ansis": "^2.3.0",
"discord.js": "^14.19.1",
"formatted-json-stringify": "^1.1.0",
"formatted-json-stringify": "^1.2.1",
"terminal-kit": "^3.1.2",
"typescript": "^5.5.4"
},
"repository": {
+2 -8
View File
@@ -183,14 +183,8 @@ export class ODCheckerRenderer_Default extends ODCheckerRenderer {
return finalComponents
}
/**Get the length of the longest string in the array. */
#getLongestLength(text:string[]): number {
let finalLength = 0
text.forEach((t) => {
const l = ansis.strip(t).length
if (l > finalLength) finalLength = l
})
return finalLength
#getLongestLength(texts:string[]): number {
return Math.max(...texts.map((t) => ansis.strip(t).length))
}
/**Get a horizontal divider used between different parts of the config checker result. */
#getHorizontalDivider(width:number): string {
+2
View File
@@ -23,6 +23,8 @@ export interface ODFlagManagerIds_Default {
"opendiscord:force-slash-update":ODFlag,
"opendiscord:no-compile":ODFlag,
"opendiscord:compile-only":ODFlag,
"opendiscord:silent":ODFlag,
"opendiscord:cli":ODFlag,
}
/**## ODFlagManager_Default `default_class`
-1
View File
@@ -1,7 +1,6 @@
///////////////////////////////////////
//DEFAULT SESSION MODULE
///////////////////////////////////////
import { Guild, TextBasedChannel, User } from "discord.js"
import { ODValidId } from "../modules/base"
import { ODStatScope, ODStatGlobalScope, ODStatsManager, ODStat, ODBasicStat, ODDynamicStat, ODValidStatValue, ODStatScopeSetMode } from "../modules/stat"
+59 -32
View File
@@ -289,6 +289,16 @@ export class ODCheckerFunctionManager extends ODManager<ODCheckerFunction> {
*/
export type ODCheckerLocationTrace = (string|number)[]
/**## ODCheckerOptions `interface`
* This interface contains all optional properties to customise in the `ODChecker` class.
*/
export interface ODCheckerOptions {
/**The name of this config in the Interactive Setup CLI. */
cliDisplayName?:string
/**The description of this config in the Interactive Setup CLI. */
cliDisplayDescription?:string
}
/**## ODChecker `class`
* This is an Open Ticket config checker.
*
@@ -310,13 +320,16 @@ export class ODChecker extends ODManagerData {
messages: ODCheckerMessage[] = []
/**Temporary storage for the quit status from the check() method (not recommended to use) */
quit: boolean = false
/**All additional properties of this config checker. */
options: ODCheckerOptions
constructor(id:ODValidId, storage: ODCheckerStorage, priority:number, config:ODConfig, structure: ODCheckerStructure){
constructor(id:ODValidId, storage: ODCheckerStorage, priority:number, config:ODConfig, structure:ODCheckerStructure, options?:ODCheckerOptions){
super(id)
this.storage = storage
this.priority = priority
this.config = config
this.structure = structure
this.options = options ?? {}
}
/**Run this checker. Returns all errors*/
@@ -391,7 +404,15 @@ export interface ODCheckerStructureOptions {
/**Add a custom checker function. Returns `true` when valid. */
custom?:(checker:ODChecker, value:ODValidJsonType, locationTrace:ODCheckerLocationTrace, locationId:ODId, locationDocs:string|null) => boolean,
/**Set the url to the documentation of this variable. */
docs?:string
docs?:string,
/**The name of this config in the Interactive Setup CLI. */
cliDisplayName?:string
/**The description of this config in the Interactive Setup CLI. */
cliDisplayDescription?:string
/**Hide the description of this config in the Interactive Setup CLI parent view/list. */
cliHideDescriptionInParent?:boolean
/**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. */
cliInitDefaultValue?:ODValidJsonType
}
/**## ODCheckerStructure `class`
@@ -426,7 +447,13 @@ export class ODCheckerStructure {
*/
export interface ODCheckerObjectStructureOptions extends ODCheckerStructureOptions {
/**Add a checker for a property in an object (can also be optional) */
children?:{key:string, checker:ODCheckerStructure, priority:number, optional:boolean}[]
children:{key:string, priority:number, optional:boolean, cliHideInEditMode?:boolean, checker:ODCheckerStructure}[],
/**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. */
cliInitSkipKeys?:string[],
/**The key of a (primitive) property in this object to show the value of in the Interactive Setup CLI when listed in an array. */
cliDisplayKeyInParentArray?:string,
/**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. */
cliDisplayAdditionalKeysInParentArray?:string[]
}
/**## ODCheckerObjectStructure `class`
@@ -500,7 +527,11 @@ export interface ODCheckerStringStructureOptions extends ODCheckerStructureOptio
/**You need to choose between ... */
choices?:string[],
/**The string needs to match this regex */
regex?:RegExp
regex?:RegExp,
/**Provide an optional list for autocomplete when using the Interactive Setup CLI. Defaults to the `choices` option. */
cliAutocompleteList?:string[],
/**Dynamically provide a list for autocomplete items when using the Interactive Setup CLI. */
cliAutocompleteFunc?:() => Promise<string[]|null>
}
/**## ODCheckerStringStructure `class`
@@ -718,7 +749,9 @@ export interface ODCheckerArrayStructureOptions extends ODCheckerStructureOption
/**Allow double values (only for `string`, `number` & `boolean`) */
allowDoubles?:boolean
/**Only allow these types in the array (for multi-type propertyCheckers) */
allowedTypes?:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]
allowedTypes?:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[],
/**The name of the properties inside this array. Used in the GUI of the Interactive Setup CLI. */
cliDisplayPropertyName?:string
}
/**## ODCheckerArrayStructure `class`
@@ -863,15 +896,15 @@ export interface ODCheckerTypeSwitchStructureOptions extends ODCheckerStructureO
/**A checker when the property is a boolean */
boolean?:ODCheckerBooleanStructure,
/**A checker when the property is null */
null?:ODCheckerStructure,
null?:ODCheckerNullStructure,
/**A checker when the property is an array */
array?:ODCheckerStructure,
array?:ODCheckerArrayStructure,
/**A checker when the property is an object */
object?:ODCheckerObjectStructure,
/**A checker when the property is something else */
other?:ODCheckerStructure,
/**A list of allowed types */
allowedTypes?:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]
allowedTypes:("string"|"number"|"boolean"|"null"|"array"|"object"|"other")[]
}
/**## ODCheckerTypeSwitchStructure `class`
@@ -925,9 +958,9 @@ export class ODCheckerTypeSwitchStructure extends ODCheckerStructure {
*/
export interface ODCheckerObjectSwitchStructureOptions extends ODCheckerStructureOptions {
/**An array of object checkers with their name, properties & priority. */
objects?:{
objects:{
/**The properties to match for this checker to be used. */
properties:{key:string, value:any}[],
properties:{key:string, value:boolean|string|number}[],
/**The name for this object type (used in rendering) */
name:string,
/**The higher the priority, the earlier this checker will be tested. */
@@ -993,11 +1026,11 @@ export class ODCheckerObjectSwitchStructure extends ODCheckerStructure {
*/
export interface ODCheckerEnabledObjectStructureOptions extends ODCheckerStructureOptions {
/**The name of the property to match the `enabledValue`. */
property?:string,
/**The value of the property to be enabled. Defaults to `true` */
enabledValue?:any,
property:string,
/**The value of the property to be enabled. (e.g. `true`) */
enabledValue:boolean|string|number,
/**The object checker to use once the property has been matched. */
checker?:ODCheckerObjectStructure
checker:ODCheckerObjectStructure
}
/**## ODCheckerEnabledObjectStructure `class`
@@ -1387,35 +1420,29 @@ export class ODCheckerCustomStructure_UniqueIdArray extends ODCheckerArrayStruct
/**The scope to push unique ids when used in this array! */
readonly usedScope: string|null
constructor(id:ODValidId, source:string, scope:string, usedScope?:string, options?:ODCheckerArrayStructureOptions){
constructor(id:ODValidId, source:string, scope:string, usedScope?:string, options?:ODCheckerArrayStructureOptions, idOptions?:Omit<ODCheckerStringStructureOptions,"minLength"|"custom">){
//add premade custom structure checker
const newOptions = options ?? {}
newOptions.custom = (checker,value,locationTrace,locationId,locationDocs) => {
const lt = checker.locationTraceDeref(locationTrace)
newOptions.propertyChecker = new ODCheckerStringStructure("opendiscord:unique-id",{...(idOptions ?? {}),minLength:1,custom:(checker,value,locationTrace,locationId,locationDocs) => {
if (typeof value != "string") return false
const localLt = checker.locationTraceDeref(locationTrace)
localLt.pop()
if (!Array.isArray(value)) return false
const uniqueArray: string[] = (checker.storage.get(source,scope) === null) ? [] : checker.storage.get(source,scope)
let localQuit = false
value.forEach((id,index) => {
if (typeof id != "string") return
const localLt = checker.locationTraceDeref(lt)
localLt.push(index)
if (uniqueArray.includes(id)){
const uniqueArray: string[] = checker.storage.get(source,scope) ?? []
if (uniqueArray.includes(value)){
//exists
if (usedScope){
const current: string[] = checker.storage.get(source,usedScope) ?? []
current.push(id)
current.push(value)
checker.storage.set(source,usedScope,current)
}
return true
}else{
//doesn't exist
checker.createMessage("opendiscord:id-non-existent","error",`The id "${id}" doesn't exist!`,localLt,null,[`"${id}"`],this.id,(this.options.docs ?? null))
localQuit = true
}
})
return !localQuit
checker.createMessage("opendiscord:id-non-existent","error",`The id "${value}" doesn't exist!`,localLt,null,[`"${value}"`],locationId,locationDocs)
return false
}
}})
super(id,newOptions)
this.source = source
this.scope = scope
+5 -4
View File
@@ -114,7 +114,7 @@ export class ODClientManager {
this.#debug.debug("Created client with permissions: "+this.permissions.join(", "))
}
/**Get all servers the bot is part of. */
getGuilds(){
async getGuilds(): Promise<discord.Guild[]> {
if (!this.initiated) throw new ODSystemError("Client isn't initiated yet!")
if (!this.ready) throw new ODSystemError("Client isn't ready yet!")
@@ -135,8 +135,8 @@ export class ODClientManager {
})
}
}
/**Log-in with a discord auth token. */
login(): Promise<boolean> {
/**Log-in with a discord auth token. Rejects returns `false` using 'softErrors' on failure. */
login(softErrors?:boolean): Promise<boolean> {
return new Promise(async (resolve,reject) => {
if (!this.initiated) reject("Client isn't initiated yet!")
if (!this.token) reject("Client doesn't have a token!")
@@ -158,7 +158,8 @@ export class ODClientManager {
this.#debug.debug("Finished discord.js client.login()")
this.loggedIn = true
}catch(err){
if (err.message.toLowerCase().includes("used disallowed intents")){
if (softErrors) return resolve(false)
else if (err.message.toLowerCase().includes("used disallowed intents")){
process.emit("uncaughtException",new ODSystemError("Used disallowed intents"))
}else if (err.message.toLowerCase().includes("tokeninvalid") || err.message.toLowerCase().includes("an invalid token was provided")){
process.emit("uncaughtException",new ODSystemError("Invalid discord bot token provided"))
+60 -13
View File
@@ -5,6 +5,7 @@ import { ODId, ODManager, ODManagerData, ODPromiseVoid, ODSystemError, ODValidId
import nodepath from "path"
import { ODDebugger } from "./console"
import fs from "fs"
import * as fjs from "formatted-json-stringify"
/**## ODConfigManager `class`
* This is an Open Ticket config manager.
@@ -14,8 +15,17 @@ import fs from "fs"
* You can use this class to get/change/add a config file (`ODConfig`) in your plugin!
*/
export class ODConfigManager extends ODManager<ODConfig> {
/**Alias to Open Ticket debugger. */
#debug: ODDebugger
constructor(debug:ODDebugger){
super(debug,"config")
this.#debug = debug
}
add(data:ODConfig|ODConfig[],overwrite?:boolean): boolean {
if (Array.isArray(data)) data.forEach((d) => d.useDebug(this.#debug))
else data.useDebug(this.#debug)
return super.add(data,overwrite)
}
/**Init all config files. */
async init(){
@@ -42,15 +52,45 @@ export class ODConfig extends ODManagerData {
path: string = ""
/**An object/array of the entire config file! Variables inside it can be edited while the bot is running! */
data: any
/**Is this config already initiated? */
initiated: boolean = false
/**An array of listeners to run when the config gets reloaded. These are not executed on the initial loading. */
protected reloadListeners: Function[] = []
/**Alias to Open Ticket debugger. */
protected debug: ODDebugger|null = null
constructor(id:ODValidId, data:any){
super(id)
this.data = data
}
/**Use the Open Ticket debugger for logs. */
useDebug(debug:ODDebugger|null){
this.debug = debug
}
/**Init the config. */
init(): ODPromiseVoid {
//nothing
this.initiated = true
if (this.debug) this.debug.debug("Initiated config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}])
//please implement this feature in your own config extension & extend this function.
}
/**Reload the config. Be aware that this doesn't update the config data everywhere in the bot! */
reload(): ODPromiseVoid {
if (this.debug) this.debug.debug("Reloaded config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}])
//please implement this feature in your own config extension & extend this function.
}
/**Save the edited config to the filesystem. This is used by the Interactive Setup CLI. It's not recommended to use this while the bot is running. */
save(): ODPromiseVoid {
if (this.debug) this.debug.debug("Saved config '"+this.file+"' in ODConfigManager.",[{key:"id",value:this.id.value}])
//please implement this feature in your own config extension & extend this function.
}
/**Listen for a reload of this JSON file! */
onReload(cb:Function){
this.reloadListeners.push(cb)
}
/**Remove all reload listeners. Not recommended! */
removeAllReloadListeners(){
this.reloadListeners = []
}
}
@@ -65,13 +105,13 @@ export class ODConfig extends ODManagerData {
* const config = new api.ODJsonConfig("plugin-config","test.json","./plugins/testplugin/")
*/
export class ODJsonConfig extends ODConfig {
/**An array of listeners to run when the config gets reloaded. These are not executed on the initial loading. */
#reloadListeners: Function[] = []
formatter: fjs.custom.BaseFormatter
constructor(id:ODValidId, file:string, customPath?:string){
constructor(id:ODValidId, file:string, customPath?:string, formatter?:fjs.custom.BaseFormatter){
super(id,{})
this.file = (file.endsWith(".json")) ? file : file+".json"
this.path = customPath ? nodepath.join("./",customPath,this.file) : nodepath.join("./config/",this.file)
this.formatter = formatter ?? new fjs.DefaultFormatter(null,true," ")
}
/**Init the config. */
@@ -79,17 +119,20 @@ export class ODJsonConfig extends ODConfig {
if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to parse config \""+nodepath.join("./",this.path)+"\", the file doesn't exist!")
try{
this.data = JSON.parse(fs.readFileSync(this.path).toString())
super.init()
}catch(err){
process.emit("uncaughtException",err)
throw new ODSystemError("Unable to parse config \""+nodepath.join("./",this.path)+"\"!")
}
}
/**Reload the JSON file. Be aware that this doesn't update classes that used individual parts of the config data! */
/**Reload the config. Be aware that this doesn't update the config data everywhere in the bot! */
reload(){
if (!this.initiated) throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\", the file hasn't been initiated yet!")
if (!fs.existsSync(this.path)) throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\", the file doesn't exist!")
try{
this.data = JSON.parse(fs.readFileSync(this.path).toString())
this.#reloadListeners.forEach((cb) => {
super.reload()
this.reloadListeners.forEach((cb) => {
try{
cb()
}catch(err){
@@ -101,12 +144,16 @@ export class ODJsonConfig extends ODConfig {
throw new ODSystemError("Unable to reload config \""+nodepath.join("./",this.path)+"\"!")
}
}
/**Listen for a reload of this JSON file! */
onReload(cb:Function){
this.#reloadListeners.push(cb)
}
/**Remove all reload listeners. Not recommended! */
removeAllReloadListeners(){
this.#reloadListeners = []
/**Save the edited config to the filesystem. This is used by the Interactive Setup CLI. It's not recommended to use this while the bot is running. */
save(): ODPromiseVoid {
if (!this.initiated) throw new ODSystemError("Unable to save config \""+nodepath.join("./",this.path)+"\", the file hasn't been initiated yet!")
try{
const contents = this.formatter.stringify(this.data)
fs.writeFileSync(this.path,contents)
super.save()
}catch(err){
process.emit("uncaughtException",err)
throw new ODSystemError("Unable to save config \""+nodepath.join("./",this.path)+"\"!")
}
}
}
+5 -3
View File
@@ -241,6 +241,8 @@ export class ODConsoleManager {
historylength = 100
/**An alias to the debugfile manager. (`otdebug.txt`) */
debugfile: ODDebugFileManager
/**Is silent mode enabled? */
silent: boolean = false
constructor(historylength:number, debugfile:ODDebugFileManager){
this.historylength = historylength
@@ -253,12 +255,12 @@ export class ODConsoleManager {
log(message:string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]): void
log(message:ODConsoleMessage|ODError|string, type?:ODConsoleMessageTypes, params?:ODConsoleMessageParam[]){
if (message instanceof ODConsoleMessage){
message.render()
if (!this.silent) message.render()
if (this.debugfile) this.debugfile.writeConsoleMessage(message)
this.history.push(message)
}else if (message instanceof ODError){
message.render()
if (!this.silent) message.render()
if (this.debugfile) this.debugfile.writeErrorMessage(message)
this.history.push(message)
@@ -272,7 +274,7 @@ export class ODConsoleManager {
else if (type == "error") newMessage = new ODConsoleErrorMessage(message,params)
else newMessage = new ODConsoleSystemMessage(message,params)
newMessage.render()
if (!this.silent) newMessage.render()
if (this.debugfile) this.debugfile.writeConsoleMessage(newMessage)
this.history.push(newMessage)
}
+3
View File
@@ -13,6 +13,8 @@ export interface ODDefaults {
crashOnError:boolean,
/**Enable the system responsible for the `--debug` flag. */
debugLoading:boolean,
/**Enable the system responsible for the `--silent` flag. */
silentLoading:boolean,
/**When enabled, you're able to use the "!OPENTICKET:dump" command to send the OT debug file. This is only possible when you're the owner of the bot. */
allowDumpCommand:boolean,
/**Enable loading all Open Ticket plugins, sadly enough is only useful for the system :) */
@@ -233,6 +235,7 @@ export class ODDefaultsManager {
errorHandling:true,
crashOnError:false,
debugLoading:true,
silentLoading:true,
allowDumpCommand:true,
pluginLoading:true,
softPluginLoading:false,
+75
View File
@@ -0,0 +1,75 @@
import {opendiscord, api, utilities} from "../../index"
import {Terminal, terminal} from "terminal-kit"
import ansis from "ansis"
const logo = [
" ██████╗ ██████╗ ███████╗███╗ ██╗ ████████╗██╗ ██████╗██╗ ██╗███████╗████████╗ ",
" ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ ",
" ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║██║ █████╔╝ █████╗ ██║ ",
" ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ ",
" ╚██████╔╝██║ ███████╗██║ ╚████║ ██║ ██║╚██████╗██║ ██╗███████╗ ██║ ",
" ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ "
]
/**A utility function to center text to a certain width. */
export function centerText(text:string,width:number){
if (width < text.length) return text
let newWidth = width-ansis.strip(text).length+1
let final = " ".repeat(newWidth/2)+text
return final
}
/**A utility function to terminate the interactive CLI. */
export async function terminate(){
terminal.grabInput(false)
terminal.clear()
terminal.green("👋 Exited the Open Ticket Interactive Setup CLI.\n")
process.exit(0)
}
terminal.on("key",(name,matches,data) => {
if (name == "CTRL_C") terminate()
})
/**Render the header of the interactive CLI. */
export function renderHeader(path:(string|number)[]|string){
terminal.grabInput(true)
terminal.clear().moveTo(1,1)
terminal(ansis.hex("#f8ba00")(logo.join("\n")+"\n"))
terminal.bold(centerText("Interactive Setup CLI - Version: "+opendiscord.versions.get("opendiscord:version").toString()+" - Support: https://discord.dj-dj.be\n",88))
if (typeof path == "string") terminal.cyan(centerText(path+"\n\n",88))
else if (path.length < 1) terminal.cyan(centerText("👋 Hi! Welcome to the Open Ticket Interactive Setup CLI! 👋\n\n",88))
else terminal.cyan(centerText("🌐 Current Location: "+path.map((v,i) => {
if (i == 0) return v.toString()
else if (typeof v == "string") return ".\""+v+"\""
else if (typeof v == "number") return "."+v
}).join("")+"\n\n",88))
}
async function renderCliModeSelector(backFn:(() => api.ODPromiseVoid)){
renderHeader([])
terminal(ansis.bold.green("Please select what CLI module you want to use.\n")+ansis.italic.gray("(use arrow keys to navigate, exit using escape)\n"))
const answer = await terminal.singleColumnMenu([
"✏️ Edit Config "+ansis.gray("=> Edit the current config, add/remove new tickets/questions/panels & more!"),
"⏱️ Quick Setup "+ansis.gray("=> A quick and easy way of setting up the bot in your Discord server."),
],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
else if (answer.selectedIndex == 0) await (await import("./editConfig.js")).renderEditConfig(async () => {await renderCliModeSelector(backFn)})
else if (answer.selectedIndex == 1) await (await import("./quickSetup.js")).renderQuickSetup(async () => {await renderCliModeSelector(backFn)})
}
export async function execute(){
if (terminal.width < 100 || terminal.height < 35){
terminal(ansis.red.bold("\n\nMake sure your console or cmd window has a "+ansis.cyan("minimum width & height")+" of "+ansis.cyan("100x35")+" characters."))
terminal(ansis.red.bold("\nOtherwise the Open Ticket Interactive Setup CLI will be rendered incorrectly."))
terminal(ansis.red.bold("\nThe current terminal dimensions are: "+ansis.cyan(terminal.width+"x"+terminal.height)+"."))
}else await renderCliModeSelector(terminate)
}
+933
View File
@@ -0,0 +1,933 @@
import {opendiscord, api, utilities} from "../../index"
import {Terminal, terminal} from "terminal-kit"
import ansis from "ansis"
import {renderHeader} from "./cli"
export async function renderEditConfig(backFn:(() => api.ODPromiseVoid)){
renderHeader([])
terminal(ansis.bold.green("Please select which config you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
const checkerList = opendiscord.checkers.getAll()
const checkerNameList = checkerList.map((checker) => (checker.options.cliDisplayName ? checker.options.cliDisplayName+" ("+checker.config.file+")" : checker.config.file))
const checkerNameLength = utilities.getLongestLength(checkerNameList)
const finalCheckerNameList = checkerNameList.map((name,index) => name.padEnd(checkerNameLength+5," ")+ansis.gray(checkerList[index].options.cliDisplayDescription ? "=> "+checkerList[index].options.cliDisplayDescription : ""))
const answer = await terminal.singleColumnMenu(finalCheckerNameList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
const checker = checkerList[answer.selectedIndex]
const configData = checker.config.data as api.ODValidJsonType
await chooseConfigStructure(checker,async () => {await renderEditConfig(backFn)},checker.structure,configData,{},NaN,["("+checker.config.path+")"])
}
async function chooseConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object|any[],parentIndex:string|number,path:(string|number)[]){
if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigEnabledObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data) await renderConfigObjectSwitchStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerNullStructure && data === null) await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
else if (structure instanceof api.ODCheckerTypeSwitchStructure) await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
else terminal.red.bold("❌ Unable to detect type of variable! Please try to edit this property in the JSON file itself!")
}
async function renderConfigObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){
if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
renderHeader(path)
terminal(ansis.bold.green("Please select which variable you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
if (!structure.options.children) return await backFn()
if (structure.options.cliDisplayName){
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName)+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
}
const list = structure.options.children.filter((child) => !child.cliHideInEditMode)
const nameList = list.map((child) => (child.checker.options.cliDisplayName ? child.checker.options.cliDisplayName : child.key))
const nameLength = utilities.getLongestLength(nameList)
const finalnameList = nameList.map((name,index) => name.padEnd(nameLength+5," ")+ansis.gray((!list[index].checker.options.cliHideDescriptionInParent && list[index].checker.options.cliDisplayDescription) ? "=> "+list[index].checker.options.cliDisplayDescription : ""))
const answer = await terminal.singleColumnMenu(finalnameList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold.defaultColor,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
const subStructure = list[answer.selectedIndex]
const subData = data[subStructure.key]
await chooseConfigStructure(checker,async () => {await renderConfigObjectStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)},subStructure.checker,subData,data,subStructure.key,[...path,subStructure.key])
}
async function renderConfigEnabledObjectStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerEnabledObjectStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){
if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
const enabledProperty = structure.options.property
const subStructure = structure.options.checker
if (!enabledProperty || !subStructure || !subStructure.options.children) return await backFn()
if (!subStructure.options.children.find((child) => child.key === structure.options.property)){
if (typeof structure.options.enabledValue == "string") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerStringStructure("opendiscord:CLI-checker-enabled-object-structure",{})})
else if (typeof structure.options.enabledValue == "number") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerNumberStructure("opendiscord:CLI-checker-enabled-object-structure",{})})
else if (typeof structure.options.enabledValue == "boolean") subStructure.options.children.unshift({key:enabledProperty,optional:false,priority:1,checker:new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-enabled-object-structure",{})})
}
await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path)
}
async function renderConfigObjectSwitchStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerObjectSwitchStructure,data:object,parent:object,parentIndex:string|number,path:(string|number)[]){
if (typeof data != "object" || Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'object'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
if (!structure.options.objects) return await backFn()
let didMatch: boolean = false
for (const objectTemplate of structure.options.objects){
if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){
//object template matches data
const subStructure = objectTemplate.checker
didMatch = true
await chooseConfigStructure(checker,backFn,subStructure,data,parent,parentIndex,path)
}
}
if (!didMatch) throw new api.ODSystemError("OT CLI => Unable to detect type of object in the object switch. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
}
async function renderConfigArrayStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerArrayStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
if (!Array.isArray(data)) throw new api.ODSystemError("OT CLI => Property is not of the type 'array'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
renderHeader(path)
terminal(ansis.bold.green("Please select what you would like to do.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
if (!structure.options.propertyChecker) return await backFn()
if (structure.options.cliDisplayName || typeof parentIndex == "string" || !isNaN(parentIndex)){
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? parentIndex.toString())+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
}
const propertyName = structure.options.cliDisplayPropertyName ?? "index"
const answer = await terminal.singleColumnMenu(data.length < 1 ? ["Add "+propertyName] : [
"Add "+propertyName,
"Edit "+propertyName,
"Move "+propertyName,
"Remove "+propertyName,
"Duplicate "+propertyName
],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
const backFnFunc = async () => {await renderConfigArrayStructureSelector(checker,backFn,structure,data,parent,parentIndex,path)}
if (answer.canceled) return await backFn()
if (answer.selectedIndex == 0) await chooseAdditionConfigStructure(checker,backFnFunc,async (newData) => {
data[data.length] = newData
await checker.config.save()
await backFnFunc()
},structure.options.propertyChecker,data,data.length,path,[])
else if (answer.selectedIndex == 1) await renderConfigArrayStructureEditSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path)
else if (answer.selectedIndex == 2) await renderconfigArrayStructureMoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path)
else if (answer.selectedIndex == 3) await renderconfigArrayStructureRemoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path)
else if (answer.selectedIndex == 4) await renderConfigArrayStructureDuplicateSelector(checker,backFnFunc,structure,structure.options.propertyChecker,data,parent,parentIndex,path)
}
async function renderConfigArrayStructureEditSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index"
renderHeader(path)
terminal(ansis.bold.green("Please select the "+propertyName+" you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i)))
const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName))
const dataAnswer = await terminal.singleColumnMenu(dataList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (dataAnswer.canceled) return await backFn()
const subData = data[dataAnswer.selectedIndex]
await chooseConfigStructure(checker,async () => {await renderConfigArrayStructureEditSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path)},structure,subData,data,dataAnswer.selectedIndex,[...path,dataAnswer.selectedIndex])
}
async function renderconfigArrayStructureMoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index"
renderHeader(path)
terminal(ansis.bold.green("Please select the "+propertyName+" you would like to move.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i)))
const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName))
const dataAnswer = await terminal.singleColumnMenu(dataList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (dataAnswer.canceled) return await backFn()
renderHeader([...path,dataAnswer.selectedIndex])
terminal(ansis.bold.green("Please select the position you would like to move to.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
const moveAnswer = await terminal.singleColumnMenu(data.map((d,i) => "Position "+(i+1)),{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (moveAnswer.canceled) return await renderconfigArrayStructureMoveSelector(checker,backFn,arrayStructure,structure,data,parent,parentIndex,path)
const subData = data[dataAnswer.selectedIndex]
const slicedData = [...data.slice(0,dataAnswer.selectedIndex),...data.slice(dataAnswer.selectedIndex+1)]
const insertedData = [...slicedData.slice(0,moveAnswer.selectedIndex),subData,...slicedData.slice(moveAnswer.selectedIndex)]
insertedData.forEach((d,i) => data[i] = d)
await checker.config.save()
terminal.bold.blue("\n\n✅ Property moved succesfully!")
await utilities.timer(400)
await backFn()
}
async function renderconfigArrayStructureRemoveSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index"
renderHeader(path)
terminal(ansis.bold.green("Please select the "+propertyName+" you would like to delete.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i)))
const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName))
const dataAnswer = await terminal.singleColumnMenu(dataList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (dataAnswer.canceled) return await backFn()
data.splice(dataAnswer.selectedIndex,1)
await checker.config.save()
terminal.bold.blue("\n\n✅ Property deleted succesfully!")
await utilities.timer(400)
await backFn()
}
async function renderConfigArrayStructureDuplicateSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),arrayStructure:api.ODCheckerArrayStructure,structure:api.ODCheckerStructure,data:any[],parent:object,parentIndex:string|number,path:(string|number)[]){
const propertyName = arrayStructure.options.cliDisplayPropertyName ?? "index"
renderHeader(path)
terminal(ansis.bold.green("Please select the "+propertyName+" you would like to duplicate.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
const longestDataListName = Math.max(...data.map((d,i) => getArrayPreviewStructureNameLength(structure,d,data,i)))
const dataList = data.map((d,i) => (i+1)+". "+getArrayPreviewFromStructure(structure,d,data,i,longestDataListName))
const dataAnswer = await terminal.singleColumnMenu(dataList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (dataAnswer.canceled) return await backFn()
data.push(JSON.parse(JSON.stringify(data[dataAnswer.selectedIndex])))
await checker.config.save()
terminal.bold.blue("\n\n✅ Property duplicated succesfully!")
await utilities.timer(400)
await backFn()
}
async function renderConfigBooleanStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerBooleanStructure,data:boolean,parent:object,parentIndex:string|number,path:(string|number)[]){
if (typeof data != "boolean") throw new api.ODSystemError("OT CLI => Property is not of the type 'boolean'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
renderHeader(path)
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the boolean property "+ansis.blue("\""+parentIndex+"\"") : "boolean property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
terminal.gray("\nCurrent value: "+ansis.bold[data ? "green" : "red"](data.toString())+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const answer = await terminal.singleColumnMenu(["false (Disabled)","true (Enabled)"],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
//run config checker
const newValue = (answer.selectedIndex == 0) ? false : true
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
parent[parentIndex] = newValue
await checker.config.save()
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await backFn()
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.gray("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderConfigBooleanStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
}
}
async function renderConfigNumberStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNumberStructure,data:number,parent:object,parentIndex:string|number,path:(string|number)[],prefillValue?:string){
if (typeof data != "number") throw new api.ODSystemError("OT CLI => Property is not of the type 'number'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
renderHeader(path)
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the number property "+ansis.blue("\""+parentIndex+"\"") : "number property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n"))
terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const answer = await terminal.inputField({
default:prefillValue,
style:terminal.cyan,
cancelable:true
}).promise
if (typeof answer != "string") return await backFn()
//run config checker
const newValue = Number(answer.replaceAll(",","."))
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
parent[parentIndex] = newValue
await checker.config.save()
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await backFn()
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.red("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderConfigNumberStructureEditor(checker,backFn,structure,data,parent,parentIndex,path,answer)
}
}
async function renderConfigStringStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStringStructure,data:string,parent:object,parentIndex:string|number,path:(string|number)[],prefillValue?:string){
if (typeof data != "string") throw new api.ODSystemError("OT CLI => Property is not of the type 'string'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
renderHeader(path)
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the string property "+ansis.blue("\""+parentIndex+"\"") : "string property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n"))
terminal.gray("\nCurrent value:"+(data.includes("\n") ? "\n" : " \"")+ansis.bold.blue(data)+ansis.gray(!data.includes("\n") ? "\"\n" : "\n"))
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const customExtraOptions = (structure instanceof api.ODCheckerCustomStructure_DiscordId) ? structure.extraOptions : undefined
const customAutocompleteFunc = structure.options.cliAutocompleteFunc ? await structure.options.cliAutocompleteFunc() : null
const autocompleteList = ((customAutocompleteFunc ?? structure.options.cliAutocompleteList) ?? customExtraOptions) ?? structure.options.choices
const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = {
style:terminal.white,
selectedStyle:terminal.bgBlue.white
}
const input = terminal.inputField({
default:prefillValue,
style:terminal.cyan,
hintStyle:terminal.gray,
cancelable:false,
autoComplete:autocompleteList,
autoCompleteHint:(!!autocompleteList),
autoCompleteMenu:(autocompleteList) ? autoCompleteMenuOpts as Terminal.Autocompletion : false
})
terminal.on("key",async (name:string,matches:string[],data:object) => {
if (name == "ESCAPE"){
terminal.removeListener("key","cli-render-string-structure-edit")
input.abort()
await backFn()
}
},({id:"cli-render-string-structure-edit"} as any))
const answer = await input.promise
terminal.removeListener("key","cli-render-string-structure-edit")
if (typeof answer != "string") return
//run config checker
const newValue = answer.replaceAll("\\n","\n")
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
parent[parentIndex] = newValue
await checker.config.save()
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await backFn()
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.red("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderConfigStringStructureEditor(checker,backFn,structure,data,parent,parentIndex,path,answer)
}
}
async function renderConfigNullStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerNullStructure,data:null,parent:object,parentIndex:string|number,path:(string|number)[]){
if (data !== null) throw new api.ODSystemError("OT CLI => Property is not of the type 'null'. Please check your config for possible errors. (index: "+parentIndex+", path: "+path.join(".")+")")
renderHeader(path)
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the null property "+ansis.blue("\""+parentIndex+"\"") : "null property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
terminal.gray("\nCurrent value: "+ansis.bold.blue("null")+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const answer = await terminal.singleColumnMenu(["null"],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
//run config checker
const newValue = null
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
parent[parentIndex] = newValue
await checker.config.save()
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await backFn()
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.red("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderConfigNullStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)
}
}
async function renderConfigTypeSwitchStructureEditor(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,data:any,parent:object,parentIndex:string|number,path:(string|number)[]){
renderHeader(path)
terminal(ansis.bold.green("You are now editing "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
terminal.gray("\nCurrent value: "+ansis.bold.blue(data.toString())+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const actionsList: string[] = []
if (structure.options.boolean) actionsList.push("Edit as boolean")
if (structure.options.string) actionsList.push("Edit as string")
if (structure.options.number) actionsList.push("Edit as number")
if (structure.options.object) actionsList.push("Edit as object")
if (structure.options.array) actionsList.push("Edit as array/list")
if (structure.options.null) actionsList.push("Edit as null")
const answer = await terminal.singleColumnMenu(actionsList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
//run selected structure editor (untested)
if (answer.selectedText.startsWith("Edit as boolean") && structure.options.boolean) await renderConfigBooleanStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.boolean,false,parent,parentIndex,path)
else if (answer.selectedText.startsWith("Edit as string") && structure.options.string) await renderConfigStringStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.string,data.toString(),parent,parentIndex,path)
else if (answer.selectedText.startsWith("Edit as number") && structure.options.number) await renderConfigNumberStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.number,0,parent,parentIndex,path)
else if (answer.selectedText.startsWith("Edit as object") && structure.options.object) await renderConfigObjectStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.object,data,parent,parentIndex,path)
else if (answer.selectedText.startsWith("Edit as array/list") && structure.options.array) await renderConfigArrayStructureSelector(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.array,data,parent,parentIndex,path)
else if (answer.selectedText.startsWith("Edit as null") && structure.options.null) await renderConfigNullStructureEditor(checker,async () => {await renderConfigTypeSwitchStructureEditor(checker,backFn,structure,data,parent,parentIndex,path)},structure.options.null,null,parent,parentIndex,path)
}
function getArrayPreviewStructureNameLength(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number): number {
if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString().length
else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString().length
else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data.length
else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null".length
else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array".length
else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
if (!structure.options.cliDisplayKeyInParentArray) return "Object".length
else return data[structure.options.cliDisplayKeyInParentArray].toString().length
}else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
const subStructure = structure.options.checker
if (!subStructure) return "<unknown-property>".length
return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex)
}else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){
for (const objectTemplate of (structure.options.objects ?? [])){
if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){
//object template matches data
const subStructure = objectTemplate.checker
return getArrayPreviewStructureNameLength(subStructure,data,parent,parentIndex)
}
}
return "<unknown-property>".length
}else if (structure instanceof api.ODCheckerTypeSwitchStructure){
if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewStructureNameLength(structure.options.boolean,data,parent,parentIndex)
else if (typeof data == "number" && structure.options.number) return getArrayPreviewStructureNameLength(structure.options.number,data,parent,parentIndex)
else if (typeof data == "string" && structure.options.string) return getArrayPreviewStructureNameLength(structure.options.string,data,parent,parentIndex)
else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewStructureNameLength(structure.options.object,data,parent,parentIndex)
else if (Array.isArray(data) && structure.options.array) return getArrayPreviewStructureNameLength(structure.options.array,data,parent,parentIndex)
else if (data === null && structure.options.null) return getArrayPreviewStructureNameLength(structure.options.null,data,parent,parentIndex)
else return "<unknown-property>".length
}else return "<unknown-property>".length
}
function getArrayPreviewFromStructure(structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object,parentIndex:string|number,nameLength:number): string {
if (structure instanceof api.ODCheckerBooleanStructure && typeof data == "boolean") return data.toString()
else if (structure instanceof api.ODCheckerNumberStructure && typeof data == "number") return data.toString()
else if (structure instanceof api.ODCheckerStringStructure && typeof data == "string") return data
else if (structure instanceof api.ODCheckerNullStructure && data === null) return "Null"
else if (structure instanceof api.ODCheckerArrayStructure && Array.isArray(data)) return "Array"
else if (structure instanceof api.ODCheckerObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
const additionalKeys = (structure.options.cliDisplayAdditionalKeysInParentArray ?? []).map((key) => key+": "+data[key].toString()).join(", ")
if (!structure.options.cliDisplayKeyInParentArray) return "Object"
else return data[structure.options.cliDisplayKeyInParentArray].toString().padEnd(nameLength+5," ")+ansis.gray(additionalKeys.length > 0 ? "("+additionalKeys+")" : "")
}else if (structure instanceof api.ODCheckerEnabledObjectStructure && typeof data == "object" && !Array.isArray(data) && data){
const subStructure = structure.options.checker
if (!subStructure) return "<unknown-property>"
return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength)
}else if (structure instanceof api.ODCheckerObjectSwitchStructure && typeof data == "object" && !Array.isArray(data) && data){
for (const objectTemplate of (structure.options.objects ?? [])){
if (objectTemplate.properties.every((prop) => data[prop.key] === prop.value)){
//object template matches data
const subStructure = objectTemplate.checker
return getArrayPreviewFromStructure(subStructure,data,parent,parentIndex,nameLength)
}
}
return "<unknown-property>"
}else if (structure instanceof api.ODCheckerTypeSwitchStructure){
if (typeof data == "boolean" && structure.options.boolean) return getArrayPreviewFromStructure(structure.options.boolean,data,parent,parentIndex,nameLength)
else if (typeof data == "number" && structure.options.number) return getArrayPreviewFromStructure(structure.options.number,data,parent,parentIndex,nameLength)
else if (typeof data == "string" && structure.options.string) return getArrayPreviewFromStructure(structure.options.string,data,parent,parentIndex,nameLength)
else if (typeof data == "object" && !Array.isArray(data) && data && structure.options.object) return getArrayPreviewFromStructure(structure.options.object,data,parent,parentIndex,nameLength)
else if (Array.isArray(data) && structure.options.array) return getArrayPreviewFromStructure(structure.options.array,data,parent,parentIndex,nameLength)
else if (data === null && structure.options.null) return getArrayPreviewFromStructure(structure.options.null,data,parent,parentIndex,nameLength)
else return "<unknown-property>"
}else return "<unknown-property>"
}
async function chooseAdditionConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){
if (structure instanceof api.ODCheckerObjectStructure) await renderAdditionConfigObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerBooleanStructure) await renderAdditionConfigBooleanStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerNumberStructure) await renderAdditionConfigNumberStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerStringStructure) await renderAdditionConfigStringStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerNullStructure) await renderAdditionConfigNullStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerEnabledObjectStructure) await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerObjectSwitchStructure) await renderAdditionConfigObjectSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerArrayStructure) await renderAdditionConfigArrayStructureSelector(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else if (structure instanceof api.ODCheckerTypeSwitchStructure) await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
else await backFn()
}
async function renderAdditionConfigObjectStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerObjectStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],localData:object={}){
const children = structure.options.children ?? []
const skipKeys = (structure.options.cliInitSkipKeys ?? [])
//add skipped properties
for (const key of skipKeys){
const childStructure = children.find((c) => c.key == key)
if (childStructure){
const defaultValue = childStructure.checker.options.cliInitDefaultValue
if (childStructure.checker instanceof api.ODCheckerBooleanStructure) localData[key] = (typeof defaultValue == "boolean" ? defaultValue : false)
else if (childStructure.checker instanceof api.ODCheckerNumberStructure) localData[key] = (typeof defaultValue == "number" ? defaultValue : 0)
else if (childStructure.checker instanceof api.ODCheckerStringStructure) localData[key] = (typeof defaultValue == "string" ? defaultValue : "")
else if (childStructure.checker instanceof api.ODCheckerNullStructure) localData[key] = (defaultValue === null ? defaultValue : null)
else if (childStructure.checker instanceof api.ODCheckerArrayStructure) localData[key] = (Array.isArray(defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : [])
else if (childStructure.checker instanceof api.ODCheckerObjectStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {})
else if (childStructure.checker instanceof api.ODCheckerObjectSwitchStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {})
else if (childStructure.checker instanceof api.ODCheckerEnabledObjectStructure) localData[key] = ((typeof defaultValue == "object" && !Array.isArray(defaultValue) && defaultValue) ? JSON.parse(JSON.stringify(defaultValue)) : {})
else if (childStructure.checker instanceof api.ODCheckerTypeSwitchStructure && typeof defaultValue != "undefined") localData[key] = JSON.parse(JSON.stringify(defaultValue))
else throw new api.ODSystemError("OT CLI => Object skip key has an invalid checker structure! key: "+key)
}
}
//add properties that need to be configured
const configChildren = children.filter((c) => !skipKeys.includes(c.key)).map((c) => {return {key:c.key,checker:c.checker}})
await configureAdditionObjectProperties(checker,configChildren,0,localData,[...path,parentIndex],(typeof parentIndex == "number") ? [...localPath] : [...localPath,parentIndex],async () => {
//go back to previous screen
await backFn()
},async () => {
//finish setup
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await nextFn(localData)
})
}
async function configureAdditionObjectProperties(checker:api.ODChecker,children:{key:string,checker:api.ODCheckerStructure}[],currentIndex:number,localData:object,path:(string|number)[],localPath:(string|number)[],backFn:(() => api.ODPromiseVoid),nextFn:(() => api.ODPromiseVoid)){
if (children.length < 1) return await nextFn()
const child = children[currentIndex]
await chooseAdditionConfigStructure(checker,async () => {
if (children[currentIndex-1]) await configureAdditionObjectProperties(checker,children,currentIndex-1,localData,path,localPath,backFn,nextFn)
else await backFn()
},async (data) => {
localData[child.key] = data
if (children[currentIndex+1]) await configureAdditionObjectProperties(checker,children,currentIndex+1,localData,path,localPath,backFn,nextFn)
else await nextFn()
},child.checker,localData,child.key,path,localPath)
}
async function renderAdditionConfigEnabledObjectStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerEnabledObjectStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){
const enabledProperty = structure.options.property
const enabledValue = structure.options.enabledValue
const subStructure = structure.options.checker
if (!enabledProperty || !subStructure || !subStructure.options.children) return await backFn()
let propertyStructure: api.ODCheckerBooleanStructure|api.ODCheckerNumberStructure|api.ODCheckerStringStructure
if (typeof enabledValue == "string") propertyStructure = new api.ODCheckerStringStructure("opendiscord:CLI-checker-enabled-object-structure",{})
else if (typeof enabledValue == "number") propertyStructure = new api.ODCheckerNumberStructure("opendiscord:CLI-checker-enabled-object-structure",{})
else if (typeof enabledValue == "boolean") propertyStructure = new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-enabled-object-structure",{})
else throw new Error("OT CLI => enabled object structure has an invalid type of enabledProperty. It must be a primitive boolean/number/string.")
const localData = {}
await chooseAdditionConfigStructure(checker,backFn,async (data) => {
if (data === enabledValue) await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,subStructure,parent,parentIndex,path,localPath,localData)
else{
localData[enabledProperty] = data
//copy old object checker to new object checker => all options get de-referenced (this is needed for the new object skip keys are temporary)
const newStructure = new api.ODCheckerObjectStructure(subStructure.id,{children:[]})
//copy all options over to the new checker
newStructure.options.children = [...subStructure.options.children]
newStructure.options.cliInitSkipKeys = subStructure.options.children.map((child) => child.key)
for (const key of Object.keys(subStructure.options)){
if (key != "children" && key != "cliInitSkipKeys") newStructure.options[key] = subStructure.options[key]
}
//adds all properties to object as "skipKeys", then continues to next function
await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigEnabledObjectStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,newStructure,parent,parentIndex,path,localPath,localData)
await nextFn(localData)
}
},propertyStructure,localData,enabledProperty,[...path,parentIndex],[...localPath,parentIndex])
}
async function renderAdditionConfigObjectSwitchStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerObjectSwitchStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){
renderHeader([...path,parentIndex])
terminal(ansis.bold.green("What type of object would you like to add?\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
const answer = await terminal.singleColumnMenu(structure.options.objects.map((obj) => obj.name),{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
const objectTemplate = structure.options.objects[answer.selectedIndex]
//copy old object checker to new object checker => all options get de-referenced (this is needed for the new object switch properties which are temporary)
const oldStructure = objectTemplate.checker
const newStructure = new api.ODCheckerObjectStructure(oldStructure.id,{children:[]})
//copy all options over to the new checker
newStructure.options.children = [...oldStructure.options.children]
newStructure.options.cliInitSkipKeys = [...(oldStructure.options.cliInitSkipKeys ?? [])]
for (const key of Object.keys(oldStructure.options)){
if (key != "children" && key != "cliInitSkipKeys") newStructure.options[key] = oldStructure.options[key]
}
//add the keys of the object switch properties to the 'cliInitSkipKeys' because they need to be skipped.
objectTemplate.properties.map((p) => p.key).forEach((p) => {
if (!newStructure.options.cliInitSkipKeys) newStructure.options.cliInitSkipKeys = [p]
else if (!newStructure.options.cliInitSkipKeys.includes(p)) newStructure.options.cliInitSkipKeys.push(p)
})
//add structure checkers for all properties
for (const prop of objectTemplate.properties){
if (!newStructure.options.children.find((child) => child.key === prop.key)){
if (typeof prop.value == "string") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerStringStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})})
else if (typeof prop.value == "number") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerNumberStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})})
else if (typeof prop.value == "boolean") newStructure.options.children.unshift({key:prop.key,optional:false,priority:1,checker:new api.ODCheckerBooleanStructure("opendiscord:CLI-checker-object-switch-structure",{cliInitDefaultValue:prop.value})})
}
}
await chooseAdditionConfigStructure(checker,backFn,nextFn,newStructure,parent,parentIndex,path,localPath)
}
async function renderAdditionConfigBooleanStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerBooleanStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){
renderHeader([...path,parentIndex])
terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the boolean property "+ansis.blue("\""+parentIndex+"\"") : "boolean property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const answer = await terminal.singleColumnMenu(["false (Disabled)","true (Enabled)"],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
//run config checker
const newValue = (answer.selectedIndex == 0) ? false : true
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await nextFn(newValue)
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.gray("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderAdditionConfigBooleanStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
}
}
async function renderAdditionConfigNumberStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerNumberStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],prefillValue?:string){
renderHeader([...path,parentIndex])
terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the number property "+ansis.blue("\""+parentIndex+"\"") : "number property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n"))
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const answer = await terminal.inputField({
default:prefillValue,
style:terminal.cyan,
cancelable:true
}).promise
if (typeof answer != "string") return await backFn()
//run config checker
const newValue = Number(answer.replaceAll(",","."))
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await nextFn(newValue)
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.red("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderAdditionConfigNumberStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,answer)
}
}
async function renderAdditionConfigStringStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerStringStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],prefillValue?:string){
renderHeader([...path,parentIndex])
terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the string property "+ansis.blue("\""+parentIndex+"\"") : "string property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(insert a new value and press enter, go back using escape)\n"))
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const customExtraOptions = (structure instanceof api.ODCheckerCustomStructure_DiscordId) ? structure.extraOptions : undefined
const customAutocompleteFunc = structure.options.cliAutocompleteFunc ? await structure.options.cliAutocompleteFunc() : null
const autocompleteList = ((customAutocompleteFunc ?? structure.options.cliAutocompleteList) ?? customExtraOptions) ?? structure.options.choices
const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = {
style:terminal.white,
selectedStyle:terminal.bgBlue.white
}
const input = terminal.inputField({
default:prefillValue,
style:terminal.cyan,
hintStyle:terminal.gray,
cancelable:false,
autoComplete:autocompleteList,
autoCompleteHint:(!!autocompleteList),
autoCompleteMenu:(autocompleteList) ? autoCompleteMenuOpts as Terminal.Autocompletion : false
})
terminal.on("key",async (name:string,matches:string[],data:object) => {
if (name == "ESCAPE"){
terminal.removeListener("key","cli-render-string-structure-add")
input.abort()
await backFn()
}
},({id:"cli-render-string-structure-add"} as any))
const answer = await input.promise
terminal.removeListener("key","cli-render-string-structure-add")
if (typeof answer != "string") return
//run config checker
const newValue = answer.replaceAll("\\n","\n")
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await nextFn(newValue)
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.red("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderAdditionConfigStringStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,answer)
}
}
async function renderAdditionConfigNullStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerNullStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){
renderHeader([...path,parentIndex])
terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the null property "+ansis.blue("\""+parentIndex+"\"") : "null property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const answer = await terminal.singleColumnMenu(["null"],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
//run config checker
const newValue = null
const newPath = [...path]
newPath.shift()
checker.messages = [] //manually clear previous messages
const isDataValid = structure.check(checker,newValue,newPath)
if (isDataValid){
terminal.bold.blue("\n\n✅ Variable saved succesfully!")
await utilities.timer(400)
await nextFn(newValue)
}else{
const messages = checker.messages.map((msg) => "=> ["+msg.type.toUpperCase()+"] "+msg.message).join("\n")
terminal.bold.blue("\n\n❌ Variable is invalid! Please try again!")
terminal.red("\n"+messages)
await utilities.timer(1000+(2000*checker.messages.length))
await renderAdditionConfigNullStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)
}
}
async function renderAdditionConfigArrayStructureSelector(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerArrayStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[],localData:any[]=[]){
renderHeader([...path,parentIndex])
terminal(ansis.bold.green("Please select what you would like to do with the new array.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
if (!structure.options.propertyChecker) return await backFn()
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const propertyName = structure.options.cliDisplayPropertyName ?? "index"
const answer = await terminal.singleColumnMenu(localData.length < 1 ? [ansis.magenta("-> Continue to next variable"),"Add "+propertyName] : [
ansis.magenta("-> Continue to next variable"),
"Add "+propertyName,
"Edit "+propertyName,
"Move "+propertyName,
"Remove "+propertyName,
"Duplicate "+propertyName,
],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
const backFnFunc = async () => {await renderAdditionConfigArrayStructureSelector(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath,localData)}
if (answer.canceled) return await backFn()
if (answer.selectedIndex == 0) await nextFn(localData)
else if (answer.selectedIndex == 1) await chooseAdditionConfigStructure(checker,backFnFunc,async (newData) => {
localData[localData.length] = newData
await backFnFunc()
},structure.options.propertyChecker,localData,localData.length,path,[])
else if (answer.selectedIndex == 2) await renderConfigArrayStructureEditSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path)
else if (answer.selectedIndex == 3) await renderconfigArrayStructureMoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path)
else if (answer.selectedIndex == 4) await renderconfigArrayStructureRemoveSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path)
else if (answer.selectedIndex == 5) await renderConfigArrayStructureDuplicateSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path)
}
async function renderAdditionConfigTypeSwitchStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){
renderHeader(path)
terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n"))
terminal.gray("\nProperty: "+ansis.bold.blue(structure.options.cliDisplayName ?? [...localPath,parentIndex].join("."))+"\n")
terminal.gray("Description: "+ansis.bold(structure.options.cliDisplayDescription ?? "/")+"\n")
const actionsList: string[] = []
if (structure.options.boolean) actionsList.push("Create as boolean")
if (structure.options.string) actionsList.push("Create as string")
if (structure.options.number) actionsList.push("Create as number")
if (structure.options.object) actionsList.push("Create as object")
if (structure.options.array) actionsList.push("Create as array/list")
if (structure.options.null) actionsList.push("Create as null")
const answer = await terminal.singleColumnMenu(actionsList,{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
//run selected structure editor (untested)
if (answer.selectedText.startsWith("Create as boolean") && structure.options.boolean) await renderAdditionConfigBooleanStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.boolean,parent,parentIndex,path,localPath)
else if (answer.selectedText.startsWith("Create as string") && structure.options.string) await renderAdditionConfigStringStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.string,parent,parentIndex,path,localPath)
else if (answer.selectedText.startsWith("Create as number") && structure.options.number) await renderAdditionConfigNumberStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.number,parent,parentIndex,path,localPath)
else if (answer.selectedText.startsWith("Create as object") && structure.options.object) await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.object,parent,parentIndex,path,localPath)
else if (answer.selectedText.startsWith("Create as array/list") && structure.options.array) await renderAdditionConfigArrayStructureSelector(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.array,parent,parentIndex,path,localPath)
else if (answer.selectedText.startsWith("Create as null") && structure.options.null) await renderAdditionConfigNullStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.null,parent,parentIndex,path,localPath)
}
+213
View File
@@ -0,0 +1,213 @@
import {opendiscord, api, utilities} from "../../index"
import {Terminal, terminal} from "terminal-kit"
import ansis from "ansis"
import * as discord from "discord.js"
import {renderHeader} from "./cli"
export async function renderQuickSetup(backFn:() => api.ODPromiseVoid){
if (quickSetupRequiresReset()) await renderQuickSetupWarning(backFn)
else await renderQuickSetupWelcome(backFn)
}
function quickSetupRequiresReset(): boolean {
const generalConfig = opendiscord.configs.get("opendiscord:general")
if (generalConfig.data.token != "your bot token here! (or leave empty when using 'tokenFromENV')") return true
if (generalConfig.data.mainColor != "#f8ba00") return true
if (generalConfig.data.language != "english") return true
if (generalConfig.data.prefix != "!ticket ") return true
if (generalConfig.data.serverId != "discord server id") return true
return false
}
async function renderQuickSetupWarning(backFn:() => api.ODPromiseVoid) {
renderHeader("⏱️ Open Ticket Quick Setup: Warning")
terminal.bold(ansis.yellow("WARNING! ")+ansis.red("By using the 'Quick Setup' feature, your current config will be completely resetted!"))
terminal.gray("\n\nAre you sure you want to continue?")
const answer = await terminal.singleColumnMenu([
ansis.green("✅ No, take me back."),
ansis.red("🚨 Yes, continue and reset the config.")
],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled || answer.selectedIndex == 0) return await backFn()
if (answer.selectedIndex == 1) await renderQuickSetupWelcome(async () => {await renderQuickSetupWarning(backFn)})
}
async function renderQuickSetupWelcome(backFn:() => api.ODPromiseVoid){
renderHeader("⏱️ Open Ticket Quick Setup: Introduction")
terminal.bold.underline.blue("Open Ticket: Quick Setup\n")
terminal.gray([
"Hi there! Thank you for downloading and installing Open Ticket.",
"You have chosen to configure the bot using the 'Quick Setup CLI'.",
"",
"This program will help you with configuring Open Ticket using a step-by-step method.",
"If you've ever used Google Forms, then this will probably be very easy for you 😉.",
"",
ansis.magenta("The configuration should normally only take around 5 minutes."),
ansis.magenta("Once you've completed the form, the bot is technically ready for usage!")
].join("\n")+"\n\n")
const answer = await terminal.singleColumnMenu([
ansis.green("Press 'Enter' to start!")
],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
else if (answer.selectedIndex == 0) await renderQuickSetupDevPortal(async () => {await renderQuickSetupWelcome(backFn)})
}
async function renderQuickSetupDevPortal(backFn:() => api.ODPromiseVoid){
renderHeader("⏱️ Open Ticket Quick Setup: Discord Bot & Developer Portal")
terminal.bold.blue("(Step 1) Have you already created a Discord bot to use for Open Ticket?\n")
const answer = await terminal.singleColumnMenu([
"✅ Yes I have, and it has been invited to the server.",
"❓ No not yet, I don't know how to create one.",
"👶 I've never seen a Discord bot before.",
],{
leftPadding:"> ",
style:terminal.gray,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return await backFn()
else if (answer.selectedIndex == 0) await renderQuickSetupBotToken(async () => {await renderQuickSetupDevPortal(backFn)})
else if (answer.selectedIndex == 1) await renderQuickSetupDevPortalGuide(0,async () => {await renderQuickSetupDevPortal(backFn)})
else if (answer.selectedIndex == 2) await renderQuickSetupDevPortalGuide(1,async () => {await renderQuickSetupDevPortal(backFn)})
}
async function renderQuickSetupDevPortalGuide(variation:0|1,backFn:() => api.ODPromiseVoid){
renderHeader("⏱️ Open Ticket Quick Setup: Discord Bot & Developer Portal")
if (variation == 0){
terminal.bold.blue("(Step 1.1) You've mentioned that you don't know how to create a Discord bot.\n\n")
terminal.gray("Please visit the following URL for a step-by-step guide on how to create a Discord bot.\nIf it still doesn't work, join our Discord server and we will help you further!\n"+ansis.magenta("=> https://otdocs.dj-dj.be/docs/guides/get-started#bot\n\n"))
}else{
terminal.bold.blue("(Step 1.2) You've mentioned that you've never seen Discord bot before.\n\n")
terminal.gray("How did you even download Open Ticket 🤪? But all jokes aside, we have a step-by-step guide on how to create a Discord bot.\nIf it still doesn't work, join our Discord server and we will help you further!\n"+ansis.magenta("=> https://otdocs.dj-dj.be/docs/guides/get-started#bot\n\n"))
}
const answer = await terminal.singleColumnMenu([
ansis.green("✅ Alright, I've made the bot. Take me back please!")
],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
return await backFn()
}
async function quickSetupLogin(token:string): Promise<api.ODClientManager|null> {
const client = new api.ODClientManager(opendiscord.debug)
client.token = token
client.intents.push("Guilds","GuildMessages","DirectMessages","GuildEmojisAndStickers","GuildMembers","MessageContent","GuildWebhooks","GuildInvites")
client.privileges.push("MessageContent","GuildMembers")
client.partials.push("Channel","Message")
client.permissions.push("AddReactions","AttachFiles","CreatePrivateThreads","CreatePublicThreads","EmbedLinks","ManageChannels","ManageGuild","ManageMessages","ChangeNickname","ManageRoles","ManageThreads","ManageWebhooks","MentionEveryone","ReadMessageHistory","SendMessages","SendMessagesInThreads","UseApplicationCommands","UseExternalEmojis","ViewAuditLog","ViewChannel")
client.initClient()
//client login
return new Promise(async (resolve) => {
try{
client.readyListener = async () => {
client.activity.setStatus("custom","Configuring Open Ticket...","idle",true)
resolve(client)
}
const success = await client.login(true)
if (!success) resolve(null)
}catch(err){
resolve(null)
}
})
}
async function renderQuickSetupBotToken(backFn:() => api.ODPromiseVoid){
renderHeader("⏱️ Open Ticket Quick Setup: Bot Token")
terminal.bold.blue("(Step 2) Please insert the token of your discord bot.\n")
terminal.gray("It will be safely stored in the 'config/general.json' file.\n\n> ")
const answer = await terminal.inputField({
style:terminal.white,
hintStyle:terminal.gray,
cancelable:true
}).promise
if (typeof answer != "string") return await backFn()
else{
const result = await quickSetupLogin(answer)
if (!result){
//login failure
terminal.red.bold("\n\n❌ Something went wrong with logging into the bot.\n")
terminal.gray("Please try again and check your token for any mistakes.\nAlso make sure the permissions and priviliged gateaway intents are configured correctly in the developer panel.")
await utilities.timer(3000)
//retry
await renderQuickSetupBotToken(backFn)
}else{
//login success
terminal.green.bold("\n\n✅ Succesfully logged into the bot.\n")
terminal.gray("Your bot should be online with the status 'Configuring Open Ticket...'.")
await utilities.timer(3000)
//continue
await renderQuickSetupServer(result,async () => {await renderQuickSetupBotToken(backFn)})
}
}
}
async function renderQuickSetupServer(client:api.ODClientManager,backFn:() => api.ODPromiseVoid){
renderHeader("⏱️ Open Ticket Quick Setup: Discord Server")
terminal.bold.blue("(Step 3) Please select a Discord Server to use.\n")
terminal.gray("The bot will only work in this server.\n\n")
const guilds = await client.getGuilds()
const nameList = guilds.map((g) => g.name)
const longestName = utilities.getLongestLength(nameList)
const guildList = guilds.map((g) => g.name.padEnd(longestName+5," ")+ansis.gray(" ("+g.id+")"))
const answer = await terminal.singleColumnMenu([ansis.green("🔄 <Refresh List>"),...guildList],{
leftPadding:"> ",
style:terminal.cyan,
selectedStyle:terminal.bgDefaultColor.bold,
submittedStyle:terminal.bgBlue,
extraLines:2,
cancelable:true
}).promise
if (answer.canceled) return backFn()
if (answer.selectedIndex == 0) return await renderQuickSetupServer(client,backFn)
const server = guilds[answer.selectedIndex-1]
await renderQuickSetupAdminRoles(client,server,[],async () => {await renderQuickSetupServer(client,backFn)})
}
async function renderQuickSetupAdminRoles(client:api.ODClientManager,guild:discord.Guild,selectedAdmins:string[],backFn:() => api.ODPromiseVoid){
renderHeader("⏱️ Open Ticket Quick Setup: Admin Roles")
terminal.bold.blue("(Step 4) Please select all 'Global Admins' roles to use.\n")
terminal.gray("Users with one of these roles will be able to access & interact with all tickets.\n\n")
}
+9 -1
View File
@@ -31,12 +31,13 @@ moduleInstalled("discord.js",true)
moduleInstalled("ansis",true)
moduleInstalled("formatted-json-stringify",true)
moduleInstalled("typescript",true)
moduleInstalled("terminal-kit",true)
tempError()
//init API
import * as api from "../api/api" //import for local use
export * as api from "../api/api" //export to other parts of bot
import ansis from "ansis" //import ansis for usage in initialization
export const opendiscord = new api.ODMain()
console.log("\n--------------------------- OPEN TICKET STARTUP ---------------------------")
@@ -103,6 +104,10 @@ export interface ODUtilities {
* Same as `string.replace(search, value)` but with async compatibility
*/
asyncReplace(text:string, regex:RegExp, func:(value:string,...args:any[]) => Promise<string>): Promise<string>
/**## getLongestLength `utility function`
* Get the length of the longest string in the array.
*/
getLongestLength(text:string[]): number
/**## easterEggs `utility object`
* Object containing data for Open Ticket easter eggs.
*/
@@ -208,6 +213,9 @@ export const utilities: ODUtilities = {
})
return result
},
getLongestLength(texts:string[]): number {
return Math.max(...texts.map((t) => ansis.strip(t).length))
},
easterEggs:{
/* THANK YOU TO ALL OUR CONTRIBUTORS!!! */
creator:"779742674932072469", //DJj123dj
+29 -21
View File
@@ -1,5 +1,24 @@
import {opendiscord, api, utilities} from "../../index"
/**Check if migration is required. Returns the last version used in the database. */
async function isMigrationRequired(): Promise<false|api.ODVersion> {
const rawVersion = await opendiscord.databases.get("opendiscord:global").get("opendiscord:last-version","opendiscord:version")
if (!rawVersion) return false
const version = api.ODVersion.fromString("opendiscord:last-version",rawVersion)
if (opendiscord.versions.get("opendiscord:version").compare(version) == "higher"){
return version
}else return false
}
/**Save all versions in `opendiscord.versions` to the global database. */
async function saveAllVersionsToDatabase(){
const globalDatabase = opendiscord.databases.get("opendiscord:global")
await opendiscord.versions.loopAll(async (version,id) => {
await globalDatabase.set("opendiscord:last-version",id.value,version.toString())
})
}
export const loadVersionMigrationSystem = async () => {
//ENTER MIGRATION CONTEXT
await preloadMigrationContext()
@@ -29,6 +48,8 @@ export const loadVersionMigrationSystem = async () => {
if (opendiscord.flags.exists("opendiscord:soft-plugins") && opendiscord.flags.get("opendiscord:soft-plugins").value) opendiscord.defaults.setDefault("softPluginLoading",true)
if (opendiscord.flags.exists("opendiscord:crash") && opendiscord.flags.get("opendiscord:crash").value) opendiscord.defaults.setDefault("crashOnError",true)
if (opendiscord.flags.exists("opendiscord:force-slash-update") && opendiscord.flags.get("opendiscord:force-slash-update").value) opendiscord.defaults.setDefault("forceSlashCommandRegistration",true)
if (opendiscord.flags.exists("opendiscord:silent") && opendiscord.flags.get("opendiscord:silent").value) opendiscord.console.silent = true
//LEAVE MIGRATION CONTEXT
await unloadMigrationContext()
@@ -36,7 +57,8 @@ export const loadVersionMigrationSystem = async () => {
return lastVersion
}
const preloadMigrationContext = async () => {
/**Initialize the migration context by loading the built-in flags, configs & databases. */
async function preloadMigrationContext(){
opendiscord.debug.debug("-- MIGRATION CONTEXT START --")
await (await import("../../data/framework/flagLoader.js")).loadAllFlags()
await opendiscord.flags.init()
@@ -47,7 +69,8 @@ const preloadMigrationContext = async () => {
opendiscord.debug.visible = true
}
const unloadMigrationContext = async () => {
/**Unload the migration context to start the bot normally. */
async function unloadMigrationContext(){
opendiscord.debug.visible = false
await opendiscord.databases.loopAll((database,id) => {opendiscord.databases.remove(id)})
await opendiscord.configs.loopAll((config,id) => {opendiscord.configs.remove(id)})
@@ -55,16 +78,8 @@ const unloadMigrationContext = async () => {
opendiscord.debug.debug("-- MIGRATION CONTEXT END --")
}
const isMigrationRequired = async (): Promise<false|api.ODVersion> => {
const rawVersion = await opendiscord.databases.get("opendiscord:global").get("opendiscord:last-version","opendiscord:version")
if (!rawVersion) return false
const version = api.ODVersion.fromString("opendiscord:last-version",rawVersion)
if (opendiscord.versions.get("opendiscord:version").compare(version) == "higher"){
return version
}else return false
}
const loadAllVersionMigrations = async (lastVersion:api.ODVersion) => {
/**Execute all version migration functions which are handled in the restricted migration context. */
async function loadAllVersionMigrations(lastVersion:api.ODVersion){
const migrations = (await import("./migration.js")).migrations
migrations.sort((a,b) => {
const comparison = a.version.compare(b.version)
@@ -83,7 +98,8 @@ const loadAllVersionMigrations = async (lastVersion:api.ODVersion) => {
}
}
export const loadAllAfterInitVersionMigrations = async (lastVersion:api.ODVersion) => {
/**Execute all version migration functions which are handled in the normal startup sequence. */
export async function loadAllAfterInitVersionMigrations(lastVersion:api.ODVersion){
const migrations = (await import("./migration.js")).migrations
migrations.sort((a,b) => {
const comparison = a.version.compare(b.version)
@@ -101,11 +117,3 @@ export const loadAllAfterInitVersionMigrations = async (lastVersion:api.ODVersio
}
}
}
const saveAllVersionsToDatabase = async () => {
const globalDatabase = opendiscord.databases.get("opendiscord:global")
await opendiscord.versions.loopAll(async (version,id) => {
await globalDatabase.set("opendiscord:last-version",id.value,version.toString())
})
}
+277 -250
View File
@@ -3,11 +3,11 @@ import {opendiscord, api, utilities} from "../../index"
const generalConfig = opendiscord.configs.get("opendiscord:general")
export const loadAllConfigCheckers = async () => {
opendiscord.checkers.add(new api.ODChecker("opendiscord:general",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:general"),defaultGeneralStructure))
opendiscord.checkers.add(new api.ODChecker("opendiscord:options",opendiscord.checkers.storage,1,opendiscord.configs.get("opendiscord:options"),defaultOptionsStructure))
opendiscord.checkers.add(new api.ODChecker("opendiscord:panels",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:panels"),defaultPanelsStructure))
opendiscord.checkers.add(new api.ODChecker("opendiscord:questions",opendiscord.checkers.storage,2,opendiscord.configs.get("opendiscord:questions"),defaultQuestionsStructure))
opendiscord.checkers.add(new api.ODChecker("opendiscord:transcripts",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:transcripts"),defaultTranscriptsStructure))
opendiscord.checkers.add(new api.ODChecker("opendiscord:general",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:general"),defaultGeneralStructure,{cliDisplayName:"General Config",cliDisplayDescription:"Configure the bot token, status, colors, permissions & more."}))
opendiscord.checkers.add(new api.ODChecker("opendiscord:questions",opendiscord.checkers.storage,2,opendiscord.configs.get("opendiscord:questions"),defaultQuestionsStructure,{cliDisplayName:"Questions Config",cliDisplayDescription:"Create, modify & delete questions which are used in options."}))
opendiscord.checkers.add(new api.ODChecker("opendiscord:options",opendiscord.checkers.storage,1,opendiscord.configs.get("opendiscord:options"),defaultOptionsStructure,{cliDisplayName:"Options Config",cliDisplayDescription:"Create, modify & delete options which are used in panels."}))
opendiscord.checkers.add(new api.ODChecker("opendiscord:panels",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:panels"),defaultPanelsStructure,{cliDisplayName:"Panels Config",cliDisplayDescription:"Create, modify & delete panels which can be spawned in discord."}))
opendiscord.checkers.add(new api.ODChecker("opendiscord:transcripts",opendiscord.checkers.storage,0,opendiscord.configs.get("opendiscord:transcripts"),defaultTranscriptsStructure,{cliDisplayName:"Transcript Config",cliDisplayDescription:"Configure everything related to transcripts."}))
}
export const loadAllConfigCheckerFunctions = async () => {
@@ -116,57 +116,59 @@ export const registerDefaultCheckerCustomTranslations = (tm:api.ODCheckerTransla
tm.quickTranslate(lm,"checker.messages.dropdownOption","message","opendiscord:dropdown-option") // A panel with dropdown enabled can only contain options of the 'ticket' type!
//TODO TRANSLATION!!!
//tm.quickTranslate(lm,"checker.messages.TODO","message","opendiscord:invalid-version") // The version specified in your config is invalid! Make sure you have updated it to the latest version!
//tm.quickTranslate(lm,"checker.messages.TODO","message","opendiscord:invalid-version") // The version specified in your config does not match! Make sure you have updated the config to the latest version!
}
//UTILITY FUNCTIONS
const createMsgStructure = (id:api.ODValidId) => {
const createMsgStructure = (id:api.ODValidId,displayName:string) => {
return new api.ODCheckerObjectStructure(id,{children:[
{key:"dm",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:msg-dm",{})},
{key:"logs",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:msg-logs",{})},
]})
{key:"dm",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:msg-dm",{cliInitDefaultValue:false,cliDisplayName:"DM Enabled",cliDisplayDescription:"Will this action be sent in DM to the creator of the ticket?"})},
{key:"logs",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:msg-logs",{cliInitDefaultValue:true,cliDisplayName:"Logs Enabled",cliDisplayDescription:"Will this action be sent in the Discord log channel?"})},
],cliDisplayName:displayName,cliDisplayDescription:"Configure which places this action gets logged/sent to."})
}
const createTicketEmbedStructure = (id:api.ODValidId) => {
return new api.ODCheckerEnabledObjectStructure(id,{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure(id,{children:[
{key:"title",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-text",{maxLength:256})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-description",{maxLength:4096})},
{key:"customColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:ticket-embed-color",true,true)},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the embed of this message."})},
{key:"title",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-text",{maxLength:256,cliDisplayName:"Title",cliDisplayDescription:"The title of this embed."})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-description",{maxLength:4096,cliDisplayName:"Description",cliDisplayDescription:"The description of this embed."})},
{key:"customColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:ticket-embed-color",true,true,{cliDisplayName:"Custom Color",cliDisplayDescription:"Set a custom color for this embed. When empty, the default bot color will be used."})},
{key:"image",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:ticket-embed-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]})},
{key:"thumbnail",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:ticket-embed-thumbnail",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]})},
{key:"fields",optional:false,priority:0,checker:new api.ODCheckerArrayStructure("opendiscord:ticket-embed-fields",{allowedTypes:["object"],propertyChecker:new api.ODCheckerObjectStructure("opendiscord:ticket-embed-fields",{children:[
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-field-name",{minLength:1,maxLength:256})},
{key:"value",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-field-value",{minLength:1,maxLength:1024})},
{key:"inline",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-field-inline",{})}
]})})},
{key:"timestamp",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-timestamp",{})}
]})})
{key:"image",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:ticket-embed-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]},{cliDisplayName:"Image",cliDisplayDescription:"Add an image to the embed using an image URL."})},
{key:"thumbnail",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:ticket-embed-thumbnail",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]},{cliDisplayName:"Thumbnail",cliDisplayDescription:"Add a thumbnail to the embed using an image URL."})},
{key:"fields",optional:false,priority:0,checker:new api.ODCheckerArrayStructure("opendiscord:ticket-embed-fields",{allowedTypes:["object"],cliDisplayPropertyName:"embed field",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:ticket-embed-fields",{children:[
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-field-name",{minLength:1,maxLength:256,cliDisplayName:"Field Name",cliDisplayDescription:"The name/title of this embed field."})},
{key:"value",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-embed-field-value",{minLength:1,maxLength:1024,cliDisplayName:"Field Value",cliDisplayDescription:"The value/description of this embed field."})},
{key:"inline",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-field-inline",{cliDisplayName:"Field Inline",cliDisplayDescription:"Should this field be displayed inline with other fields?"})}
],cliDisplayName:"Field",cliDisplayDescription:"Customise and configure an embed field."}),cliDisplayName:"Fields",cliDisplayDescription:"Customise and configure embed fields."})},
{key:"timestamp",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-embed-timestamp",{cliDisplayName:"Timestamp",cliDisplayDescription:"Add a timestamp to the embed."})}
],cliDisplayName:"Message Embed",cliDisplayDescription:"Configure the embed of this message."}),cliInitDefaultValue:{enabled:false,title:"",description:"",customColor:"",image:"",thumbnail:"",fields:[],timestamp:false},cliDisplayName:"Message Embed",cliDisplayDescription:"Configure the embed of this message."})
}
const createTicketPingStructure = (id:api.ODValidId) => {
return new api.ODCheckerObjectStructure(id,{children:[
{key:"@here",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-here",{})},
{key:"@everyone",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-everyone",{})},
{key:"custom",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ping-custom","role",[],{allowDoubles:false})},
]})
{key:"@here",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-here",{cliDisplayName:"@here Ping",cliDisplayDescription:"Enable/disable an '@here' ping."})},
{key:"@everyone",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-ping-everyone",{cliDisplayName:"@everyone Ping",cliDisplayDescription:"Enable/disable an '@everyone' ping."})},
{key:"custom",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ping-custom","role",[],{allowDoubles:false,cliDisplayPropertyName:"custom role id",cliDisplayName:"Custom Role Ping",cliDisplayDescription:"Choose your own roles to ping in this message."},{cliDisplayName:"Custom Role",cliDisplayDescription:"The discord role ID of a custom mention/ping."})},
],cliInitDefaultValue:{"@here":true,"@everyone":false,custom:[],cliDisplayName:"Message Pings",cliDisplayDescription:"Configure the pings/mentions of this message."}})
}
const createPanelEmbedStructure = (id:api.ODValidId) => {
return new api.ODCheckerEnabledObjectStructure(id,{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure(id,{children:[
{key:"title",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-text",{maxLength:256})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-description",{maxLength:4096})},
{key:"customColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:panel-embed-color",true,true)},
{key:"url",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:panel-embed-url",true,{allowHttp:false})},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-embed-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the embed of this panel."})},
{key:"title",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-text",{maxLength:256,cliDisplayName:"Title",cliDisplayDescription:"The title of this embed."})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-description",{maxLength:4096,cliDisplayName:"Description",cliDisplayDescription:"The description of this embed."})},
{key:"customColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:panel-embed-color",true,true,{cliDisplayName:"Custom Color",cliDisplayDescription:"Set a custom color for this embed. When empty, the default bot color will be used."})},
{key:"url",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:panel-embed-url",true,{allowHttp:false},{cliDisplayName:"URL",cliDisplayDescription:"Set a URL which will be displayed in the title of the embed."})},
{key:"image",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:panel-embed-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]})},
{key:"thumbnail",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:panel-embed-thumbnail",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]})},
{key:"image",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:panel-embed-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]},{cliDisplayName:"Image",cliDisplayDescription:"Add an image to the embed using an image URL."})},
{key:"thumbnail",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:panel-embed-thumbnail",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]},{cliDisplayName:"Thumbnail",cliDisplayDescription:"Add a thumbnail to the embed using an image URL."})},
{key:"footer",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-footer",{maxLength:2048})},
{key:"fields",optional:false,priority:0,checker:new api.ODCheckerArrayStructure("opendiscord:panel-embed-fields",{allowedTypes:["object"],propertyChecker:new api.ODCheckerObjectStructure("opendiscord:panel-embed-fields",{children:[
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-field-name",{minLength:1,maxLength:256})},
{key:"value",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-field-value",{minLength:1,maxLength:1024})},
{key:"inline",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-embed-field-inline",{})}
]})})},
{key:"timestamp",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-embed-timestamp",{})}
]})})
{key:"footer",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-footer",{maxLength:2048,cliDisplayName:"Footer",cliDisplayDescription:"The footer of this embed."})},
{key:"fields",optional:false,priority:0,checker:new api.ODCheckerArrayStructure("opendiscord:panel-embed-fields",{allowedTypes:["object"],cliDisplayPropertyName:"embed field",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:panel-embed-fields",{children:[
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-field-name",{minLength:1,maxLength:256,cliDisplayName:"Field Name",cliDisplayDescription:"The name/title of this embed field."})},
{key:"value",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-embed-field-value",{minLength:1,maxLength:1024,cliDisplayName:"Field Value",cliDisplayDescription:"The value/description of this embed field."})},
{key:"inline",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-embed-field-inline",{cliDisplayName:"Field Inline",cliDisplayDescription:"Should this field be displayed inline with other fields?"})}
],cliDisplayName:"Field",cliDisplayDescription:"Customise and configure an embed field."}),cliDisplayName:"Fields",cliDisplayDescription:"Customise and configure embed fields."})},
{key:"timestamp",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-embed-timestamp",{cliDisplayName:"Timestamp",cliDisplayDescription:"Add a timestamp to the embed."})}
],cliDisplayName:"Panel Embed",cliDisplayDescription:"Configure the embed of this panel."}),cliInitDefaultValue:{enabled:false,title:"",description:"",customColor:"",url:"",image:"",thumbnail:"",footer:"",fields:[],timestamp:false},cliDisplayName:"Panel Embed",cliDisplayDescription:"Configure the embed of this panel."})
}
function loadFromEnv(){
@@ -177,8 +179,8 @@ function loadFromEnv(){
//STRUCTURES
export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendiscord:general",{children:[
//STATUS
{key:"_INFO",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:info",{children:[
//INFO
{key:"_INFO",optional:false,priority:0,cliHideInEditMode:true,checker:new api.ODCheckerObjectStructure("opendiscord:info",{children:[
{key:"support",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:info-support",{choices:["https://otdocs.dj-dj.be"]})},
{key:"discord",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:info-discord",{choices:["https://discord.dj-dj.be"]})},
{key:"version",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:info-version",{custom(checker,value,locationTrace,locationId,locationDocs) {
@@ -186,16 +188,16 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis
if (typeof value != "string") return false
else if (value != "open-ticket-"+opendiscord.versions.get("opendiscord:version").toString()){
checker.createMessage("opendiscord:invalid-version","warning","The version specified in your config is invalid! Make sure you have updated it to the latest version!",lt,null,[],locationId,locationDocs)
checker.createMessage("opendiscord:invalid-version","warning","The version specified in your config does not match! Make sure you have updated the config to the latest version!",lt,null,[],locationId,locationDocs)
return false
}else return true
},})},
]})},
//BASIC
{key:"token",optional:false,priority:0,checker:(loadFromEnv()) ? new api.ODCheckerStringStructure("opendiscord:token-disabled",{}) : new api.ODCheckerCustomStructure_DiscordToken("opendiscord:token")},
{key:"tokenFromENV",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:token-env",{})},
{key:"mainColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:main-color",true,false)},
{key:"token",optional:false,priority:0,checker:(loadFromEnv()) ? new api.ODCheckerStringStructure("opendiscord:token-disabled",{cliDisplayName:"Token",cliDisplayDescription:"The token of your discord bot."}) : new api.ODCheckerCustomStructure_DiscordToken("opendiscord:token",{cliDisplayName:"Token",cliDisplayDescription:"The token of your discord bot."})},
{key:"tokenFromENV",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:token-env",{cliDisplayName:"Token From ENV",cliDisplayDescription:"Use the token from the .env file instead of general.json."})},
{key:"mainColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:main-color",true,false,{cliDisplayName:"Main Color",cliDisplayDescription:"The main color of your bot, used in almost all embeds."})},
{key:"language",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:language",{
custom:(checker,value,locationTrace,locationId,locationDocs) => {
const lt = checker.locationTraceDeref(locationTrace)
@@ -206,109 +208,113 @@ export const defaultGeneralStructure = new api.ODCheckerObjectStructure("opendis
return false
}else return true
},
cliAutocompleteList:opendiscord.defaults.getDefault("languageList"),
cliDisplayName:"Language",
cliDisplayDescription:"The language of the bot. Visit README.md for a list of available translations."
})},
{key:"prefix",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:prefix",{minLength:1})},
{key:"serverId",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:server-id","server",false,[])},
{key:"globalAdmins",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:global-admins","role",[],{allowDoubles:false})},
{key:"slashCommands",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:slash-commands",{})},
{key:"textCommands",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:text-commands",{})},
{key:"prefix",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:prefix",{minLength:1,cliDisplayName:"Prefix",cliDisplayDescription:"The prefix used for the text-commands from the bot."})},
{key:"serverId",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:server-id","server",false,[],{cliDisplayName:"Server Id",cliDisplayDescription:"The ID of the discord server you will be using this bot in."})},
{key:"globalAdmins",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:global-admins","role",[],{allowDoubles:false,cliDisplayPropertyName:"global admin role",cliDisplayName:"Global Admin Roles",cliDisplayDescription:"A list of role IDs that are able to interact with all commands and tickets."},{cliDisplayName:"Global Admin Role",cliDisplayDescription:"The discord role ID of a global admin."})},
{key:"slashCommands",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:slash-commands",{cliDisplayName:"Enable Slash Commands",cliDisplayDescription:"Enable/disable slash commands in the bot. When disabled, the commands will not be displayed."})},
{key:"textCommands",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:text-commands",{cliDisplayName:"Enable Text Commands",cliDisplayDescription:"Enable/disable text commands in the bot. (Disabling is recommended in large servers)"})},
//STATUS
{key:"status",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:status",{
property:"enabled",
enabledValue:true,
checker:new api.ODCheckerObjectStructure("opendiscord:status",{children:[
{key:"type",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:status-type",{choices:["listening","watching","playing","custom"]})},
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:status-text",{minLength:1,maxLength:128})},
{key:"status",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:status-type",{choices:["online","invisible","idle","dnd"]})},
]})
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:status-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the status. When disabled, the bot will be online without any status."})},
{key:"type",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:status-type",{choices:["listening","watching","playing","custom"],cliDisplayName:"Type",cliDisplayDescription:"The type of status: Listening, Watching, Playing or Custom."})},
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:status-text",{minLength:1,maxLength:128,cliDisplayName:"Text",cliDisplayDescription:"The text displayed in the status."})},
{key:"status",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:status-status",{choices:["online","invisible","idle","dnd"],cliDisplayName:"Status",cliDisplayDescription:"The profile status of the bot: Online, Invisible, Idle or Do Not Disturb."})},
],cliDisplayName:"Bot Status",cliDisplayDescription:"Manage the status of the bot."}),
cliDisplayName:"Bot Status",
cliDisplayDescription:"Manage the status of the bot."
})},
//SYSTEM
{key:"system",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:system",{children:[
{key:"removeParticipantsOnClose",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:remove-participants-on-close",{})},
{key:"replyOnTicketCreation",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-ticket-creation",{})},
{key:"replyOnReactionRole",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-reaction-role",{})},
{key:"useTranslatedConfigChecker",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:use-translated-config-checker",{})},
{key:"preferSlashOverText",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:prefer-slash-over-text",{})},
{key:"sendErrorOnUnknownCommand",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:send-error-on-unknown-command",{})},
{key:"questionFieldsInCodeBlock",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:question-fields-in-code-block",{})},
{key:"disableVerifyBars",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:disable-verify-bars",{})},
{key:"useRedErrorEmbeds",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:use-red-error-embeds",{})},
{key:"emojiStyle",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:emoji-style",{choices:["before","after","double","disabled"]})},
{key:"removeParticipantsOnClose",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:remove-participants-on-close",{cliDisplayName:"Remove Participants On Close",cliDisplayDescription:"When enabled, all participants except admins will be removed from the ticket."})},
{key:"replyOnTicketCreation",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-ticket-creation",{cliDisplayName:"Reply On Ticket Creation",cliDisplayDescription:"When enabled, the bot will send an ephemeral reply in the channel of the panel when creating a ticket."})},
{key:"replyOnReactionRole",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:reply-on-reaction-role",{cliDisplayName:"Reply On Reaction Role",cliDisplayDescription:"When enabled, the bot will send an ephemeral reply in the channel of the panel when using a role button."})},
{key:"useTranslatedConfigChecker",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:use-translated-config-checker",{cliDisplayName:"Use Translated Config Checker",cliDisplayDescription:"Use a translated config checker to better understand the errors the bot gives."})},
{key:"preferSlashOverText",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:prefer-slash-over-text",{cliDisplayName:"Prefer Slash Over Text",cliDisplayDescription:"Prefer displaying slash commands over text commands in help menus."})},
{key:"sendErrorOnUnknownCommand",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:send-error-on-unknown-command",{cliDisplayName:"Send Error On Unknown Command",cliDisplayDescription:"Send an error when using the text-command prefix without a valid command."})},
{key:"questionFieldsInCodeBlock",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:question-fields-in-code-block",{cliDisplayName:"Questions Fields In Code Blocks",cliDisplayDescription:"Display question fields in code blocks instead of plain text."})},
{key:"disableVerifyBars",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:disable-verify-bars",{cliDisplayName:"Disable Verifybars",cliDisplayDescription:"Disable the (✅/❌) verify buttons in all commands. (Not recommended)"})},
{key:"useRedErrorEmbeds",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:use-red-error-embeds",{cliDisplayName:"Use Red Error Embeds",cliDisplayDescription:"Display all error messages with a red border instead of the default color of the bot."})},
{key:"emojiStyle",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:emoji-style",{choices:["before","after","double","disabled"],cliDisplayName:"Emoji Style",cliDisplayDescription:"Choose how the bot will display emojis in message titles. (Visit docs for more info)"})},
{key:"enableTicketClaimButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-claim-buttons",{})},
{key:"enableTicketCloseButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-close-buttons",{})},
{key:"enableTicketPinButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-pin-buttons",{})},
{key:"enableTicketDeleteButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-delete-buttons",{})},
{key:"enableTicketActionWithReason",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-action-with-reason",{})},
{key:"enableDeleteWithoutTranscript",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-delete-without-transcript",{})},
{key:"enableTicketClaimButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-claim-buttons",{cliDisplayName:"Enable Ticket Claim Buttons",cliDisplayDescription:"Enable/disable buttons for claiming a ticket. Be aware that this doesn't disable the command!"})},
{key:"enableTicketCloseButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-close-buttons",{cliDisplayName:"Enable Ticket Close Buttons",cliDisplayDescription:"Enable/disable buttons for closing a ticket. Be aware that this doesn't disable the command!"})},
{key:"enableTicketPinButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-pin-buttons",{cliDisplayName:"Enable Ticket Pin Buttons",cliDisplayDescription:"Enable/disable buttons for pinning a ticket. Be aware that this doesn't disable the command!"})},
{key:"enableTicketDeleteButtons",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-delete-buttons",{cliDisplayName:"Enable Ticket Delete Buttons",cliDisplayDescription:"Enable/disable buttons for deleting a ticket. Be aware that this doesn't disable the command!"})},
{key:"enableTicketActionWithReason",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-ticket-action-with-reason",{cliDisplayName:"Enable Ticket Action With Reason",cliDisplayDescription:"Enable/disable buttons to write an additional reason for all ticket actions."})},
{key:"enableDeleteWithoutTranscript",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:enable-delete-without-transcript",{cliDisplayName:"Enable Delete Without Transcript",cliDisplayDescription:"Enable/disable the ability to delete tickets without a transcript."})},
{key:"logs",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:system-logs",{
property:"enabled",
enabledValue:true,
checker:new api.ODCheckerObjectStructure("opendiscord:system-logs",{children:[
{key:"channel",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:log-channel","channel",false,[])},
]})
})},
{key:"logs",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:system-logs",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:system-logs",{children:[
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:logs-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable discord logs in a discord channel."})},
{key:"channel",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:logs-channel","channel",false,[],{cliDisplayName:"Log Channel",cliDisplayDescription:"The ID of the discord channel to log messages to. You can configure the messages somewhere else."})},
],cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage everything related to logs in a discord channel."}),cliDisplayName:"Discord Logs",cliDisplayDescription:"Manage everything related to logs in a discord channel."})},
{key:"limits",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:limits",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:limits",{children:[
{key:"globalMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:limits-global",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1})},
{key:"userMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:limits-user",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1})}
]})})},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:limits-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable global limits."})},
{key:"globalMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:limits-global",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1,cliDisplayName:"Global Maximum",cliDisplayDescription:"The maximum amount of tickets that are able to exist in the server at the same time."})},
{key:"userMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:limits-user",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1,cliDisplayName:"User Maximum",cliDisplayDescription:"The maximum amount of tickets from a specific user that are able to exist in the server at the same time."})}
],cliDisplayName:"Global Limits",cliDisplayDescription:"Manage global limits for ticket creation to reduce the workload on your support team."}),cliDisplayName:"Global Limits",cliDisplayDescription:"Manage global limits for ticket creation to reduce the workload on your support team."})},
{key:"permissions",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:system-permissions",{children:[
{key:"help",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-help","role",false,["admin","everyone","none"])},
{key:"panel",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-panel","role",false,["admin","everyone","none"])},
{key:"ticket",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-ticket","role",false,["admin","everyone","none"])},
{key:"close",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-close","role",false,["admin","everyone","none"])},
{key:"delete",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-delete","role",false,["admin","everyone","none"])},
{key:"reopen",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-reopen","role",false,["admin","everyone","none"])},
{key:"claim",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-claim","role",false,["admin","everyone","none"])},
{key:"unclaim",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unclaim","role",false,["admin","everyone","none"])},
{key:"pin",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-pin","role",false,["admin","everyone","none"])},
{key:"unpin",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unpin","role",false,["admin","everyone","none"])},
{key:"move",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-move","role",false,["admin","everyone","none"])},
{key:"rename",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-rename","role",false,["admin","everyone","none"])},
{key:"add",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-add","role",false,["admin","everyone","none"])},
{key:"remove",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-remove","role",false,["admin","everyone","none"])},
{key:"blacklist",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-blacklist","role",false,["admin","everyone","none"])},
{key:"stats",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-stats","role",false,["admin","everyone","none"])},
{key:"clear",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-clear","role",false,["admin","everyone","none"])},
{key:"autoclose",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autoclose","role",false,["admin","everyone","none"])},
{key:"autodelete",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autodelete","role",false,["admin","everyone","none"])}
]})},
{key:"help",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-help","role",false,["admin","everyone","none"],{cliDisplayName:"Help",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"panel",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-panel","role",false,["admin","everyone","none"],{cliDisplayName:"Panel",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"ticket",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-ticket","role",false,["admin","everyone","none"],{cliDisplayName:"Ticket",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"close",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-close","role",false,["admin","everyone","none"],{cliDisplayName:"Close",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"delete",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-delete","role",false,["admin","everyone","none"],{cliDisplayName:"Delete",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"reopen",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-reopen","role",false,["admin","everyone","none"],{cliDisplayName:"Reopen",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"claim",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-claim","role",false,["admin","everyone","none"],{cliDisplayName:"Claim",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"unclaim",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unclaim","role",false,["admin","everyone","none"],{cliDisplayName:"Unclaim",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"pin",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-pin","role",false,["admin","everyone","none"],{cliDisplayName:"Pin",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"unpin",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-unpin","role",false,["admin","everyone","none"],{cliDisplayName:"Unpin",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"move",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-move","role",false,["admin","everyone","none"],{cliDisplayName:"Move",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"rename",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-rename","role",false,["admin","everyone","none"],{cliDisplayName:"Rename",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"add",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-add","role",false,["admin","everyone","none"],{cliDisplayName:"Add User",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"remove",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-remove","role",false,["admin","everyone","none"],{cliDisplayName:"Remove User",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"blacklist",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-blacklist","role",false,["admin","everyone","none"],{cliDisplayName:"Blacklist",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"stats",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-stats","role",false,["admin","everyone","none"],{cliDisplayName:"Stats",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"clear",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-clear","role",false,["admin","everyone","none"],{cliDisplayName:"Clear Tickets",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"autoclose",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autoclose","role",false,["admin","everyone","none"],{cliDisplayName:"Autoclose",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})},
{key:"autodelete",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:permissions-autodelete","role",false,["admin","everyone","none"],{cliDisplayName:"Autodelete",cliHideDescriptionInParent:true,cliDisplayDescription:"Set the permissions to 'everyone' for everyone, 'admin' for admin only, 'none' to disable or a custom discord role ID."})}
],cliDisplayName:"Permissions",cliDisplayDescription:"Manage all button & command permissions in the bot. (Visit docs for more info)"})},
{key:"messages",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:system-permissions",{children:[
{key:"creation",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-creation")},
{key:"closing",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-closing")},
{key:"deleting",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-deleting")},
{key:"reopening",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-reopening")},
{key:"claiming",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-claiming")},
{key:"pinning",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-pinning")},
{key:"adding",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-adding")},
{key:"removing",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-removing")},
{key:"renaming",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-renaming")},
{key:"moving",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-moving")},
{key:"blacklisting",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-blacklisting")},
{key:"roleAdding",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-role-adding")},
{key:"roleRemoving",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-role-removing")}
]})},
]})}
]})
{key:"creation",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-creation","Ticket Created")},
{key:"closing",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-closing","Ticket Closed")},
{key:"deleting",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-deleting","Ticket Deleted")},
{key:"reopening",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-reopening","Ticket Reopened")},
{key:"claiming",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-claiming","Ticket Claimed")},
{key:"pinning",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-pinning","Ticket Pinned")},
{key:"adding",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-adding","User Added")},
{key:"removing",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-removing","User Removed")},
{key:"renaming",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-renaming","Ticket Renamed")},
{key:"moving",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-moving","Ticket Moved")},
{key:"blacklisting",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-blacklisting","User Blacklisted")},
{key:"roleAdding",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-role-adding","Role Added")},
{key:"roleRemoving",optional:false,priority:0,checker:createMsgStructure("opendiscord:msg-role-removing","Role Removed")}
],cliDisplayName:"Messages",cliDisplayDescription:"Manage all messages & DM's for each action of the bot. (Visit docs for more info)"})},
],cliDisplayName:"System",cliDisplayDescription:"Configure everything related to the ticket system."})}
],cliDisplayName:"General",cliDisplayDescription:"General settings for the bot."})
export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendiscord:options",{allowedTypes:["object"],propertyChecker:new api.ODCheckerObjectSwitchStructure("opendiscord:options",{objects:[
export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendiscord:options",{allowedTypes:["object"],cliDisplayPropertyName:"option",propertyChecker:new api.ODCheckerObjectSwitchStructure("opendiscord:options",{objects:[
//TICKET
{name:"ticket",priority:0,properties:[{key:"type",value:"ticket"}],checker:new api.ODCheckerObjectStructure("opendiscord:ticket",{children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:ticket-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-name",{minLength:2,maxLength:45})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-description",{maxLength:256})},
{name:"Ticket",priority:0,properties:[{key:"type",value:"ticket"}],checker:new api.ODCheckerObjectStructure("opendiscord:ticket",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],cliInitSkipKeys:["readonlyAdmins"],children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:ticket-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this ticket option. Used in panels."})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this ticket option."})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this ticket option."})},
//TICKET BUTTON
{key:"button",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[
{key:"emoji",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true)},
{key:"label",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80})},
{key:"color",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"]})},
{key:"emoji",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})},
{key:"label",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})},
{key:"color",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})},
],custom:(checker,value,locationTrace,locationId,locationDocs) => {
const lt = checker.locationTraceDeref(locationTrace)
//check if emoji & label exists
@@ -318,80 +324,89 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc
checker.createMessage("opendiscord:invalid-button","error",`This button needs to have at least an "emoji" or "label"!`,lt,null,[`"emoji"`,`"label"`],locationId,locationDocs)
return false
}else return true
}})},
},cliDisplayName:"Button",cliDisplayDescription:"Customise the button/dropdown layout of this ticket option."})},
//TICKET ADMINS
{key:"ticketAdmins",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ticket-admins","role",[],{allowDoubles:false})},
{key:"readonlyAdmins",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-readonly-admins","role",[],{allowDoubles:false})},
{key:"allowCreationByBlacklistedUsers",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-allow-blacklisted-users",{})},
{key:"questions",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:option-questions","openticket","question-ids","question-ids-used",{allowDoubles:false,maxLength:5})},
{key:"ticketAdmins",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-ticket-admins","role",[],{allowDoubles:false,cliDisplayPropertyName:"ticket admin role",cliDisplayName:"Ticket Admin Roles",cliDisplayDescription:"A list of role IDs that are only able to interact with this ticket option."},{cliDisplayName:"Ticket Admin Role",cliDisplayDescription:"The discord role ID of a ticket admin."})},
{key:"readonlyAdmins",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:ticket-readonly-admins","role",[],{allowDoubles:false,cliInitDefaultValue:[],cliDisplayPropertyName:"read-only ticket admin role",cliDisplayName:"Readonly Admin Roles",cliDisplayDescription:"A list of role IDs that are only able to read this ticket option."},{cliDisplayName:"Readonly Admin Role",cliDisplayDescription:"The discord role ID of a readonly admin."})},
{key:"allowCreationByBlacklistedUsers",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-allow-blacklisted-users",{cliDisplayName:"Allow Creation By Blacklisted Users",cliDisplayDescription:"When enabled, the blacklist doesn't apply to this ticket option/type and users are still able to create a ticket."})},
{key:"questions",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:option-questions","openticket","question-ids","question-ids-used",{allowDoubles:false,maxLength:5,cliDisplayPropertyName:"question",cliDisplayName:"Questions",cliDisplayDescription:"A list of valid question IDs to ask before creating this ticket."},{cliDisplayName:"Question ID",cliDisplayDescription:"A valid question ID from the questions.json config.",cliAutocompleteFunc:async () => {
const uncheckedRawData = opendiscord.configs.get("opendiscord:questions").data
if (!Array.isArray(uncheckedRawData)) return null
const idList = uncheckedRawData.filter((option) => typeof option == "object" && typeof option["id"] == "string").map((option) => option.id)
return (idList.length > 0) ? idList : null
}})},
//TICKET CHANNEL
{key:"channel",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel",{children:[
{key:"prefix",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-prefix",{maxLength:25,regex:/^[^\s]*$/})},
{key:"suffix",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-suffix",{choices:["user-name","user-id","random-number","random-hex","counter-dynamic","counter-fixed"]})},
{key:"channel",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel",{cliInitSkipKeys:["backupCategory","claimedCategory"],children:[
{key:"prefix",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-prefix",{maxLength:25,regex:/^[^\s]*$/,cliDisplayName:"Prefix",cliDisplayDescription:"The prefix of the name of the ticket channel. (e.g. 'question-')"})},
{key:"suffix",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-suffix",{choices:["user-name","user-id","random-number","random-hex","counter-dynamic","counter-fixed"],cliDisplayName:"Suffix",cliDisplayDescription:"The suffix mode to use. The number/text will be appended after the prefix."})},
{key:"category",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-category","category",true,[])},
{key:"closedCategory",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-closed-category","category",true,[])},
{key:"backupCategory",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-backup-category","category",true,[])},
{key:"claimedCategory",optional:false,priority:0,checker:new api.ODCheckerArrayStructure("opendiscord:ticket-channel-claimed-category",{allowDoubles:false,allowedTypes:["object"],propertyChecker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel-claimed-category",{children:[
{key:"user",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-user","user",false,[])},
{key:"category",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-category","category",false,[])}
]})})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-description",{})},
]})},
{key:"category",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-category","category",true,[],{cliDisplayName:"Category",cliDisplayDescription:"The category the ticket will be created in. Leave empty for no category."})},
{key:"closedCategory",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-closed-category","category",true,[],{cliDisplayName:"Closed Category",cliDisplayDescription:"An additional category where the ticket will be moved to when closed."})},
{key:"backupCategory",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-backup-category","category",true,[],{cliDisplayName:"Backup Category",cliDisplayDescription:"An additional category where the ticket will be created in when the primary category is full (50 channels)."})},
{key:"claimedCategory",optional:false,priority:0,checker:new api.ODCheckerArrayStructure("opendiscord:ticket-channel-claimed-category",{allowDoubles:false,allowedTypes:["object"],cliDisplayPropertyName:"claim category",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:ticket-channel-claimed-category",{children:[
{key:"user",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-user","user",false,[],{cliDisplayName:"User",cliDisplayDescription:"A discord user ID of the ticket claimer."})},
{key:"category",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:ticket-channel-claimed-category","category",false,[],{cliDisplayName:"Category",cliDisplayDescription:"A discord category ID to move the ticket to."})}
],cliDisplayName:"Claimed Category",cliDisplayDescription:"A collection of a user ID and a category ID. The ticket will be moved to the category when this user claims the ticket."}),cliDisplayName:"Claimed Categories",cliDisplayDescription:"Add categories to move the ticket to when a user claims a ticket."})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-channel-description",{cliDisplayName:"Channel Description",cliDisplayDescription:"The description of the ticket channel. Visible in the discord client."})},
],cliDisplayName:"Channel",cliDisplayDescription:"Manage all settings related to the ticket channel and categories."})},
//DM MESSAGE
{key:"dmMessage",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:ticket-dm-message",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-dm-message",{children:[
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-message-text",{maxLength:4096})},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-message-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the DM message on ticket creation."})},
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-message-text",{maxLength:4096,cliDisplayName:"Message Text",cliDisplayDescription:"The raw text of the DM message. Leave empty to only use the embed."})},
{key:"embed",optional:false,priority:0,checker:createTicketEmbedStructure("opendiscord:ticket-message-embed")}
]})})},
],cliDisplayName:"DM Message",cliDisplayDescription:"The DM message is the message that will be sent to the creator of the ticket when he/she creates a ticket."}),cliDisplayName:"DM Message",cliDisplayDescription:"The DM message is the message that will be sent to the creator of the ticket when he/she creates a ticket."})},
//TICKET MESSAGE
{key:"ticketMessage",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:ticket-message",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-message",{children:[
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-message-text",{maxLength:4096})},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-message-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the ticket message on ticket creation. (Recommended)"})},
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-message-text",{maxLength:4096,cliDisplayName:"Message Text",cliDisplayDescription:"The raw text of the ticket message. Leave empty to only use the embed."})},
{key:"embed",optional:false,priority:0,checker:createTicketEmbedStructure("opendiscord:ticket-message-embed")},
{key:"ping",optional:false,priority:0,checker:createTicketPingStructure("opendiscord:ticket-message-ping")}
]})})},
],cliDisplayName:"Ticket Message",cliDisplayDescription:"The Ticket Message is the message that will be sent in the ticket itself. It contains a few buttons for quick access to actions."}),cliDisplayName:"Ticket Message",cliDisplayDescription:"The Ticket Message is the message that will be sent in the ticket itself. It contains a few buttons for quick access to actions."})},
//AUTOCLOSE
{key:"autoclose",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-autoclose",{children:[
{key:"enableInactiveHours",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autoclose-enable-hours",{})},
{key:"inactiveHours",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-autoclose-hours",{zeroAllowed:false,negativeAllowed:false,floatAllowed:true,min:1,max:8544})},
{key:"enableUserLeave",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autoclose-enable-leave",{})},
{key:"disableOnClaim",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autoclose-disable-claim",{})},
]})},
{key:"enableInactiveHours",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autoclose-enable-hours",{cliDisplayName:"Enable Inactive Hours",cliDisplayDescription:"Enable/disable closing the ticket when it has been inactive for the configured amount of time."})},
{key:"inactiveHours",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-autoclose-hours",{zeroAllowed:false,negativeAllowed:false,floatAllowed:true,min:1,max:8544,cliDisplayName:"Inactive Hours",cliDisplayDescription:"The amount of hours the ticket must be inactive."})},
{key:"enableUserLeave",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autoclose-enable-leave",{cliDisplayName:"Enable User Leave",cliDisplayDescription:"Instantly close the ticket when the creator of the ticket leaves the server."})},
{key:"disableOnClaim",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autoclose-disable-claim",{cliDisplayName:"Disable On Claim",cliDisplayDescription:"Disable the autoclose system when the ticket is claimed by any admin."})},
],cliDisplayName:"Autoclose",cliDisplayDescription:"Manage the autoclose system for this ticket type/option."})},
//AUTODELETE
{key:"autodelete",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-autodelete",{children:[
{key:"enableInactiveDays",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autodelete-enable-days",{})},
{key:"inactiveDays",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-autodelete-days",{zeroAllowed:false,negativeAllowed:false,floatAllowed:true,min:1,max:356})},
{key:"enableUserLeave",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autodelete-enable-leave",{})},
{key:"disableOnClaim",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autodelete-disable-claim",{})},
]})},
{key:"enableInactiveDays",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autodelete-enable-days",{cliDisplayName:"Enable Inactive Days",cliDisplayDescription:"Enable/disable deleting the ticket when it has been inactive for the configured amount of time."})},
{key:"inactiveDays",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-autodelete-days",{zeroAllowed:false,negativeAllowed:false,floatAllowed:true,min:1,max:356,cliDisplayName:"Inactive Days",cliDisplayDescription:"The amount of days the ticket must be inactive."})},
{key:"enableUserLeave",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autodelete-enable-leave",{cliDisplayName:"Enable User Leave",cliDisplayDescription:"Instantly delete the ticket when the creator of the ticket leaves the server."})},
{key:"disableOnClaim",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-autodelete-disable-claim",{cliDisplayName:"Disable On Claim",cliDisplayDescription:"Disable the autodelete system when the ticket is claimed by any admin."})},
],cliDisplayName:"Autodelete",cliDisplayDescription:"Manage the autodelete system for this ticket type/option."})},
//COOLDOWN
{key:"cooldown",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:ticket-cooldown",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-cooldown",{children:[
{key:"cooldownMinutes",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-cooldown-minutes",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1,max:512640})},
]})})},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-cooldown-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the cooldown of this ticket option."})},
{key:"cooldownMinutes",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-cooldown-minutes",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1,max:512640,cliDisplayName:"Cooldown Minutes",cliDisplayDescription:"The amount of minutes a user needs to wait before creating another ticket of this type/option."})},
],cliDisplayName:"Cooldown",cliDisplayDescription:"Manage cooldowns for this ticket type/option."}),cliDisplayName:"Cooldown",cliDisplayDescription:"Manage cooldowns for this ticket type/option."})},
//LIMITS
{key:"limits",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:ticket-limits",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-limits",{children:[
{key:"globalMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-limits-global",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1})},
{key:"userMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-limits-user",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1})}
]})})},
]})},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:ticket-limits-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the limits of this ticket option. This is not related to the global ticket limits."})},
{key:"globalMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-limits-global",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1,cliInitDefaultValue:10,cliDisplayName:"Global Maximum",cliDisplayDescription:"The maximum amount of tickets of this type/option that are able to exist in the server at the same time."})},
{key:"userMaximum",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:ticket-limits-user",{zeroAllowed:false,negativeAllowed:false,floatAllowed:false,min:1,cliInitDefaultValue:3,cliDisplayName:"User Maximum",cliDisplayDescription:"The maximum amount of tickets of this type/option from a specific user that are able to exist in the server at the same time."})}
],cliDisplayName:"Option Limits",cliDisplayDescription:"Manage option-based limits for ticket creation to reduce the workload on your support team."}),cliDisplayName:"Limits",cliDisplayDescription:"Manage option-based limits for ticket creation to reduce the workload on your support team."})},
],cliDisplayName:"Ticket Option",cliDisplayDescription:"Manage all ticket-specific settings of this option/type."})},
//WEBSITE
{name:"website",priority:0,properties:[{key:"type",value:"website"}],checker:new api.ODCheckerObjectStructure("opendiscord:options-website",{children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:website-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:website-name",{minLength:2,maxLength:45})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:website-description",{maxLength:256})},
{name:"Website",priority:0,properties:[{key:"type",value:"website"}],checker:new api.ODCheckerObjectStructure("opendiscord:website",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:website-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this website option. Used in panels."})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:website-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this website option."})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:website-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this website option."})},
//WEBSITE BUTTON
{key:"button",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[
{key:"emoji",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true)},
{key:"label",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80})},
{key:"emoji",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})},
{key:"label",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})},
],custom:(checker,value,locationTrace,locationId,locationDocs) => {
const lt = checker.locationTraceDeref(locationTrace)
//check if emoji & label exists
@@ -401,23 +416,23 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc
checker.createMessage("opendiscord:invalid-button","error",`This button needs to have at least an "emoji" or "label"!`,lt,null,[`"emoji"`,`"label"`],locationId,locationDocs)
return false
}else return true
}})},
},cliDisplayName:"Button",cliDisplayDescription:"Customise the button layout of this website option."})},
//WEBSITE URL
{key:"url",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:website-url",false,{allowHttp:false})},
]})},
{key:"url",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:website-url",false,{allowHttp:false},{cliDisplayName:"URL",cliDisplayDescription:"The URL this button will link to."})},
],cliDisplayName:"Website Option",cliDisplayDescription:"Manage all settings of this website/url option."})},
//REACTION ROLES
{name:"role",priority:0,properties:[{key:"type",value:"role"}],checker:new api.ODCheckerObjectStructure("opendiscord:options-role",{children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:role-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:role-name",{minLength:2,maxLength:45})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:role-description",{maxLength:256})},
{name:"Reaction Role",priority:0,properties:[{key:"type",value:"role"}],checker:new api.ODCheckerObjectStructure("opendiscord:role",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:role-id","openticket","option-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this role option. Used in panels."})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:role-name",{minLength:2,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this role option."})},
{key:"description",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:role-description",{maxLength:256,cliDisplayName:"Description",cliDisplayDescription:"The description of this role option."})},
//ROLE BUTTON
{key:"button",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:ticket-button",{children:[
{key:"emoji",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true)},
{key:"label",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80})},
{key:"color",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"]})},
{key:"emoji",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_EmojiString("opendiscord:ticket-button-emoji",0,1,true,{cliDisplayName:"Emoji",cliDisplayDescription:"The emoji of the button. At least 1 of 2 (emoji/label) must be provided."})},
{key:"label",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-label",{maxLength:80,cliDisplayName:"Label",cliDisplayDescription:"The label of the button. At least 1 of 2 (emoji/label) must be provided."})},
{key:"color",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:ticket-button-color",{choices:["gray","red","green","blue"],cliDisplayName:"Color",cliDisplayDescription:"The color of the button. Does not apply when using a dropdown panel."})},
],custom:(checker,value,locationTrace,locationId,locationDocs) => {
const lt = checker.locationTraceDeref(locationTrace)
//check if emoji & label exists
@@ -427,117 +442,129 @@ export const defaultOptionsStructure = new api.ODCheckerArrayStructure("opendisc
checker.createMessage("opendiscord:invalid-button","error",`This button needs to have at least an "emoji" or "label"!`,lt,null,[`"emoji"`,`"label"`],locationId,locationDocs)
return false
}else return true
}})},
},cliDisplayName:"Button",cliDisplayDescription:"Customise the button layout of this reaction role option."})},
//ROLE SETTINGS
{key:"roles",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:role-roles","role",[],{allowDoubles:false,minLength:1})},
{key:"mode",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:role-mode",{choices:["add","remove","add&remove"]})},
{key:"removeRolesOnAdd",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:role-remove-roles","role",[],{allowDoubles:false})},
{key:"addOnMemberJoin",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:role-add-on-join",{})},
]})},
]})})
{key:"roles",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:role-roles","role",[],{allowDoubles:false,minLength:1,cliDisplayPropertyName:"role",cliDisplayName:"Roles",cliDisplayDescription:"A list of roles to add/remove when clicking on the button."},{cliDisplayName:"Role",cliDisplayDescription:"The discord role ID you want to add/remove."})},
{key:"mode",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:role-mode",{choices:["add","remove","add&remove"],cliDisplayName:"Mode",cliDisplayDescription:"Decide how the button will work: add-only, remove-only or add & remove."})},
{key:"removeRolesOnAdd",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordIdArray("opendiscord:role-remove-roles","role",[],{allowDoubles:false,cliDisplayPropertyName:"role",cliDisplayName:"Remove Roles On Add",cliDisplayDescription:"An additional list of roles to remove when the roles of this option are added. (Can be used to select between roles)"},{cliDisplayName:"Remove Role",cliDisplayDescription:"The discord role ID you want to remove when other roles are added."})},
{key:"addOnMemberJoin",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:role-add-on-join",{cliDisplayName:"Add On Member Join",cliDisplayDescription:"Automatically add these roles to a user when joining the server."})},
],cliDisplayName:"Reaction Role Option",cliDisplayDescription:"Manage all settings of this reaction role option."})},
],cliDisplayName:"Option",cliDisplayDescription:"Manage an option of one of the 3 types: ticket, website, role."}),cliDisplayName:"Options",cliDisplayDescription:"A list of all options in the bot. Here you can add, modify & remove ticket types, website buttons & reaction roles!"})
export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendiscord:panels",{allowedTypes:["object"],propertyChecker:new api.ODCheckerObjectStructure("opendiscord:panels",{children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:panel-id","openticket","panel-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-name",{minLength:3,maxLength:50})},
{key:"dropdown",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-dropdown",{})},
{key:"options",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:panel-options","openticket","option-ids","option-ids-used",{allowDoubles:false,maxLength:25})},
export const defaultPanelsStructure = new api.ODCheckerArrayStructure("opendiscord:panels",{allowedTypes:["object"],cliDisplayPropertyName:"panel",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:panels",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","dropdown"],children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:panel-id","openticket","panel-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this panel. Used in the /panel command."})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-name",{minLength:3,maxLength:50,cliDisplayName:"Name",cliDisplayDescription:"The name of this panel."})},
{key:"dropdown",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-dropdown",{cliDisplayName:"Dropdown",cliDisplayDescription:"Decide whether to use buttons or a dropdown in the panel. Dropdowns only support options of the 'ticket' type!"})},
{key:"options",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueIdArray("opendiscord:panel-options","openticket","option-ids","option-ids-used",{allowDoubles:false,maxLength:25,cliDisplayPropertyName:"option",cliDisplayName:"Options",cliDisplayDescription:"A list of valid option IDs to show in this panel."},{cliDisplayName:"Option ID",cliDisplayDescription:"A valid option ID from the options.json config.",cliAutocompleteFunc:async () => {
const uncheckedRawData = opendiscord.configs.get("opendiscord:options").data
if (!Array.isArray(uncheckedRawData)) return null
const idList = uncheckedRawData.filter((option) => typeof option == "object" && typeof option["id"] == "string").map((option) => option.id)
return (idList.length > 0) ? idList : null
}})},
//EMBED & TEXT
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-text",{maxLength:4096})},
{key:"text",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-text",{maxLength:4096,cliDisplayName:"Panel Text",cliDisplayDescription:"The raw text of the panel message. Leave empty to use the embed."})},
{key:"embed",optional:false,priority:0,checker:createPanelEmbedStructure("opendiscord:panel-embed")},
//SETTINGS
{key:"settings",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:panel-settings",{children:[
{key:"dropdownPlaceholder",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-placeholder",{maxLength:100})},
{key:"enableMaxTicketsWarningInText",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-text",{})},
{key:"enableMaxTicketsWarningInEmbed",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-embed",{})},
{key:"settings",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:panel-settings",{cliInitSkipKeys:["dropdownPlaceholder","describeOptionsCustomTitle"],children:[
{key:"dropdownPlaceholder",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-placeholder",{maxLength:100,cliInitDefaultValue:"Create a ticket!",cliDisplayName:"Dropdown Placeholder",cliDisplayDescription:"Configure the text displayed in the dropdown when nothing is selected."})},
{key:"enableMaxTicketsWarningInText",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-text",{cliDisplayName:"Enable Max Tickets Warning (Text)",cliDisplayDescription:"Enable/disable the warning which shows how many tickets you can create in the text contents of the panel."})},
{key:"enableMaxTicketsWarningInEmbed",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-maxtickets-embed",{cliDisplayName:"Enable Max Tickets Warning (Embed)",cliDisplayDescription:"Enable/disable the warning which shows how many tickets you can create in the embed of the panel."})},
{key:"describeOptionsLayout",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-describe-layout",{choices:["simple","normal","detailed"]})},
{key:"describeOptionsCustomTitle",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-describe-title",{maxLength:512})},
{key:"describeOptionsInText",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-describe-text",{})},
{key:"describeOptionsInEmbedFields",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-describe-fields",{})},
{key:"describeOptionsInEmbedDescription",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-describe-embed",{})},
]})},
]})})
{key:"describeOptionsLayout",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-describe-layout",{choices:["simple","normal","detailed"],cliDisplayName:"Describe Options Layout",cliDisplayDescription:"The layout to use in the auto-generated option descriptions (simple, normal, detailed)."})},
{key:"describeOptionsCustomTitle",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:panel-settings-describe-title",{maxLength:512,cliDisplayName:"Describe Options Title",cliDisplayDescription:"Customise the title to use in the auto-generated option descriptions."})},
{key:"describeOptionsInText",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-describe-text",{cliDisplayName:"Describe Options In Text",cliDisplayDescription:"Enable/disable showing the auto-generated option descriptions in the raw text contents of the panel."})},
{key:"describeOptionsInEmbedFields",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-describe-fields",{cliDisplayName:"Describe Options In Embed Fields",cliDisplayDescription:"Enable/disable showing the auto-generated option descriptions in the embed fields of the panel."})},
{key:"describeOptionsInEmbedDescription",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:panel-settings-describe-embed",{cliDisplayName:"Describe Options In Embed Description",cliDisplayDescription:"Enable/disable showing the auto-generated option descriptions in the embed description of the panel."})},
],cliDisplayName:"Settings",cliDisplayDescription:"Manage additional settings & customisability for this panel."})},
],cliDisplayName:"Panel",cliDisplayDescription:"Manage, customise and configure a panel to your preference."}),cliDisplayName:"Panels",cliDisplayDescription:"A list of all panels in the bot. Here you can add, modify & remove existing panels or customise them to your preference."})
export const defaultQuestionsStructure = new api.ODCheckerArrayStructure("opendiscord:questions",{allowedTypes:["object"],propertyChecker:new api.ODCheckerObjectStructure("opendiscord:questions",{children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45})},
{key:"type",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:question-type",{choices:["short","paragraph"]})},
export const defaultQuestionsStructure = new api.ODCheckerArrayStructure("opendiscord:questions",{allowedTypes:["object"],cliDisplayPropertyName:"question",propertyChecker:new api.ODCheckerObjectStructure("opendiscord:questions",{cliDisplayKeyInParentArray:"name",cliDisplayAdditionalKeysInParentArray:["id","type"],children:[
{key:"id",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UniqueId("opendiscord:question-id","openticket","question-ids",{regex:/^[A-Za-z0-9-éèçàêâôûî]+$/,minLength:3,maxLength:40,cliDisplayName:"Id",cliDisplayDescription:"The id of this question. Used in ticket options."})},
{key:"name",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:question-name",{minLength:3,maxLength:45,cliDisplayName:"Name",cliDisplayDescription:"The name of this question."})},
{key:"type",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:question-type",{choices:["short","paragraph"],cliDisplayName:"Type",cliDisplayDescription:"The type of this question (short/paragraph)."})},
{key:"required",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{})},
{key:"placeholder",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100})},
{key:"required",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:question-required",{cliDisplayName:"Required",cliDisplayDescription:"Is this question required? If not, it can be left empty."})},
{key:"placeholder",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:question-placeholder",{maxLength:100,cliDisplayName:"Placeholder",cliDisplayDescription:"The placeholder to show in the field when nothing has been written yet."})},
{key:"length",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:question-length",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:question-length",{children:[
{key:"min",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false})},
{key:"max",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false})},
]})})},
]})})
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:question-length-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable length validation for this question."})},
{key:"min",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:question-length-min",{min:0,max:1024,negativeAllowed:false,floatAllowed:false,cliDisplayName:"Min Length",cliDisplayDescription:"The minimum amount of characters required."})},
{key:"max",optional:false,priority:0,checker:new api.ODCheckerNumberStructure("opendiscord:question-length-max",{min:1,max:1024,negativeAllowed:false,floatAllowed:false,cliInitDefaultValue:100,cliDisplayName:"Max Length",cliDisplayDescription:"The maximum amount of characters allowed."})},
],cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."}),cliDisplayName:"Length Validation",cliDisplayDescription:"Add length validation to the question. This way, the contents must be at least/most ... characters."})},
],cliDisplayName:"Question",cliDisplayDescription:"Manage, customise and configure a question to your preference."}),cliDisplayName:"Questions",cliDisplayDescription:"A list of all questions in the bot. Here you can add, modify & remove existing questions or customise them to your preference."})
export const defaultTranscriptsStructure = new api.ODCheckerObjectStructure("opendiscord:transcripts",{children:[
//GENERAL
{key:"general",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:transcripts-general",{property:"enabled",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-general",{children:[
{key:"enableChannel",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-channel",{})},
{key:"enableCreatorDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-creator-dm",{})},
{key:"enableParticipantDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-participant-dm",{})},
{key:"enableActiveAdminDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-active-admin-dm",{})},
{key:"enableEveryAdminDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-every-admin-dm",{})},
{key:"enabled",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable the transcript system."})},
{key:"channel",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:transcripts-channel","channel",true,[])},
{key:"mode",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-mode",{choices:["html","text"]})},
]})})},
{key:"enableChannel",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-channel",{cliDisplayName:"Enable Channel",cliDisplayDescription:"Send the transcript to a specific channel in your server (configurable in 'channel' property)."})},
{key:"enableCreatorDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-creator-dm",{cliDisplayName:"Enable Creator DM",cliDisplayDescription:"Send the transcript in DM to the creator of the ticket."})},
{key:"enableParticipantDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-participant-dm",{cliDisplayName:"Enable Participant DM",cliDisplayDescription:"Send the transcript in DM to all non-admin participants of the ticket."})},
{key:"enableActiveAdminDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-active-admin-dm",{cliDisplayName:"Enable Active Admin DM",cliDisplayDescription:"Send the transcript in DM to all admins that actively wrote in the ticket."})},
{key:"enableEveryAdminDM",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-enable-every-admin-dm",{cliDisplayName:"Enable Every Admin DM",cliDisplayDescription:"Send the transcript in DM to all admins assigned to the ticket."})},
{key:"channel",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_DiscordId("opendiscord:transcripts-channel","channel",true,[],{cliDisplayName:"Channel",cliDisplayDescription:"The discord channel ID to send the transcript to."})},
{key:"mode",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-mode",{choices:["html","text"],cliDisplayName:"Transcript Mode",cliDisplayDescription:"The transcript type to use: 'text' or 'html'."})},
],cliDisplayName:"General",cliDisplayDescription:"General settings for the transcripts."}),cliDisplayName:"General",cliDisplayDescription:"General settings for the transcripts."})},
//EMBED SETTINGS
{key:"embedSettings",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-embed-settings",{children:[
{key:"customColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-embed-color",false,true)},
{key:"listAllParticipants",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-embed-list-participants",{})},
{key:"includeTicketStats",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-embed-include-ticket-stats",{})},
]})},
{key:"customColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-embed-color",false,true,{cliDisplayName:"Custom Color",cliDisplayDescription:"Use a custom color in the embed. When empty, the default bot color will be used."})},
{key:"listAllParticipants",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-embed-list-participants",{cliDisplayName:"List Participants",cliDisplayDescription:"List all participants of the ticket in the embed."})},
{key:"includeTicketStats",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-embed-include-ticket-stats",{cliDisplayName:"Include Ticket Stats",cliDisplayDescription:"Include some stats from the ticket in the embed."})},
],cliDisplayName:"Embed Settings",cliDisplayDescription:"Settings and customisability related to the embed which contains the transcript."})},
//TEXT STYLE
{key:"textTranscriptStyle",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-text",{children:[
{key:"layout",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-text-layout",{choices:["simple","normal","detailed"]})},
{key:"includeStats",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-stats",{})},
{key:"includeIds",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-ids",{})},
{key:"includeEmbeds",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-embeds",{})},
{key:"includeFiles",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-files",{})},
{key:"includeBotMessages",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-bots",{})},
{key:"layout",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-text-layout",{choices:["simple","normal","detailed"],cliDisplayName:"Layout",cliDisplayDescription:"The layout to use in the text-transcripts (simple, normal, detailed)."})},
{key:"includeStats",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-stats",{cliDisplayName:"Include Stats",cliDisplayDescription:"Include statistics in the transcript?"})},
{key:"includeIds",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-ids",{cliDisplayName:"Include Ids",cliDisplayDescription:"Include role, channel & user ID's in the transcript?"})},
{key:"includeEmbeds",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-embeds",{cliDisplayName:"Include Embeds",cliDisplayDescription:"Include message embeds in the transcript?"})},
{key:"includeFiles",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-files",{cliDisplayName:"Include Files",cliDisplayDescription:"Include files & attachments in the transcript?"})},
{key:"includeBotMessages",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-text-include-bots",{cliDisplayName:"Include Bots",cliDisplayDescription:"Include messages sent by bots/apps?"})},
{key:"fileMode",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-text-file-mode",{choices:["custom","channel-name","channel-id","user-name","user-id"]})},
{key:"customFileName",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-file-name",{maxLength:512,regex:/^[^\.#%&{}\\<>*?/!'":@`|=]*$/})},
]})},
{key:"fileMode",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-text-file-mode",{choices:["custom","channel-name","channel-id","user-name","user-id"],cliDisplayName:"File Mode",cliDisplayDescription:"Select the mode the transcript will be named: custom, channel-name, user-name, user-id."})},
{key:"customFileName",optional:false,priority:0,checker:new api.ODCheckerStringStructure("opendiscord:transcripts-file-name",{maxLength:512,regex:/^[^\.#%&{}\\<>*?/!'":@`|=]*$/,cliDisplayName:"Custom File Name",cliDisplayDescription:"Use this as transcript name when the mode is set to 'custom'."})},
],cliDisplayName:"Text Transcript Style",cliDisplayDescription:"Configure the 'Text Transcripts' from Open Ticket."})},
//HTML STYLE
{key:"htmlTranscriptStyle",optional:false,priority:0,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-html",{children:[
//HTML BACKGROUND
{key:"background",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:transcripts-html-background",{property:"enableCustomBackground",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-html-background",{children:[
{key:"backgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-background-color",false,true)},
{key:"backgroundImage",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:transcripts-html-background-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]})},
]})})},
{key:"enableCustomBackground",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-html-background-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable background customisation in the HTML Transcripts."})},
{key:"backgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-background-color",false,true,{cliDisplayName:"Background Color",cliDisplayDescription:"The hex-color of the background."})},
{key:"backgroundImage",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:transcripts-html-background-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp",".gif"]},{cliDisplayName:"Background Image",cliDisplayDescription:"A URL to an image to use in the background. This will overwrite the background color."})},
],cliDisplayName:"Background Style",cliDisplayDescription:"Customise the background of the HTML Transcripts."}),cliDisplayName:"Background Style",cliDisplayDescription:"Customise the background of the HTML Transcripts."})},
//HTML HEADER
{key:"header",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:transcripts-html-header",{property:"enableCustomHeader",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-html-header",{children:[
{key:"backgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-header-bgcolor",false,false)},
{key:"decoColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-header-decocolor",false,false)},
{key:"textColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-header-textcolor",false,false)},
]})})},
{key:"enableCustomHeader",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-html-header-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable header customisation in the HTML Transcripts."})},
{key:"backgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-header-bgcolor",false,false,{cliDisplayName:"Background Color",cliDisplayDescription:"The hex-color of the header background."})},
{key:"decoColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-header-decocolor",false,false,{cliDisplayName:"Decoration Color",cliDisplayDescription:"The hex-color of the header decoration (e.g. horizontal line)."})},
{key:"textColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-header-textcolor",false,false,{cliDisplayName:"Text Color",cliDisplayDescription:"The hex-color of the header text."})},
],cliDisplayName:"Header Style",cliDisplayDescription:"Customise the header of the HTML Transcripts."}),cliDisplayName:"Header Style",cliDisplayDescription:"Customise the header of the HTML Transcripts."})},
//HTML STATS
{key:"stats",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:transcripts-html-stats",{property:"enableCustomStats",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-html-stats",{children:[
{key:"backgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-bgcolor",false,false)},
{key:"keyTextColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-keycolor",false,false)},
{key:"valueTextColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-valuecolor",false,false)},
{key:"hideBackgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-hidebgcolor",false,false)},
{key:"hideTextColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-hidecolor",false,false)},
]})})},
{key:"enableCustomStats",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-html-stats-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable stats customisation in the HTML Transcripts."})},
{key:"backgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-bgcolor",false,false,{cliDisplayName:"Background Color",cliDisplayDescription:"The hex-color of the stats background."})},
{key:"keyTextColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-keycolor",false,false,{cliDisplayName:"Key Text Color",cliDisplayDescription:"The hex-color of the stats key text."})},
{key:"valueTextColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-valuecolor",false,false,{cliDisplayName:"Value Text Color",cliDisplayDescription:"The hex-color of the stats value text."})},
{key:"hideBackgroundColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-hidebgcolor",false,false,{cliDisplayName:"Hide Background Color",cliDisplayDescription:"The hex-color of the stats hide button background."})},
{key:"hideTextColor",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_HexColor("opendiscord:transcripts-html-stats-hidecolor",false,false,{cliDisplayName:"Hide Text Color",cliDisplayDescription:"The hex-color of the stats hide button text."})},
],cliDisplayName:"Stats Style",cliDisplayDescription:"Customise the stats of the HTML Transcripts."}),cliDisplayName:"Stats Style",cliDisplayDescription:"Customise the stats of the HTML Transcripts."})},
//HTML FAVICON
{key:"favicon",optional:false,priority:0,checker:new api.ODCheckerEnabledObjectStructure("opendiscord:transcripts-html-favicon",{property:"enableCustomFavicon",enabledValue:true,checker:new api.ODCheckerObjectStructure("opendiscord:transcripts-html-favicon",{children:[
{key:"imageUrl",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:transcripts-html-favicon-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp"]})},
]})})},
]})},
]})
{key:"enableCustomFavicon",optional:false,priority:0,checker:new api.ODCheckerBooleanStructure("opendiscord:transcripts-html-favicon-enabled",{cliDisplayName:"Enabled",cliDisplayDescription:"Enable/disable favicon customisation in the HTML Transcripts."})},
{key:"imageUrl",optional:false,priority:0,checker:new api.ODCheckerCustomStructure_UrlString("opendiscord:transcripts-html-favicon-image",true,{allowHttp:false,allowedExtensions:[".png",".jpg",".jpeg",".webp"]},{cliDisplayName:"LOREMIPSUM",cliDisplayDescription:"IPSUMLOREM"})},
],cliDisplayName:"Favicon Style",cliDisplayDescription:"Customise the favicon of the HTML Transcripts."}),cliDisplayName:"Favicon Style",cliDisplayDescription:"Customise the favicon of the HTML Transcripts."})},
],cliDisplayName:"Html Transcript Style",cliDisplayDescription:"Configure the 'Html Transcripts' from Open Ticket."})},
],cliDisplayName:"Transcripts",cliDisplayDescription:"All settings related to transcripts."})
export const defaultUnusedOptionsFunction = (manager:api.ODCheckerManager, functions:api.ODCheckerFunctionManager): api.ODCheckerResult => {
const optionList: string[] = manager.storage.get("openticket","option-ids")
+367 -5
View File
@@ -1,12 +1,374 @@
import {opendiscord, api, utilities} from "../../index"
import * as fjs from "formatted-json-stringify"
export const loadAllConfigs = async () => {
const devconfigFlag = opendiscord.flags.get("opendiscord:dev-config")
const isDevconfig = devconfigFlag ? devconfigFlag.value : false
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:general","general.json",(isDevconfig) ? "./devconfig/" : "./config/"))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:options","options.json",(isDevconfig) ? "./devconfig/" : "./config/"))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:panels","panels.json",(isDevconfig) ? "./devconfig/" : "./config/"))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:questions","questions.json",(isDevconfig) ? "./devconfig/" : "./config/"))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:transcripts","transcripts.json",(isDevconfig) ? "./devconfig/" : "./config/"))
/** How to add more config variables?
* - Add the variable to the config files in `./config/` & `./devconfig/`.
* - Add the variable to the config in ./src/core/api/defaults/config.ts (interfaces + types)
* - Add the variable to the config checker in ./src/data/framework/checkerLoader.ts
* - Make sure it's compatible with the Interactive Setup CLI.
* - Make sure the Migration Manager automatically adds the variable when missing.
* - Add the variable to the formatters in this file.
* - Update the documentation reference.
*/
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:general","general.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultGeneralFormatter))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:questions","questions.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultQuestionsFormatter))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:options","options.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultOptionsFormatter))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:panels","panels.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultPanelsFormatter))
opendiscord.configs.add(new api.ODJsonConfig("opendiscord:transcripts","transcripts.json",(isDevconfig) ? "./devconfig/" : "./config/",defaultTranscriptsFormatter))
}
//FORMATTERS
export const defaultGeneralFormatter = new fjs.ObjectFormatter(null,true,[
new fjs.ObjectFormatter("_INFO",true,[
new fjs.PropertyFormatter("support"),
new fjs.PropertyFormatter("discord"),
new fjs.PropertyFormatter("version"),
]),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("token"),
new fjs.PropertyFormatter("tokenFromENV"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("mainColor"),
new fjs.PropertyFormatter("language"),
new fjs.PropertyFormatter("prefix"),
new fjs.PropertyFormatter("serverId"),
new fjs.ArrayFormatter("globalAdmins",false,new fjs.PropertyFormatter(null)),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("slashCommands"),
new fjs.PropertyFormatter("textCommands"),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("status",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("type"),
new fjs.PropertyFormatter("text"),
new fjs.PropertyFormatter("status"),
]),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("system",true,[
new fjs.PropertyFormatter("removeParticipantsOnClose"),
new fjs.PropertyFormatter("replyOnTicketCreation"),
new fjs.PropertyFormatter("replyOnReactionRole"),
new fjs.PropertyFormatter("useTranslatedConfigChecker"),
new fjs.PropertyFormatter("preferSlashOverText"),
new fjs.PropertyFormatter("sendErrorOnUnknownCommand"),
new fjs.PropertyFormatter("questionFieldsInCodeBlock"),
new fjs.PropertyFormatter("disableVerifyBars"),
new fjs.PropertyFormatter("useRedErrorEmbeds"),
new fjs.PropertyFormatter("emojiStyle"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("enableTicketClaimButtons"),
new fjs.PropertyFormatter("enableTicketCloseButtons"),
new fjs.PropertyFormatter("enableTicketPinButtons"),
new fjs.PropertyFormatter("enableTicketDeleteButtons"),
new fjs.PropertyFormatter("enableTicketActionWithReason"),
new fjs.PropertyFormatter("enableDeleteWithoutTranscript"),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("logs",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("channel"),
]),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("limits",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("globalMaximum"),
new fjs.PropertyFormatter("userMaximum"),
]),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("permissions",true,[
new fjs.PropertyFormatter("help"),
new fjs.PropertyFormatter("panel"),
new fjs.PropertyFormatter("ticket"),
new fjs.PropertyFormatter("close"),
new fjs.PropertyFormatter("delete"),
new fjs.PropertyFormatter("reopen"),
new fjs.PropertyFormatter("claim"),
new fjs.PropertyFormatter("unclaim"),
new fjs.PropertyFormatter("pin"),
new fjs.PropertyFormatter("unpin"),
new fjs.PropertyFormatter("move"),
new fjs.PropertyFormatter("rename"),
new fjs.PropertyFormatter("add"),
new fjs.PropertyFormatter("remove"),
new fjs.PropertyFormatter("blacklist"),
new fjs.PropertyFormatter("stats"),
new fjs.PropertyFormatter("clear"),
new fjs.PropertyFormatter("autoclose"),
new fjs.PropertyFormatter("autodelete"),
]),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("messages",true,[
new fjs.DefaultFormatter("creation",false),
new fjs.DefaultFormatter("closing",false),
new fjs.DefaultFormatter("deleting",false),
new fjs.DefaultFormatter("reopening",false),
new fjs.DefaultFormatter("claiming",false),
new fjs.DefaultFormatter("pinning",false),
new fjs.DefaultFormatter("adding",false),
new fjs.DefaultFormatter("removing",false),
new fjs.DefaultFormatter("renaming",false),
new fjs.DefaultFormatter("moving",false),
new fjs.DefaultFormatter("blacklisting",false),
new fjs.DefaultFormatter("roleAdding",false),
new fjs.DefaultFormatter("roleRemoving",false),
]),
]),
])
export const defaultQuestionsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[
{key:"type",value:"short",formatter:new fjs.ObjectFormatter(null,true,[
new fjs.PropertyFormatter("id"),
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("type"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("required"),
new fjs.PropertyFormatter("placeholder"),
new fjs.ObjectFormatter("length",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("min"),
new fjs.PropertyFormatter("max"),
]),
])},
{key:"type",value:"paragraph",formatter:new fjs.ObjectFormatter(null,true,[
new fjs.PropertyFormatter("id"),
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("type"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("required"),
new fjs.PropertyFormatter("placeholder"),
new fjs.ObjectFormatter("length",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("min"),
new fjs.PropertyFormatter("max"),
]),
])}
]))
export const defaultOptionsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectSwitchFormatter(null,[
{key:"type",value:"ticket",formatter:new fjs.ObjectFormatter(null,true,[
new fjs.PropertyFormatter("id"),
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("description"),
new fjs.PropertyFormatter("type"),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("button",true,[
new fjs.PropertyFormatter("emoji"),
new fjs.PropertyFormatter("label"),
new fjs.PropertyFormatter("color"),
]),
new fjs.TextFormatter(""),
new fjs.ArrayFormatter("ticketAdmins",false,new fjs.PropertyFormatter(null)),
new fjs.ArrayFormatter("readonlyAdmins",false,new fjs.PropertyFormatter(null)),
new fjs.PropertyFormatter("allowCreationByBlacklistedUsers"),
new fjs.ArrayFormatter("questions",false,new fjs.PropertyFormatter(null)),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("channel",true,[
new fjs.PropertyFormatter("prefix"),
new fjs.PropertyFormatter("suffix"),
new fjs.PropertyFormatter("category"),
new fjs.PropertyFormatter("backupCategory"),
new fjs.PropertyFormatter("closedCategory"),
new fjs.ArrayFormatter("claimedCategory",true,new fjs.ObjectFormatter(null,false,[
new fjs.PropertyFormatter("user"),
new fjs.PropertyFormatter("category"),
])),
new fjs.PropertyFormatter("description"),
]),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("dmMessage",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("text"),
new fjs.ObjectFormatter("embed",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("title"),
new fjs.PropertyFormatter("description"),
new fjs.PropertyFormatter("customColor"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("image"),
new fjs.PropertyFormatter("thumbnail"),
new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("value"),
new fjs.PropertyFormatter("inline"),
])),
new fjs.PropertyFormatter("timestamp"),
]),
]),
new fjs.ObjectFormatter("ticketMessage",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("text"),
new fjs.ObjectFormatter("embed",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("title"),
new fjs.PropertyFormatter("description"),
new fjs.PropertyFormatter("customColor"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("image"),
new fjs.PropertyFormatter("thumbnail"),
new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("value"),
new fjs.PropertyFormatter("inline"),
])),
new fjs.PropertyFormatter("timestamp"),
]),
new fjs.ObjectFormatter("ping",true,[
new fjs.PropertyFormatter("@here"),
new fjs.PropertyFormatter("@everyone"),
new fjs.ArrayFormatter("custom",true,new fjs.PropertyFormatter(null)),
]),
]),
new fjs.ObjectFormatter("autoclose",true,[
new fjs.PropertyFormatter("enableInactiveHours"),
new fjs.PropertyFormatter("inactiveHours"),
new fjs.PropertyFormatter("enableUserLeave"),
new fjs.PropertyFormatter("disableOnClaim"),
]),
new fjs.ObjectFormatter("autodelete",true,[
new fjs.PropertyFormatter("enableInactiveDays"),
new fjs.PropertyFormatter("inactiveDays"),
new fjs.PropertyFormatter("enableUserLeave"),
new fjs.PropertyFormatter("disableOnClaim"),
]),
new fjs.ObjectFormatter("cooldown",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("cooldownMinutes"),
]),
new fjs.ObjectFormatter("limits",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("globalMaximum"),
new fjs.PropertyFormatter("userMaximum"),
]),
])},
{key:"type",value:"website",formatter:new fjs.ObjectFormatter(null,true,[
new fjs.PropertyFormatter("id"),
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("description"),
new fjs.PropertyFormatter("type"),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("button",true,[
new fjs.PropertyFormatter("emoji"),
new fjs.PropertyFormatter("label"),
]),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("url"),
])},
{key:"type",value:"role",formatter:new fjs.ObjectFormatter(null,true,[
new fjs.PropertyFormatter("id"),
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("description"),
new fjs.PropertyFormatter("type"),
new fjs.TextFormatter(""),
new fjs.ObjectFormatter("button",true,[
new fjs.PropertyFormatter("emoji"),
new fjs.PropertyFormatter("label"),
new fjs.PropertyFormatter("color"),
]),
new fjs.TextFormatter(""),
new fjs.ArrayFormatter("roles",false,new fjs.PropertyFormatter(null)),
new fjs.PropertyFormatter("mode"),
new fjs.ArrayFormatter("removeRolesOnAdd",false,new fjs.PropertyFormatter(null)),
new fjs.PropertyFormatter("addOnMemberJoin"),
])}
]))
export const defaultPanelsFormatter = new fjs.ArrayFormatter(null,true,new fjs.ObjectFormatter(null,true,[
new fjs.PropertyFormatter("id"),
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("dropdown"),
new fjs.ArrayFormatter("options",false,new fjs.PropertyFormatter(null)),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("text"),
new fjs.ObjectFormatter("embed",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.PropertyFormatter("title"),
new fjs.PropertyFormatter("description"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("customColor"),
new fjs.PropertyFormatter("url"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("image"),
new fjs.PropertyFormatter("thumbnail"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("footer"),
new fjs.ArrayFormatter("fields",true,new fjs.ObjectFormatter(null,false,[
new fjs.PropertyFormatter("name"),
new fjs.PropertyFormatter("value"),
new fjs.PropertyFormatter("inline"),
])),
new fjs.PropertyFormatter("timestamp"),
]),
new fjs.ObjectFormatter("settings",true,[
new fjs.PropertyFormatter("dropdownPlaceholder"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("enableMaxTicketsWarningInText"),
new fjs.PropertyFormatter("enableMaxTicketsWarningInEmbed"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("describeOptionsLayout"),
new fjs.PropertyFormatter("describeOptionsCustomTitle"),
new fjs.PropertyFormatter("describeOptionsInText"),
new fjs.PropertyFormatter("describeOptionsInEmbedFields"),
new fjs.PropertyFormatter("describeOptionsInEmbedDescription"),
]),
]))
export const defaultTranscriptsFormatter = new fjs.ObjectFormatter(null,true,[
new fjs.ObjectFormatter("general",true,[
new fjs.PropertyFormatter("enabled"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("enableChannel"),
new fjs.PropertyFormatter("enableCreatorDM"),
new fjs.PropertyFormatter("enableParticipantDM"),
new fjs.PropertyFormatter("enableActiveAdminDM"),
new fjs.PropertyFormatter("enableEveryAdminDM"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("channel"),
new fjs.PropertyFormatter("mode"),
]),
new fjs.ObjectFormatter("embedSettings",true,[
new fjs.PropertyFormatter("customColor"),
new fjs.PropertyFormatter("listAllParticipants"),
new fjs.PropertyFormatter("includeTicketStats"),
]),
new fjs.ObjectFormatter("textTranscriptStyle",true,[
new fjs.PropertyFormatter("layout"),
new fjs.PropertyFormatter("includeStats"),
new fjs.PropertyFormatter("includeIds"),
new fjs.PropertyFormatter("includeEmbeds"),
new fjs.PropertyFormatter("includeFiles"),
new fjs.PropertyFormatter("includeBotMessages"),
new fjs.TextFormatter(""),
new fjs.PropertyFormatter("fileMode"),
new fjs.PropertyFormatter("customFileName"),
]),
new fjs.ObjectFormatter("htmlTranscriptStyle",true,[
new fjs.ObjectFormatter("background",true,[
new fjs.PropertyFormatter("enableCustomBackground"),
new fjs.PropertyFormatter("backgroundColor"),
new fjs.PropertyFormatter("backgroundImage"),
]),
new fjs.ObjectFormatter("header",true,[
new fjs.PropertyFormatter("enableCustomHeader"),
new fjs.PropertyFormatter("backgroundColor"),
new fjs.PropertyFormatter("decoColor"),
new fjs.PropertyFormatter("textColor"),
]),
new fjs.ObjectFormatter("stats",true,[
new fjs.PropertyFormatter("enableCustomStats"),
new fjs.PropertyFormatter("backgroundColor"),
new fjs.PropertyFormatter("keyTextColor"),
new fjs.PropertyFormatter("valueTextColor"),
new fjs.PropertyFormatter("hideBackgroundColor"),
new fjs.PropertyFormatter("hideTextColor"),
]),
new fjs.ObjectFormatter("favicon",true,[
new fjs.PropertyFormatter("enableCustomFavicon"),
new fjs.PropertyFormatter("imageUrl"),
]),
]),
])
+2
View File
@@ -15,4 +15,6 @@ export const loadAllFlags = async () => {
opendiscord.flags.add(new api.ODFlag("opendiscord:force-slash-update","Force Slash Update","Force update all slash commands.","--force-slash",["-fs"]))
opendiscord.flags.add(new api.ODFlag("opendiscord:no-compile","No Compile","Disable compilation of plugins & bot before starting.","--no-compile",[]))
opendiscord.flags.add(new api.ODFlag("opendiscord:compile-only","Compile Only","This description will never be shown because the bot wouldn't run when this flag is enabled :)","--compile-only",[]))
opendiscord.flags.add(new api.ODFlag("opendiscord:silent","Silent Mode","Run the bot without displaying logs. The startscreen will still be displayed.","--silent",[]))
opendiscord.flags.add(new api.ODFlag("opendiscord:cli","Run CLI","This description will never be shown because the bot wouldn't run when this flag is enabled :)","--cli",[]))
}
+28 -3
View File
@@ -115,6 +115,17 @@ const main = async () => {
opendiscord.debug.visible = (debugFlag) ? debugFlag.value : false
}
//load silent mode
if (opendiscord.defaults.getDefault("silentLoading")){
const silentFlag = opendiscord.flags.get("opendiscord:silent")
opendiscord.console.silent = (silentFlag) ? silentFlag.value : false
if (opendiscord.console.silent){
opendiscord.console.silent = false
opendiscord.log("Silent mode is active! Logs won't be shown in the console.","warning")
opendiscord.console.silent = true
}
}
//load progress bar renderers
opendiscord.log("Loading progress bars...","system")
if (opendiscord.defaults.getDefault("progressBarRendererLoading")){
@@ -258,9 +269,10 @@ const main = async () => {
//render config checker
const advancedCheckerFlag = opendiscord.flags.get("opendiscord:checker")
const disableCheckerFlag = opendiscord.flags.get("opendiscord:no-checker")
const useCliFlag = opendiscord.flags.get("opendiscord:cli")
await opendiscord.events.get("onCheckerRender").emit([opendiscord.checkers.renderer,opendiscord.checkers])
if (opendiscord.defaults.getDefault("checkerRendering") && !(disableCheckerFlag ? disableCheckerFlag.value : false)){
if (opendiscord.defaults.getDefault("checkerRendering") && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){
//check if there is a result (otherwise throw minor error)
const result = opendiscord.checkers.lastResult
if (!result) return opendiscord.log("Failed to render Config Checker! (couldn't fetch result)","error")
@@ -279,7 +291,7 @@ const main = async () => {
}
//quit config checker (when required)
if (opendiscord.checkers.lastResult && !opendiscord.checkers.lastResult.valid && !(disableCheckerFlag ? disableCheckerFlag.value : false)){
if (opendiscord.checkers.lastResult && !opendiscord.checkers.lastResult.valid && !(disableCheckerFlag ? disableCheckerFlag.value : false) && !(useCliFlag ? useCliFlag.value : false)){
await opendiscord.events.get("onCheckerQuit").emit([opendiscord.checkers])
if (opendiscord.defaults.getDefault("checkerQuit")){
process.exit(1)
@@ -287,6 +299,14 @@ const main = async () => {
}
}
//switch to CLI context instead of running the bot
if (useCliFlag && useCliFlag.value){
await (await (import("./core/cli/cli.js"))).execute()
await utilities.timer(1000)
console.log("\n\n"+ansis.red("❌ Something went wrong in the Interactive Setup CLI. Please try again or report a bug in our discord server."))
process.exit(0)
}
//plugin loading before client
await opendiscord.events.get("onPluginBeforeClientLoad").emit([])
await opendiscord.events.get("afterPluginBeforeClientLoaded").emit([])
@@ -353,7 +373,7 @@ const main = async () => {
const client = opendiscord.client
//check if all servers are valid
const botServers = client.getGuilds()
const botServers = await client.getGuilds()
const generalConfig = opendiscord.configs.get("opendiscord:general")
const serverId = generalConfig.data.serverId ? generalConfig.data.serverId : ""
if (!serverId) throw new api.ODSystemError("Server Id Missing!")
@@ -823,6 +843,11 @@ const main = async () => {
opendiscord.log("Please help us improve the translation by contributing to our project!","warning")
console.log("===================")
}
if (opendiscord.console.silent){
opendiscord.console.silent = false
opendiscord.log("Silent mode is active! Logs won't be shown in the console.","warning")
opendiscord.console.silent = true
}
await opendiscord.events.get("afterStartScreensRendered").emit([opendiscord.startscreen])
}
+1 -1
View File
@@ -47,7 +47,7 @@
"descriptionColor":"normal"
},
"active":{
"versions":["4.0.0"],
"versions":["4.0.0","4.0.1","4.0.2","4.0.3","4.0.4"],
"languages":[],
"allLanguages":true,
"usingPlugins":true,