Merge branch 'dev-v4.1.0' into dev
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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,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"
|
||||
|
||||
|
||||
@@ -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)){
|
||||
//exists
|
||||
if (usedScope){
|
||||
const current: string[] = checker.storage.get(source,usedScope) ?? []
|
||||
current.push(id)
|
||||
checker.storage.set(source,usedScope,current)
|
||||
}
|
||||
}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
|
||||
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(value)
|
||||
checker.storage.set(source,usedScope,current)
|
||||
}
|
||||
})
|
||||
return !localQuit
|
||||
}
|
||||
return true
|
||||
}else{
|
||||
//doesn't exist
|
||||
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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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)+"\"!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -100,12 +116,4 @@ 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())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user