Files
open-ticket/src/core/api/modules/builder.ts
T

1517 lines
58 KiB
TypeScript

///////////////////////////////////////
//BUILDER MODULE
///////////////////////////////////////
import { ODId, ODValidButtonColor, ODValidId, ODSystemError, ODInterfaceWithPartialProperty, ODManagerWithSafety, ODManagerData } from "./base"
import * as discord from "discord.js"
import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker"
import { ODDebugger } from "./console"
/**## ODBuilderImplementation `class`
* This is an Open Ticket builder implementation.
*
* It is a basic implementation of the `ODWorkerManager` used by all `ODBuilder` classes.
*
* This class can't be used stand-alone & needs to be extended from!
*/
export class ODBuilderImplementation<Instance,Source extends string,Params,BuildType extends {id:ODId}> extends ODManagerData {
/**The manager that has all workers of this implementation */
workers: ODWorkerManager<Instance,Source,Params>
/**Cache a build or create it every time from scratch when this.build() gets executed. */
allowCache: boolean = false
/**Did the build already got created/cached? */
didCache: boolean = false
/**The cache of this build. */
cache:BuildType|null = null
constructor(id:ODValidId, callback?:ODWorkerCallback<Instance,Source,Params>, priority?:number, callbackId?:ODValidId){
super(id)
this.workers = new ODWorkerManager("ascending")
if (callback) this.workers.add(new ODWorker(callbackId ? callbackId : id,priority ?? 0,callback))
}
/**Set if caching is allowed */
setCacheMode(allowed:boolean){
this.allowCache = allowed
this.resetCache()
return this
}
/**Reset the current cache */
resetCache(){
this.cache = null
this.didCache = false
return this
}
/**Execute all workers & return the result. */
async build(source:Source, params:Params): Promise<BuildType> {
throw new ODSystemError("Tried to build an unimplemented ODBuilderImplementation")
}
}
/**## ODBuilderManager `class`
* This is an Open Ticket builder manager.
*
* It contains all Open Ticket builders. You can find messages, embeds, files & dropdowns, buttons & modals all here!
*
* Using the Open Ticket builder system has a few advantages compared to vanilla discord.js:
* - plugins can extend/edit messages
* - automatically reply on error
* - independent workers (with priority)
* - fail-safe design using try-catch
* - cache frequently used objects
* - get to know the source of the build request for a specific message, button, etc
* - And so much more!
*/
export class ODBuilderManager {
/**The manager for all button builders */
buttons: ODButtonManager
/**The manager for all dropdown builders */
dropdowns: ODDropdownManager
/**The manager for all file/attachment builders */
files: ODFileManager
/**The manager for all embed builders */
embeds: ODEmbedManager
/**The manager for all message builders */
messages: ODMessageManager
/**The manager for all modal builders */
modals: ODModalManager
constructor(debug:ODDebugger){
this.buttons = new ODButtonManager(debug)
this.dropdowns = new ODDropdownManager(debug)
this.files = new ODFileManager(debug)
this.embeds = new ODEmbedManager(debug)
this.messages = new ODMessageManager(debug)
this.modals = new ODModalManager(debug)
}
}
/**## ODComponentBuildResult `interface`
* This interface contains the result from a built component (button/dropdown). This can be used in the `ODMessage` builder!
*/
export interface ODComponentBuildResult {
/**The id of this component (button or dropdown) */
id:ODId,
/**The discord component or `\n` when it is a spacer between action rows */
component:discord.MessageActionRowComponentBuilder|"\n"|null
}
/**## ODButtonManager `class`
* This is an Open Ticket button manager.
*
* It contains all Open Ticket button builders. Here, you can add your own buttons or edit existing ones!
*
* It's recommended to use this system in combination with all the other Open Ticket builders!
*/
export class ODButtonManager extends ODManagerWithSafety<ODButton<string,any>> {
constructor(debug:ODDebugger){
super(() => {
return new ODButton("opendiscord:unknown-button",(instance,params,source,cancel) => {
instance.setCustomId("od:unknown-button")
instance.setMode("button")
instance.setColor("red")
instance.setLabel("<ODError:Unknown Button>")
instance.setEmoji("✖")
instance.setDisabled(true)
cancel()
})
},debug,"button")
}
/**Get a newline component for buttons & dropdowns! */
getNewLine(id:ODValidId): ODComponentBuildResult {
return {
id:new ODId(id),
component:"\n"
}
}
}
/**## ODButtonData `interface`
* This interface contains the data to build a button.
*/
export interface ODButtonData {
/**The custom id of this button */
customId:string,
/**The mode of this button */
mode:"button"|"url",
/**The url for when the mode is set to "url" */
url:string|null,
/**The button color */
color:ODValidButtonColor|null,
/**The button label */
label:string|null,
/**The button emoji */
emoji:string|null,
/**Is the button disabled? */
disabled:boolean
}
/**## ODButtonInstance `class`
* This is an Open Ticket button instance.
*
* It contains all properties & functions to build a button!
*/
export class ODButtonInstance {
/**The current data of this button */
data: ODButtonData = {
customId:"",
mode:"button",
url:null,
color:null,
label:null,
emoji:null,
disabled:false
}
/**Set the custom id of this button */
setCustomId(id:ODButtonData["customId"]){
this.data.customId = id
return this
}
/**Set the mode of this button */
setMode(mode:ODButtonData["mode"]){
this.data.mode = mode
return this
}
/**Set the url of this button */
setUrl(url:ODButtonData["url"]){
this.data.url = url
return this
}
/**Set the color of this button */
setColor(color:ODButtonData["color"]){
this.data.color = color
return this
}
/**Set the label of this button */
setLabel(label:ODButtonData["label"]){
this.data.label = label
return this
}
/**Set the emoji of this button */
setEmoji(emoji:ODButtonData["emoji"]){
this.data.emoji = emoji
return this
}
/**Disable this button */
setDisabled(disabled:ODButtonData["disabled"]){
this.data.disabled = disabled
return this
}
}
/**## ODButton `class`
* This is an Open Ticket button builder.
*
* With this class, you can create a button to use in a message.
* The only difference with normal buttons is that this one can be edited by Open Ticket plugins!
*
* This is possible by using "workers" or multiple functions that will be executed in priority order!
*/
export class ODButton<Source extends string,Params> extends ODBuilderImplementation<ODButtonInstance,Source,Params,ODComponentBuildResult> {
/**Build this button & compile it for discord.js */
async build(source:Source, params:Params): Promise<ODComponentBuildResult> {
if (this.didCache && this.cache && this.allowCache) return this.cache
try {
//create instance
const instance = new ODButtonInstance()
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
//create the discord.js button
const button = new discord.ButtonBuilder()
if (instance.data.mode == "button") button.setCustomId(instance.data.customId)
if (instance.data.mode == "url") button.setStyle(discord.ButtonStyle.Link)
else if (instance.data.color == "gray") button.setStyle(discord.ButtonStyle.Secondary)
else if (instance.data.color == "blue") button.setStyle(discord.ButtonStyle.Primary)
else if (instance.data.color == "green") button.setStyle(discord.ButtonStyle.Success)
else if (instance.data.color == "red") button.setStyle(discord.ButtonStyle.Danger)
if (instance.data.url) button.setURL(instance.data.url)
if (instance.data.label) button.setLabel(instance.data.label)
if (instance.data.emoji) button.setEmoji(instance.data.emoji)
if (instance.data.disabled) button.setDisabled(instance.data.disabled)
if (!instance.data.emoji && !instance.data.label) button.setLabel(instance.data.customId)
this.cache = {id:this.id,component:button}
this.didCache = true
return {id:this.id,component:button}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODButton:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,component:null}
}
}
}
/**## ODQuickButton `class`
* This is an Open Ticket quick button builder.
*
* With this class, you can quickly create a button to use in a message.
* This quick button can be used by Open Ticket plugins instead of the normal builders to speed up the process!
*
* Because of the quick functionality, these buttons are less customisable by other plugins.
*/
export class ODQuickButton {
/**The id of this button. */
id: ODId
/**The current data of this button */
data: ODButtonData = {
customId:"",
mode:"button",
url:null,
color:null,
label:null,
emoji:null,
disabled:false
}
constructor(id:ODValidId,data:ODButtonData){
this.id = new ODId(id)
this.data = data
}
/**Build this button & compile it for discord.js */
async build(): Promise<ODComponentBuildResult> {
try {
//create the discord.js button
const button = new discord.ButtonBuilder()
if (this.data.mode == "button") button.setCustomId(this.data.customId)
if (this.data.mode == "url") button.setStyle(discord.ButtonStyle.Link)
else if (this.data.color == "gray") button.setStyle(discord.ButtonStyle.Secondary)
else if (this.data.color == "blue") button.setStyle(discord.ButtonStyle.Primary)
else if (this.data.color == "green") button.setStyle(discord.ButtonStyle.Success)
else if (this.data.color == "red") button.setStyle(discord.ButtonStyle.Danger)
if (this.data.url) button.setURL(this.data.url)
if (this.data.label) button.setLabel(this.data.label)
if (this.data.emoji) button.setEmoji(this.data.emoji)
if (this.data.disabled) button.setDisabled(this.data.disabled)
if (!this.data.emoji && !this.data.label) button.setLabel(this.data.customId)
return {id:this.id,component:button}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODQuickButton:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,component:null}
}
}
}
/**## ODDropdownManager `class`
* This is an Open Ticket dropdown manager.
*
* It contains all Open Ticket dropdown builders. Here, you can add your own dropdowns or edit existing ones!
*
* It's recommended to use this system in combination with all the other Open Ticket builders!
*/
export class ODDropdownManager extends ODManagerWithSafety<ODDropdown<string,any>> {
constructor(debug:ODDebugger){
super(() => {
return new ODDropdown("opendiscord:unknown-dropdown",(instance,params,source,cancel) => {
instance.setCustomId("od:unknown-dropdown")
instance.setType("string")
instance.setPlaceholder("❌ <ODError:Unknown Dropdown>")
instance.setDisabled(true)
instance.setOptions([
{emoji:"❌",label:"<ODError:Unknown Dropdown>",value:"error"}
])
cancel()
})
},debug,"dropdown")
}
/**Get a newline component for buttons & dropdowns! */
getNewLine(id:ODValidId): ODComponentBuildResult {
return {
id:new ODId(id),
component:"\n"
}
}
}
/**## ODDropdownData `interface`
* This interface contains the data to build a dropdown.
*/
export interface ODDropdownData {
/**The custom id of this dropdown */
customId:string,
/**The type of this dropdown */
type:"string"|"role"|"channel"|"user"|"mentionable",
/**The placeholder of this dropdown */
placeholder:string|null,
/**The minimum amount of items to be selected in this dropdown */
minValues:number|null,
/**The maximum amount of items to be selected in this dropdown */
maxValues:number|null,
/**Is this dropdown disabled? */
disabled:boolean,
/**Allowed channel types when the type is "channel" */
channelTypes:discord.ChannelType[]
/**The options when the type is "string" */
options:discord.SelectMenuComponentOptionData[],
/**The options when the type is "user" */
users:discord.User[],
/**The options when the type is "role" */
roles:discord.Role[],
/**The options when the type is "channel" */
channels:discord.Channel[],
/**The options when the type is "mentionable" */
mentionables:(discord.User|discord.Role)[],
}
/**## ODDropdownInstance `class`
* This is an Open Ticket dropdown instance.
*
* It contains all properties & functions to build a dropdown!
*/
export class ODDropdownInstance {
/**The current data of this dropdown */
data: ODDropdownData = {
customId:"",
type:"string",
placeholder:null,
minValues:null,
maxValues:null,
disabled:false,
channelTypes:[],
options:[],
users:[],
roles:[],
channels:[],
mentionables:[]
}
/**Set the custom id of this dropdown */
setCustomId(id:ODDropdownData["customId"]){
this.data.customId = id
return this
}
/**Set the type of this dropdown */
setType(type:ODDropdownData["type"]){
this.data.type = type
return this
}
/**Set the placeholder of this dropdown */
setPlaceholder(placeholder:ODDropdownData["placeholder"]){
this.data.placeholder = placeholder
return this
}
/**Set the minimum amount of values in this dropdown */
setMinValues(minValues:ODDropdownData["minValues"]){
this.data.minValues = minValues
return this
}
/**Set the maximum amount of values ax this dropdown */
setMaxValues(maxValues:ODDropdownData["maxValues"]){
this.data.maxValues = maxValues
return this
}
/**Set the disabled of this dropdown */
setDisabled(disabled:ODDropdownData["disabled"]){
this.data.disabled = disabled
return this
}
/**Set the channel types of this dropdown */
setChannelTypes(channelTypes:ODDropdownData["channelTypes"]){
this.data.channelTypes = channelTypes
return this
}
/**Set the options of this dropdown (when `type == "string"`) */
setOptions(options:ODDropdownData["options"]){
this.data.options = options
return this
}
/**Set the users of this dropdown (when `type == "user"`) */
setUsers(users:ODDropdownData["users"]){
this.data.users = users
return this
}
/**Set the roles of this dropdown (when `type == "role"`) */
setRoles(roles:ODDropdownData["roles"]){
this.data.roles = roles
return this
}
/**Set the channels of this dropdown (when `type == "channel"`) */
setChannels(channels:ODDropdownData["channels"]){
this.data.channels = channels
return this
}
/**Set the mentionables of this dropdown (when `type == "mentionable"`) */
setMentionables(mentionables:ODDropdownData["mentionables"]){
this.data.mentionables = mentionables
return this
}
}
/**## ODDropdown `class`
* This is an Open Ticket dropdown builder.
*
* With this class, you can create a dropdown to use in a message.
* The only difference with normal dropdowns is that this one can be edited by Open Ticket plugins!
*
* This is possible by using "workers" or multiple functions that will be executed in priority order!
*/
export class ODDropdown<Source extends string,Params> extends ODBuilderImplementation<ODDropdownInstance,Source,Params,ODComponentBuildResult> {
/**Build this dropdown & compile it for discord.js */
async build(source:Source, params:Params): Promise<ODComponentBuildResult> {
if (this.didCache && this.cache && this.allowCache) return this.cache
try{
//create instance
const instance = new ODDropdownInstance()
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
//create the discord.js dropdown
if (instance.data.type == "string"){
const dropdown = new discord.StringSelectMenuBuilder()
dropdown.setCustomId(instance.data.customId)
dropdown.setOptions(...instance.data.options)
if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder)
if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues)
if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues)
if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled)
this.cache = {id:this.id,component:dropdown}
this.didCache = true
return {id:this.id,component:dropdown}
}else if (instance.data.type == "user"){
const dropdown = new discord.UserSelectMenuBuilder()
dropdown.setCustomId(instance.data.customId)
if (instance.data.users.length > 0) dropdown.setDefaultUsers(...instance.data.users.map((u) => u.id))
if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder)
if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues)
if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues)
if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled)
this.cache = {id:this.id,component:dropdown}
this.didCache = true
return {id:this.id,component:dropdown}
}else if (instance.data.type == "role"){
const dropdown = new discord.RoleSelectMenuBuilder()
dropdown.setCustomId(instance.data.customId)
if (instance.data.roles.length > 0) dropdown.setDefaultRoles(...instance.data.roles.map((r) => r.id))
if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder)
if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues)
if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues)
if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled)
this.cache = {id:this.id,component:dropdown}
this.didCache = true
return {id:this.id,component:dropdown}
}else if (instance.data.type == "channel"){
const dropdown = new discord.ChannelSelectMenuBuilder()
dropdown.setCustomId(instance.data.customId)
if (instance.data.channels.length > 0) dropdown.setDefaultChannels(...instance.data.channels.map((c) => c.id))
if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder)
if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues)
if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues)
if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled)
this.cache = {id:this.id,component:dropdown}
this.didCache = true
return {id:this.id,component:dropdown}
}else if (instance.data.type == "mentionable"){
const dropdown = new discord.MentionableSelectMenuBuilder()
const values: ({type:discord.SelectMenuDefaultValueType.User,id:string}|{type:discord.SelectMenuDefaultValueType.Role,id:string})[] = []
instance.data.mentionables.forEach((m) => {
if (m instanceof discord.User){
values.push({type:discord.SelectMenuDefaultValueType.User,id:m.id})
}else{
values.push({type:discord.SelectMenuDefaultValueType.Role,id:m.id})
}
})
dropdown.setCustomId(instance.data.customId)
if (instance.data.mentionables.length > 0) dropdown.setDefaultValues(...values)
if (instance.data.placeholder) dropdown.setPlaceholder(instance.data.placeholder)
if (instance.data.minValues) dropdown.setMinValues(instance.data.minValues)
if (instance.data.maxValues) dropdown.setMaxValues(instance.data.maxValues)
if (instance.data.disabled) dropdown.setDisabled(instance.data.disabled)
this.cache = {id:this.id,component:dropdown}
this.didCache = true
return {id:this.id,component:dropdown}
}else{
throw new Error("Tried to build an ODDropdown with unknown type!")
}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODDropdown:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,component:null}
}
}
}
/**## ODQuickDropdown `class`
* This is an Open Ticket quick dropdown builder.
*
* With this class, you can quickly create a dropdown to use in a message.
* This quick dropdown can be used by Open Ticket plugins instead of the normal builders to speed up the process!
*
* Because of the quick functionality, these dropdowns are less customisable by other plugins.
*/
export class ODQuickDropdown {
/**The id of this dropdown. */
id: ODId
/**The current data of this dropdown */
data: ODDropdownData = {
customId:"",
type:"string",
placeholder:null,
minValues:null,
maxValues:null,
disabled:false,
channelTypes:[],
options:[],
users:[],
roles:[],
channels:[],
mentionables:[]
}
constructor(id:ODValidId,data:ODDropdownData){
this.id = new ODId(id)
this.data = data
}
/**Build this dropdown & compile it for discord.js */
async build(): Promise<ODComponentBuildResult> {
try{
//create the discord.js dropdown
if (this.data.type == "string"){
const dropdown = new discord.StringSelectMenuBuilder()
dropdown.setCustomId(this.data.customId)
dropdown.setOptions(...this.data.options)
if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder)
if (this.data.minValues) dropdown.setMinValues(this.data.minValues)
if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues)
if (this.data.disabled) dropdown.setDisabled(this.data.disabled)
return {id:this.id,component:dropdown}
}else if (this.data.type == "user"){
const dropdown = new discord.UserSelectMenuBuilder()
dropdown.setCustomId(this.data.customId)
if (this.data.users.length > 0) dropdown.setDefaultUsers(...this.data.users.map((u) => u.id))
if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder)
if (this.data.minValues) dropdown.setMinValues(this.data.minValues)
if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues)
if (this.data.disabled) dropdown.setDisabled(this.data.disabled)
return {id:this.id,component:dropdown}
}else if (this.data.type == "role"){
const dropdown = new discord.RoleSelectMenuBuilder()
dropdown.setCustomId(this.data.customId)
if (this.data.roles.length > 0) dropdown.setDefaultRoles(...this.data.roles.map((r) => r.id))
if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder)
if (this.data.minValues) dropdown.setMinValues(this.data.minValues)
if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues)
if (this.data.disabled) dropdown.setDisabled(this.data.disabled)
return {id:this.id,component:dropdown}
}else if (this.data.type == "channel"){
const dropdown = new discord.ChannelSelectMenuBuilder()
dropdown.setCustomId(this.data.customId)
if (this.data.channels.length > 0) dropdown.setDefaultChannels(...this.data.channels.map((c) => c.id))
if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder)
if (this.data.minValues) dropdown.setMinValues(this.data.minValues)
if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues)
if (this.data.disabled) dropdown.setDisabled(this.data.disabled)
return {id:this.id,component:dropdown}
}else if (this.data.type == "mentionable"){
const dropdown = new discord.MentionableSelectMenuBuilder()
const values: ({type:discord.SelectMenuDefaultValueType.User,id:string}|{type:discord.SelectMenuDefaultValueType.Role,id:string})[] = []
this.data.mentionables.forEach((m) => {
if (m instanceof discord.User){
values.push({type:discord.SelectMenuDefaultValueType.User,id:m.id})
}else{
values.push({type:discord.SelectMenuDefaultValueType.Role,id:m.id})
}
})
dropdown.setCustomId(this.data.customId)
if (this.data.mentionables.length > 0) dropdown.setDefaultValues(...values)
if (this.data.placeholder) dropdown.setPlaceholder(this.data.placeholder)
if (this.data.minValues) dropdown.setMinValues(this.data.minValues)
if (this.data.maxValues) dropdown.setMaxValues(this.data.maxValues)
if (this.data.disabled) dropdown.setDisabled(this.data.disabled)
return {id:this.id,component:dropdown}
}else{
throw new Error("Tried to build an ODQuickDropdown with unknown type!")
}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODQuickDropdown:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,component:null}
}
}
}
/**## ODFileManager `class`
* This is an Open Ticket file manager.
*
* It contains all Open Ticket file builders. Here, you can add your own files or edit existing ones!
*
* It's recommended to use this system in combination with all the other Open Ticket builders!
*/
export class ODFileManager extends ODManagerWithSafety<ODFile<string,any>> {
constructor(debug:ODDebugger){
super(() => {
return new ODFile("opendiscord:unknown-file",(instance,params,source,cancel) => {
instance.setName("openticket_unknown-file.txt")
instance.setDescription("❌ <ODError:Unknown File>")
instance.setContents("Couldn't find file in registery `opendiscord.builders.files`")
cancel()
})
},debug,"file")
}
}
/**## ODFileData `interface`
* This interface contains the data to build a file.
*/
export interface ODFileData {
/**The file buffer, string or raw data */
file:discord.BufferResolvable
/**The name of the file */
name:string,
/**The description of the file */
description:string|null,
/**Set the file to be a spoiler */
spoiler:boolean
}
/**## ODFileBuildResult `interface`
* This interface contains the result from a built file (attachment). This can be used in the `ODMessage` builder!
*/
export interface ODFileBuildResult {
/**The id of this file */
id:ODId,
/**The discord file */
file:discord.AttachmentBuilder|null
}
/**## ODFileInstance `class`
* This is an Open Ticket file instance.
*
* It contains all properties & functions to build a file!
*/
export class ODFileInstance {
/**The current data of this file */
data: ODFileData = {
file:"",
name:"file.txt",
description:null,
spoiler:false
}
/**Set the file path of this attachment */
setFile(file:string){
this.data.file = file
return this
}
/**Set the file contents of this attachment */
setContents(contents:string|Buffer){
this.data.file = (typeof contents == "string") ? Buffer.from(contents) : contents
return this
}
/**Set the name of this attachment */
setName(name:ODFileData["name"]){
this.data.name = name
return this
}
/**Set the description of this attachment */
setDescription(description:ODFileData["description"]){
this.data.description = description
return this
}
/**Set this attachment to show as a spoiler */
setSpoiler(spoiler:ODFileData["spoiler"]){
this.data.spoiler = spoiler
return this
}
}
/**## ODFile `class`
* This is an Open Ticket file builder.
*
* With this class, you can create a file to use in a message.
* The only difference with normal files is that this one can be edited by Open Ticket plugins!
*
* This is possible by using "workers" or multiple functions that will be executed in priority order!
*/
export class ODFile<Source extends string,Params> extends ODBuilderImplementation<ODFileInstance,Source,Params,ODFileBuildResult> {
/**Build this attachment & compile it for discord.js */
async build(source:Source, params:Params): Promise<ODFileBuildResult> {
if (this.didCache && this.cache && this.allowCache) return this.cache
try{
//create instance
const instance = new ODFileInstance()
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
//create the discord.js attachment
const file = new discord.AttachmentBuilder(instance.data.file)
file.setName(instance.data.name ? instance.data.name : "file.txt")
if (instance.data.description) file.setDescription(instance.data.description)
if (instance.data.spoiler) file.setSpoiler(instance.data.spoiler)
this.cache = {id:this.id,file}
this.didCache = true
return {id:this.id,file}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODFile:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,file:null}
}
}
}
/**## ODQuickFile `class`
* This is an Open Ticket quick file builder.
*
* With this class, you can quickly create a file to use in a message.
* This quick file can be used by Open Ticket plugins instead of the normal builders to speed up the process!
*
* Because of the quick functionality, these files are less customisable by other plugins.
*/
export class ODQuickFile {
/**The id of this file. */
id: ODId
/**The current data of this file */
data: ODFileData = {
file:"",
name:"file.txt",
description:null,
spoiler:false
}
constructor(id:ODValidId,data:ODFileData){
this.id = new ODId(id)
this.data = data
}
/**Build this attachment & compile it for discord.js */
async build(): Promise<ODFileBuildResult> {
try{
//create the discord.js attachment
const file = new discord.AttachmentBuilder(this.data.file)
file.setName(this.data.name ? this.data.name : "file.txt")
if (this.data.description) file.setDescription(this.data.description)
if (this.data.spoiler) file.setSpoiler(this.data.spoiler)
return {id:this.id,file}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODQuickFile:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,file:null}
}
}
}
/**## ODEmbedManager `class`
* This is an Open Ticket embed manager.
*
* It contains all Open Ticket embed builders. Here, you can add your own embeds or edit existing ones!
*
* It's recommended to use this system in combination with all the other Open Ticket builders!
*/
export class ODEmbedManager extends ODManagerWithSafety<ODEmbed<string,any>> {
constructor(debug:ODDebugger){
super(() => {
return new ODEmbed("opendiscord:unknown-embed",(instance,params,source,cancel) => {
instance.setFooter("opendiscord:unknown-embed")
instance.setColor("#ff0000")
instance.setTitle("❌ <ODError:Unknown Embed>")
instance.setDescription("Couldn't find embed in registery `opendiscord.builders.embeds`")
cancel()
})
},debug,"embed")
}
}
/**## ODEmbedData `interface`
* This interface contains the data to build an embed.
*/
export interface ODEmbedData {
/**The title of the embed */
title:string|null,
/**The color of the embed */
color:discord.ColorResolvable|null,
/**The url of the embed */
url:string|null,
/**The description of the embed */
description:string|null,
/**The author text of the embed */
authorText:string|null,
/**The author image of the embed */
authorImage:string|null,
/**The author url of the embed */
authorUrl:string|null,
/**The footer text of the embed */
footerText:string|null,
/**The footer image of the embed */
footerImage:string|null,
/**The image of the embed */
image:string|null,
/**The thumbnail of the embed */
thumbnail:string|null,
/**The fields of the embed */
fields:ODInterfaceWithPartialProperty<discord.EmbedField,"inline">[],
/**The timestamp of the embed */
timestamp:number|Date|null
}
/**## ODEmbedBuildResult `interface`
* This interface contains the result from a built embed. This can be used in the `ODMessage` builder!
*/
export interface ODEmbedBuildResult {
/**The id of this embed */
id:ODId,
/**The discord embed */
embed:discord.EmbedBuilder|null
}
/**## ODEmbedInstance `class`
* This is an Open Ticket embed instance.
*
* It contains all properties & functions to build an embed!
*/
export class ODEmbedInstance {
/**The current data of this embed */
data: ODEmbedData = {
title:null,
color:null,
url:null,
description:null,
authorText:null,
authorImage:null,
authorUrl:null,
footerText:null,
footerImage:null,
image:null,
thumbnail:null,
fields:[],
timestamp:null
}
/**Set the title of this embed */
setTitle(title:ODEmbedData["title"]){
this.data.title = title
return this
}
/**Set the color of this embed */
setColor(color:ODEmbedData["color"]){
this.data.color = color
return this
}
/**Set the url of this embed */
setUrl(url:ODEmbedData["url"]){
this.data.url = url
return this
}
/**Set the description of this embed */
setDescription(description:ODEmbedData["description"]){
this.data.description = description
return this
}
/**Set the author of this embed */
setAuthor(text:ODEmbedData["authorText"], image?:ODEmbedData["authorImage"], url?:ODEmbedData["authorUrl"]){
this.data.authorText = text
this.data.authorImage = image ?? null
this.data.authorUrl = url ?? null
return this
}
/**Set the footer of this embed */
setFooter(text:ODEmbedData["footerText"], image?:ODEmbedData["footerImage"]){
this.data.footerText = text
this.data.footerImage = image ?? null
return this
}
/**Set the image of this embed */
setImage(image:ODEmbedData["image"]){
this.data.image = image
return this
}
/**Set the thumbnail of this embed */
setThumbnail(thumbnail:ODEmbedData["thumbnail"]){
this.data.thumbnail = thumbnail
return this
}
/**Set the fields of this embed */
setFields(fields:ODEmbedData["fields"]){
//TEMP CHECKS
fields.forEach((field,index) => {
if (field.value.length >= 1024) throw new ODSystemError("ODEmbed:setFields() => field "+index+" reached 1024 character limit!")
if (field.name.length >= 256) throw new ODSystemError("ODEmbed:setFields() => field "+index+" reached 256 name character limit!")
})
this.data.fields = fields
return this
}
/**Add fields to this embed */
addFields(...fields:ODEmbedData["fields"]){
//TEMP CHECKS
fields.forEach((field,index) => {
if (field.value.length >= 1024) throw new ODSystemError("ODEmbed:addFields() => field "+index+" reached 1024 character limit!")
if (field.name.length >= 256) throw new ODSystemError("ODEmbed:addFields() => field "+index+" reached 256 name character limit!")
})
this.data.fields.push(...fields)
return this
}
/**Clear all fields from this embed */
clearFields(){
this.data.fields = []
return this
}
/**Set the timestamp of this embed */
setTimestamp(timestamp:ODEmbedData["timestamp"]){
this.data.timestamp = timestamp
return this
}
}
/**## ODEmbed `class`
* This is an Open Ticket embed builder.
*
* With this class, you can create a embed to use in a message.
* The only difference with normal embeds is that this one can be edited by Open Ticket plugins!
*
* This is possible by using "workers" or multiple functions that will be executed in priority order!
*/
export class ODEmbed<Source extends string,Params> extends ODBuilderImplementation<ODEmbedInstance,Source,Params,ODEmbedBuildResult> {
/**Build this embed & compile it for discord.js */
async build(source:Source, params:Params): Promise<ODEmbedBuildResult> {
if (this.didCache && this.cache && this.allowCache) return this.cache
try{
//create instance
const instance = new ODEmbedInstance()
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
//create the discord.js embed
const embed = new discord.EmbedBuilder()
if (instance.data.title) embed.setTitle(instance.data.title)
if (instance.data.color) embed.setColor(instance.data.color)
if (instance.data.url) embed.setURL(instance.data.url)
if (instance.data.description) embed.setDescription(instance.data.description)
if (instance.data.authorText) embed.setAuthor({
name:instance.data.authorText,
iconURL:instance.data.authorImage ?? undefined,
url:instance.data.authorUrl ?? undefined
})
if (instance.data.footerText) embed.setFooter({
text:instance.data.footerText,
iconURL:instance.data.footerImage ?? undefined,
})
if (instance.data.image) embed.setImage(instance.data.image)
if (instance.data.thumbnail) embed.setThumbnail(instance.data.thumbnail)
if (instance.data.timestamp) embed.setTimestamp(instance.data.timestamp)
if (instance.data.fields.length > 0) embed.setFields(instance.data.fields)
this.cache = {id:this.id,embed}
this.didCache = true
return {id:this.id,embed}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODEmbed:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,embed:null}
}
}
}
/**## ODQuickEmbed `class`
* This is an Open Ticket quick embed builder.
*
* With this class, you can quickly create a embed to use in a message.
* This quick embed can be used by Open Ticket plugins instead of the normal builders to speed up the process!
*
* Because of the quick functionality, these embeds are less customisable by other plugins.
*/
export class ODQuickEmbed {
/**The id of this embed. */
id: ODId
/**The current data of this embed */
data: ODEmbedData = {
title:null,
color:null,
url:null,
description:null,
authorText:null,
authorImage:null,
authorUrl:null,
footerText:null,
footerImage:null,
image:null,
thumbnail:null,
fields:[],
timestamp:null
}
constructor(id:ODValidId,data:ODEmbedData){
this.id = new ODId(id)
this.data = data
}
/**Build this embed & compile it for discord.js */
async build(): Promise<ODEmbedBuildResult> {
try{
//create the discord.js embed
const embed = new discord.EmbedBuilder()
if (this.data.title) embed.setTitle(this.data.title)
if (this.data.color) embed.setColor(this.data.color)
if (this.data.url) embed.setURL(this.data.url)
if (this.data.description) embed.setDescription(this.data.description)
if (this.data.authorText) embed.setAuthor({
name:this.data.authorText,
iconURL:this.data.authorImage ?? undefined,
url:this.data.authorUrl ?? undefined
})
if (this.data.footerText) embed.setFooter({
text:this.data.footerText,
iconURL:this.data.footerImage ?? undefined,
})
if (this.data.image) embed.setImage(this.data.image)
if (this.data.thumbnail) embed.setThumbnail(this.data.thumbnail)
if (this.data.timestamp) embed.setTimestamp(this.data.timestamp)
if (this.data.fields.length > 0) embed.setFields(this.data.fields)
return {id:this.id,embed}
}catch(err){
process.emit("uncaughtException",new ODSystemError("ODQuickEmbed:build(\""+this.id.value+"\") => Major Error (see next error)"))
process.emit("uncaughtException",err)
return {id:this.id,embed:null}
}
}
}
/**## ODMessageManager `class`
* This is an Open Ticket message manager.
*
* It contains all Open Ticket message builders. Here, you can add your own messages or edit existing ones!
*
* It's recommended to use this system in combination with all the other Open Ticket builders!
*/
export class ODMessageManager extends ODManagerWithSafety<ODMessage<string,any>> {
constructor(debug:ODDebugger){
super(() => {
return new ODMessage("opendiscord:unknown-message",(instance,params,source,cancel) => {
instance.setContent("**❌ <ODError:Unknown Message>**\nCouldn't find message in registery `opendiscord.builders.messages`")
cancel()
})
},debug,"message")
}
}
/**## ODMessageData `interface`
* This interface contains the data to build a message.
*/
export interface ODMessageData {
/**The content of this message. `null` when no content */
content:string|null,
/**Poll data for this message */
poll:discord.PollData|null,
/**Try to make this message ephemeral when available */
ephemeral:boolean,
/**Embeds from this message */
embeds:ODEmbedBuildResult[],
/**Components from this message */
components:ODComponentBuildResult[],
/**Files from this message */
files:ODFileBuildResult[],
/**Additional options that aren't covered by the Open Ticket api!*/
additionalOptions:Omit<discord.MessageCreateOptions,"poll"|"content"|"embeds"|"components"|"files"|"flags">
}
/**## ODMessageBuildResult `interface`
* This interface contains the result from a built message. This can be sent in a discord channel!
*/
export interface ODMessageBuildResult {
/**The id of this message */
id:ODId,
/**The discord message */
message:Omit<discord.MessageCreateOptions,"flags">,
/**When enabled, the bot will try to send this as an ephemeral message */
ephemeral:boolean
}
/**## ODMessageBuildSentResult `interface`
* This interface contains the result from a sent built message. This can be used to edit, view & save the message that got created.
*/
export interface ODMessageBuildSentResult<InGuild extends boolean> {
/**Did the message get sent successfully? */
success:boolean,
/**The message that got sent. */
message:discord.Message<InGuild>|null
}
/**## ODMessageInstance `class`
* This is an Open Ticket message instance.
*
* It contains all properties & functions to build a message!
*/
export class ODMessageInstance {
/**The current data of this message */
data: ODMessageData = {
content:null,
poll:null,
ephemeral:false,
embeds:[],
components:[],
files:[],
additionalOptions:{
}
}
/**Set the content of this message */
setContent(content:ODMessageData["content"]){
this.data.content = content
return this
}
/**Set the poll of this message */
setPoll(poll:ODMessageData["poll"]){
this.data.poll = poll
return this
}
/**Make this message ephemeral when possible */
setEphemeral(ephemeral:ODMessageData["ephemeral"]){
this.data.ephemeral = ephemeral
return this
}
/**Set the embeds of this message */
setEmbeds(...embeds:ODEmbedBuildResult[]){
this.data.embeds = embeds
return this
}
/**Add an embed to this message! */
addEmbed(embed:ODEmbedBuildResult){
this.data.embeds.push(embed)
return this
}
/**Remove an embed from this message */
removeEmbed(id:ODValidId){
const index = this.data.embeds.findIndex((embed) => embed.id.value === new ODId(id).value)
if (index > -1) this.data.embeds.splice(index,1)
return this
}
/**Get an embed from this message */
getEmbed(id:ODValidId){
const embed = this.data.embeds.find((embed) => embed.id.value === new ODId(id).value)
if (embed) return embed.embed
else return null
}
/**Set the components of this message */
setComponents(...components:ODComponentBuildResult[]){
this.data.components = components
return this
}
/**Add a component to this message! */
addComponent(component:ODComponentBuildResult){
this.data.components.push(component)
return this
}
/**Remove a component from this message */
removeComponent(id:ODValidId){
const index = this.data.components.findIndex((component) => component.id.value === new ODId(id).value)
if (index > -1) this.data.components.splice(index,1)
return this
}
/**Get a component from this message */
getComponent(id:ODValidId){
const component = this.data.components.find((component) => component.id.value === new ODId(id).value)
if (component) return component.component
else return null
}
/**Set the files of this message */
setFiles(...files:ODFileBuildResult[]){
this.data.files = files
return this
}
/**Add a file to this message! */
addFile(file:ODFileBuildResult){
this.data.files.push(file)
return this
}
/**Remove a file from this message */
removeFile(id:ODValidId){
const index = this.data.files.findIndex((file) => file.id.value === new ODId(id).value)
if (index > -1) this.data.files.splice(index,1)
return this
}
/**Get a file from this message */
getFile(id:ODValidId){
const file = this.data.files.find((file) => file.id.value === new ODId(id).value)
if (file) return file.file
else return null
}
}
/**## ODMessage `class`
* This is an Open Ticket message builder.
*
* With this class, you can create a message to send in a discord channel.
* The only difference with normal messages is that this one can be edited by Open Ticket plugins!
*
* This is possible by using "workers" or multiple functions that will be executed in priority order!
*/
export class ODMessage<Source extends string,Params> extends ODBuilderImplementation<ODMessageInstance,Source,Params,ODMessageBuildResult> {
/**Build this message & compile it for discord.js */
async build(source:Source, params:Params){
if (this.didCache && this.cache && this.allowCache) return this.cache
//create instance
const instance = new ODMessageInstance()
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
//create the discord.js message
const componentArray: discord.ActionRowBuilder<discord.MessageActionRowComponentBuilder>[] = []
let currentRow: discord.ActionRowBuilder<discord.MessageActionRowComponentBuilder> = new discord.ActionRowBuilder()
instance.data.components.forEach((c) => {
//return when component crashed
if (c.component == null) return
else if (c.component == "\n"){
//create new current row when required
if (currentRow.components.length > 0){
componentArray.push(currentRow)
currentRow = new discord.ActionRowBuilder()
}
}else if (c.component instanceof discord.BaseSelectMenuBuilder){
//push current row when not empty
if (currentRow.components.length > 0){
componentArray.push(currentRow)
currentRow = new discord.ActionRowBuilder()
}
currentRow.addComponents(c.component)
//create new current row after dropdown
componentArray.push(currentRow)
currentRow = new discord.ActionRowBuilder()
}else{
//push button to current row
currentRow.addComponents(c.component)
}
//create new row when 5 rows in length
if (currentRow.components.length == 5){
componentArray.push(currentRow)
currentRow = new discord.ActionRowBuilder()
}
})
//push final row to array
if (currentRow.components.length > 0) componentArray.push(currentRow)
const filteredEmbeds = instance.data.embeds.map((e) => e.embed).filter((e) => e instanceof discord.EmbedBuilder) as discord.EmbedBuilder[]
const filteredFiles = instance.data.files.map((f) => f.file).filter((f) => f instanceof discord.AttachmentBuilder) as discord.AttachmentBuilder[]
const message : discord.MessageCreateOptions = {
content:instance.data.content ?? "",
poll:instance.data.poll ?? undefined,
embeds:filteredEmbeds,
components:componentArray,
files:filteredFiles
}
let result = {id:this.id,message,ephemeral:instance.data.ephemeral}
Object.assign(result.message,instance.data.additionalOptions)
this.cache = result
this.didCache = true
return result
}
}
/**## ODModalManager `class`
* This is an Open Ticket modal manager.
*
* It contains all Open Ticket modal builders. Here, you can add your own modals or edit existing ones!
*
* It's recommended to use this system in combination with all the other Open Ticket builders!
*/
export class ODModalManager extends ODManagerWithSafety<ODModal<string,any>> {
constructor(debug:ODDebugger){
super(() => {
return new ODModal("opendiscord:unknown-modal",(instance,params,source,cancel) => {
instance.setCustomId("od:unknown-modal")
instance.setTitle("❌ <ODError:Unknown Modal>")
instance.setQuestions(
{
style:"short",
customId:"error",
label:"error",
placeholder:"Contact the bot creator for more info!"
}
)
cancel()
})
},debug,"modal")
}
}
/**## ODModalDataQuestion `interface`
* This interface contains the data to build a modal question.
*/
export interface ODModalDataQuestion {
/**The style of this modal question */
style:"short"|"paragraph",
/**The custom id of this modal question */
customId:string
/**The label of this modal question */
label?:string,
/**The min length of this modal question */
minLength?:number,
/**The max length of this modal question */
maxLength?:number,
/**Is this modal question required? */
required?:boolean,
/**The placeholder of this modal question */
placeholder?:string,
/**The initial value of this modal question */
value?:string
}
/**## ODModalData `interface`
* This interface contains the data to build a modal.
*/
export interface ODModalData {
/**The custom id of this modal */
customId:string,
/**The title of this modal */
title:string|null,
/**The collection of questions in this modal */
questions:ODModalDataQuestion[],
}
/**## ODModalBuildResult `interface`
* This interface contains the result from a built modal (form). This can be used in the `ODMessage` builder!
*/
export interface ODModalBuildResult {
/**The id of this modal */
id:ODId,
/**The discord modal */
modal:discord.ModalBuilder
}
/**## ODModalInstance `class`
* This is an Open Ticket modal instance.
*
* It contains all properties & functions to build a modal!
*/
export class ODModalInstance {
/**The current data of this modal */
data: ODModalData = {
customId:"",
title:null,
questions:[]
}
/**Set the custom id of this modal */
setCustomId(customId:ODModalData["customId"]){
this.data.customId = customId
return this
}
/**Set the title of this modal */
setTitle(title:ODModalData["title"]){
this.data.title = title
return this
}
/**Set the questions of this modal */
setQuestions(...questions:ODModalData["questions"]){
this.data.questions = questions
return this
}
/**Add a question to this modal! */
addQuestion(question:ODModalDataQuestion){
this.data.questions.push(question)
return this
}
/**Remove a question from this modal */
removeQuestion(customId:string){
const index = this.data.questions.findIndex((question) => question.customId === customId)
if (index > -1) this.data.questions.splice(index,1)
return this
}
/**Get a question from this modal */
getQuestion(customId:string){
const question = this.data.questions.find((question) => question.customId === customId)
if (question) return question
else return null
}
}
/**## ODModal `class`
* This is an Open Ticket modal builder.
*
* With this class, you can create a modal to use as response in interactions.
* The only difference with normal modals is that this one can be edited by Open Ticket plugins!
*
* This is possible by using "workers" or multiple functions that will be executed in priority order!
*/
export class ODModal<Source extends string,Params> extends ODBuilderImplementation<ODModalInstance,Source,Params,ODModalBuildResult> {
/**Build this modal & compile it for discord.js */
async build(source:Source, params:Params){
if (this.didCache && this.cache && this.allowCache) return this.cache
//create instance
const instance = new ODModalInstance()
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
//create the discord.js modal
const modal = new discord.ModalBuilder()
modal.setCustomId(instance.data.customId)
if (instance.data.title) modal.setTitle(instance.data.title)
else modal.setTitle(instance.data.customId)
instance.data.questions.forEach((question) => {
const input = new discord.TextInputBuilder()
.setStyle(question.style == "paragraph" ? discord.TextInputStyle.Paragraph : discord.TextInputStyle.Short)
.setCustomId(question.customId)
.setLabel(question.label ? question.label : question.customId)
.setRequired(question.required ? true : false)
if (question.minLength) input.setMinLength(question.minLength)
if (question.maxLength) input.setMaxLength(question.maxLength)
if (question.value) input.setValue(question.value)
if (question.placeholder) input.setPlaceholder(question.placeholder)
modal.addComponents(
new discord.ActionRowBuilder<discord.ModalActionRowComponentBuilder>()
.addComponents(input)
)
})
this.cache = {id:this.id,modal}
this.didCache = true
return {id:this.id,modal}
}
}