/////////////////////////////////////// //OPENTICKET OPTION MODULE /////////////////////////////////////// import { ODDatabase } from "../modules/database" import { ODJsonConfig_DefaultOptionEmbedSettingsType, ODJsonConfig_DefaultOptionPingSettingsType } from "../defaults/config" import { ODId, ODManager, ODValidJsonType, ODValidId, ODVersion, ODValidButtonColor, ODManagerData, ODSystemError } from "../modules/base" import { ODDebugger } from "../modules/console" import * as discord from "discord.js" import * as crypto from "crypto" import { ODRoleUpdateMode } from "./role" /**## ODOptionManager `class` * This is an Open Ticket option manager. * * This class manages all registered options in the bot. This also includes temporary options generated from tickets where the original option got deleted. * * All option types including: tickets, websites & reaction roles are stored here. */ export class ODOptionManager extends ODManager { /**A reference to the Open Ticket debugger. */ #debug: ODDebugger /**The option suffix manager used to generate channel suffixes for ticket names. */ suffix: ODOptionSuffixManager constructor(debug:ODDebugger){ super(debug,"option") this.#debug = debug this.suffix = new ODOptionSuffixManager(debug) } add(data:ODOption, overwrite?:boolean): boolean { data.useDebug(this.#debug,"option data") return super.add(data,overwrite) } } /**## ODOptionDataJson `interface` * The JSON representatation from a single option property. */ export interface ODOptionDataJson { /**The id of this property. */ id:string, /**The value of this property. */ value:ODValidJsonType } /**## ODOptionDataJson `interface` * The JSON representatation from a single option. */ export interface ODOptionJson { /**The id of this option. */ id:string, /**The type of this option. (e.g. `opendiscord:ticket`, `opendiscord:website`, `opendiscord:role`) */ type:string, /**The version of Open Ticket used to create this option & store it in the database. */ version:string, /**The full list of properties/variables related to this option. */ data:ODOptionDataJson[] } /**## ODOption `class` * This is an Open Ticket option. * * This class contains all data related to this option (parsed from the config). * * It's recommended to use `ODTicketOption`, `ODWebsiteOption` or `ODRoleOption` instead! */ export class ODOption extends ODManager> { /**The id of this option. (from the config) */ id:ODId /**The type of this option. (e.g. `opendiscord:ticket`, `opendiscord:website`, `opendiscord:role`) */ type: string constructor(id:ODValidId, type:string, data:ODOptionData[]){ super() this.id = new ODId(id) this.type = type data.forEach((data) => { this.add(data) }) } /**Convert this option to a JSON object for storing this option in the database. */ toJson(version:ODVersion): ODOptionJson { const data = this.getAll().map((data) => { return { id:data.id.toString(), value:data.value } }) return { id:this.id.toString(), type:this.type, version:version.toString(), data } } /**Create an option from a JSON object in the database. */ static fromJson(json:ODOptionJson): ODOption { return new ODOption(json.id,json.type,json.data.map((data) => new ODOptionData(data.id,data.value))) } } /**## ODOptionData `class` * This is Open Ticket option data. * * This class contains a single property for an option. (string, number, boolean, object, array, null) * * When this property is edited, the database will be updated automatically. */ export class ODOptionData extends ODManagerData { /**The value of this property. */ #value: DataType constructor(id:ODValidId, value:DataType){ super(id) this.#value = value } /**The value of this property. */ set value(value:DataType){ this.#value = value this._change() } get value(): DataType { return this.#value } /**Refresh the database. Is only required to be used when updating `ODOptionData` with an object/array as value. */ refreshDatabase(){ this._change() } } /**## ODTicketOptionIds `type` * This interface is a list of ids available in the `ODTicketOption` class. * It's used to generate typescript declarations for this class. */ export interface ODTicketOptionIds { "opendiscord:name":ODOptionData, "opendiscord:description":ODOptionData, "opendiscord:button-emoji":ODOptionData, "opendiscord:button-label":ODOptionData, "opendiscord:button-color":ODOptionData, "opendiscord:admins":ODOptionData, "opendiscord:admins-readonly":ODOptionData, "opendiscord:allow-blacklisted-users":ODOptionData, "opendiscord:questions":ODOptionData, "opendiscord:channel-prefix":ODOptionData, "opendiscord:channel-suffix":ODOptionData<"user-name"|"user-nickname"|"user-id"|"random-number"|"random-hex"|"counter-dynamic"|"counter-fixed">, "opendiscord:channel-category":ODOptionData, "opendiscord:channel-category-closed":ODOptionData, "opendiscord:channel-category-backup":ODOptionData, "opendiscord:channel-categories-claimed":ODOptionData<{user:string,category:string}[]>, "opendiscord:channel-topic":ODOptionData, "opendiscord:dm-message-enabled":ODOptionData, "opendiscord:dm-message-text":ODOptionData, "opendiscord:dm-message-embed":ODOptionData, "opendiscord:ticket-message-enabled":ODOptionData, "opendiscord:ticket-message-text":ODOptionData, "opendiscord:ticket-message-embed":ODOptionData, "opendiscord:ticket-message-ping":ODOptionData, "opendiscord:autoclose-enable-hours":ODOptionData, "opendiscord:autoclose-enable-leave":ODOptionData, "opendiscord:autoclose-disable-claim":ODOptionData, "opendiscord:autoclose-hours":ODOptionData, "opendiscord:autodelete-enable-days":ODOptionData, "opendiscord:autodelete-enable-leave":ODOptionData, "opendiscord:autodelete-disable-claim":ODOptionData, "opendiscord:autodelete-days":ODOptionData, "opendiscord:cooldown-enabled":ODOptionData, "opendiscord:cooldown-minutes":ODOptionData, "opendiscord:limits-enabled":ODOptionData, "opendiscord:limits-maximum-global":ODOptionData, "opendiscord:limits-maximum-user":ODOptionData "opendiscord:slowmode-enabled":ODOptionData, "opendiscord:slowmode-seconds":ODOptionData, } /**## ODTicketOption `class` * This is an Open Ticket ticket option. * * This class contains all data related to an Open Ticket ticket option (parsed from the config). * * Use this option to create a new ticket! */ export class ODTicketOption extends ODOption { type: "opendiscord:ticket" = "opendiscord:ticket" constructor(id:ODValidId, data:ODOptionData[]){ super(id,"opendiscord:ticket",data) } get(id:OptionId): ODTicketOptionIds[OptionId] get(id:ODValidId): ODOptionData|null get(id:ODValidId): ODOptionData|null { return super.get(id) } remove(id:OptionId): ODTicketOptionIds[OptionId] remove(id:ODValidId): ODOptionData|null remove(id:ODValidId): ODOptionData|null { return super.remove(id) } exists(id:keyof ODTicketOptionIds): boolean exists(id:ODValidId): boolean exists(id:ODValidId): boolean { return super.exists(id) } static fromJson(json: ODOptionJson): ODTicketOption { return new ODTicketOption(json.id,json.data.map((data) => new ODOptionData(data.id,data.value))) } } /**## ODWebsiteOptionIds `type` * This interface is a list of ids available in the `ODWebsiteOption` class. * It's used to generate typescript declarations for this class. */ export interface ODWebsiteOptionIds { "opendiscord:name":ODOptionData, "opendiscord:description":ODOptionData, "opendiscord:button-emoji":ODOptionData, "opendiscord:button-label":ODOptionData, "opendiscord:url":ODOptionData, } /**## ODWebsiteOption `class` * This is an Open Ticket website option. * * This class contains all data related to an Open Ticket website option (parsed from the config). * * Use this option to create a button which links to a website! */ export class ODWebsiteOption extends ODOption { type: "opendiscord:website" = "opendiscord:website" constructor(id:ODValidId, data:ODOptionData[]){ super(id,"opendiscord:website",data) } get(id:OptionId): ODWebsiteOptionIds[OptionId] get(id:ODValidId): ODOptionData|null get(id:ODValidId): ODOptionData|null { return super.get(id) } remove(id:OptionId): ODWebsiteOptionIds[OptionId] remove(id:ODValidId): ODOptionData|null remove(id:ODValidId): ODOptionData|null { return super.remove(id) } exists(id:keyof ODWebsiteOptionIds): boolean exists(id:ODValidId): boolean exists(id:ODValidId): boolean { return super.exists(id) } static fromJson(json: ODOptionJson): ODWebsiteOption { return new ODWebsiteOption(json.id,json.data.map((data) => new ODOptionData(data.id,data.value))) } } /**## ODRoleOptionIds `type` * This interface is a list of ids available in the `ODRoleOption` class. * It's used to generate typescript declarations for this class. */ export interface ODRoleOptionIds { "opendiscord:name":ODOptionData, "opendiscord:description":ODOptionData, "opendiscord:button-emoji":ODOptionData, "opendiscord:button-label":ODOptionData, "opendiscord:button-color":ODOptionData, "opendiscord:roles":ODOptionData, "opendiscord:mode":ODOptionData, "opendiscord:remove-roles-on-add":ODOptionData, "opendiscord:add-on-join":ODOptionData } /**## ODRoleOption `class` * This is an Open Ticket role option. * * This class contains all data related to an Open Ticket role option (parsed from the config). * * Use this option to create a button for reaction roles! */ export class ODRoleOption extends ODOption { type: "opendiscord:role" = "opendiscord:role" constructor(id:ODValidId, data:ODOptionData[]){ super(id,"opendiscord:role",data) } get(id:OptionId): ODRoleOptionIds[OptionId] get(id:ODValidId): ODOptionData|null get(id:ODValidId): ODOptionData|null { return super.get(id) } remove(id:OptionId): ODRoleOptionIds[OptionId] remove(id:ODValidId): ODOptionData|null remove(id:ODValidId): ODOptionData|null { return super.remove(id) } exists(id:keyof ODRoleOptionIds): boolean exists(id:ODValidId): boolean exists(id:ODValidId): boolean { return super.exists(id) } static fromJson(json:ODOptionJson): ODRoleOption { return new ODRoleOption(json.id,json.data.map((data) => new ODOptionData(data.id,data.value))) } } /**## ODOptionSuffixManager `class` * This is an Open Ticket option suffix manager. * * This class manages all suffixes from option in the bot. The id of an option suffix is the same as the option id. * * All ticket options should have a corresponding option suffix class. */ export class ODOptionSuffixManager extends ODManager { constructor(debug:ODDebugger){ super(debug,"ticket suffix") } /**Instantly get the suffix from an `ODTicketOption`. */ async getSuffixFromOption(option:ODTicketOption,user:discord.User,guild:discord.Guild): Promise { const suffix = this.getAll().find((suffix) => suffix.option.id.value == option.id.value) if (!suffix) return null try{ const member = await guild.members.fetch(user.id) return await suffix.getSuffix(member) }catch(err){ process.emit("uncaughtException",err) return null } } } /**## ODOptionSuffix `class` * This is an Open Ticket option suffix. * * This class can generate a suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionSuffix extends ODManagerData { /**The option of this suffix. */ option: ODTicketOption constructor(id:ODValidId, option:ODTicketOption){ super(id) this.option = option } /**Get the suffix for a new ticket. */ async getSuffix(member:discord.GuildMember): Promise { throw new ODSystemError("Tried to use an unimplemented ODOptionSuffix!") } } /**## ODOptionUserNameSuffix `class` * This is an Open Ticket user-name option suffix. * * This class can generate a user-name suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionUserNameSuffix extends ODOptionSuffix { async getSuffix(member:discord.GuildMember): Promise { return member.user.username } } /**## ODOptionUserNicknameSuffix `class` * This is an Open Ticket user-nickname option suffix. * * This class can generate a user-nickname suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionUserNicknameSuffix extends ODOptionSuffix { async getSuffix(member:discord.GuildMember): Promise { return member.displayName } } /**## ODOptionUserIdSuffix `class` * This is an Open Ticket user-id option suffix. * * This class can generate a user-id suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionUserIdSuffix extends ODOptionSuffix { async getSuffix(member:discord.GuildMember): Promise { return member.id } } /**## ODOptionCounterDynamicSuffix `class` * This is an Open Ticket counter-dynamic option suffix. * * This class can generate a counter-dynamic suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionCounterDynamicSuffix extends ODOptionSuffix { /**The database where the value of this counter is stored. */ database: ODDatabase constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ super(id,option) this.database = database this.#init() } /**Initialize the database for this suffix. */ async #init(){ if (!await this.database.exists("opendiscord:option-suffix-counter",this.option.id.value)) await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,0) } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValue = await this.database.get("opendiscord:option-suffix-counter",this.option.id.value) const currentValue = (typeof rawCurrentValue != "number") ? 0 : rawCurrentValue const newValue = currentValue+1 await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,newValue) return newValue.toString() } } /**## ODOptionCounterFixedSuffix `class` * This is an Open Ticket counter-fixed option suffix. * * This class can generate a counter-fixed suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionCounterFixedSuffix extends ODOptionSuffix { /**The database where the value of this counter is stored. */ database: ODDatabase constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ super(id,option) this.database = database this.#init() } /**Initialize the database for this suffix. */ async #init(){ if (!await this.database.exists("opendiscord:option-suffix-counter",this.option.id.value)) await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,0) } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValue = await this.database.get("opendiscord:option-suffix-counter",this.option.id.value) const currentValue = (typeof rawCurrentValue != "number") ? 0 : rawCurrentValue const newValue = (currentValue >= 9999) ? 0 : currentValue+1 await this.database.set("opendiscord:option-suffix-counter",this.option.id.value,newValue) if (newValue.toString().length == 1) return "000"+newValue.toString() else if (newValue.toString().length == 2) return "00"+newValue.toString() else if (newValue.toString().length == 3) return "0"+newValue.toString() else return newValue.toString() } } /**## ODOptionRandomNumberSuffix `class` * This is an Open Ticket random-number option suffix. * * This class can generate a random-number suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionRandomNumberSuffix extends ODOptionSuffix { /**The database where previous random numbers are stored. */ database: ODDatabase constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ super(id,option) this.database = database this.#init() } /**Initialize the database for this suffix. */ async #init(){ if (!await this.database.exists("opendiscord:option-suffix-history",this.option.id.value)) await this.database.set("opendiscord:option-suffix-history",this.option.id.value,[]) } /**Get a unique number for this suffix. */ #generateUniqueValue(history:string[]): string { const rawNumber = Math.round(Math.random()*1000).toString() let number = rawNumber if (rawNumber.length == 1) number = "000"+rawNumber else if (rawNumber.length == 2) number = "00"+rawNumber else if (rawNumber.length == 3) number = "0"+rawNumber if (history.includes(number)) return this.#generateUniqueValue(history) else return number } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValues = await this.database.get("opendiscord:option-suffix-history",this.option.id.value) const currentValues = ((Array.isArray(rawCurrentValues)) ? rawCurrentValues : []) as string[] const newValue = this.#generateUniqueValue(currentValues) currentValues.push(newValue) if (currentValues.length > 50) currentValues.shift() await this.database.set("opendiscord:option-suffix-history",this.option.id.value,currentValues) return newValue } } /**## ODOptionRandomHexSuffix `class` * This is an Open Ticket random-hex option suffix. * * This class can generate a random-hex suffix for a discord channel name from a specific option. * * Use `getSuffix()` to get the new suffix! */ export class ODOptionRandomHexSuffix extends ODOptionSuffix { /**The database where previous random hexes are stored. */ database: ODDatabase constructor(id:ODValidId, option:ODTicketOption, database:ODDatabase){ super(id,option) this.database = database this.#init() } /**Initialize the database for this suffix. */ async #init(){ if (!await this.database.exists("opendiscord:option-suffix-history",this.option.id.value)) await this.database.set("opendiscord:option-suffix-history",this.option.id.value,[]) } /**Get a unique hex-string for this suffix. */ #generateUniqueValue(history:string[]): string { const hex = crypto.randomBytes(2).toString("hex") if (history.includes(hex)) return this.#generateUniqueValue(history) else return hex } async getSuffix(member:discord.GuildMember): Promise { const rawCurrentValues = await this.database.get("opendiscord:option-suffix-history",this.option.id.value) const currentValues = ((Array.isArray(rawCurrentValues)) ? rawCurrentValues : []) as string[] const newValue = this.#generateUniqueValue(currentValues) currentValues.push(newValue) if (currentValues.length > 50) currentValues.shift() await this.database.set("opendiscord:option-suffix-history",this.option.id.value,currentValues) return newValue } }