From c64220cad4e3728fb3e1182a94520f2a99c6bc8f Mon Sep 17 00:00:00 2001 From: DJj123dj <80536295+DJj123dj@users.noreply.github.com> Date: Sat, 23 May 2026 15:53:52 +0200 Subject: [PATCH] (v4.2.0) Added sub-panels --- src/builders/buttons.ts | 19 +++- src/builders/dropdowns.ts | 4 +- src/builders/messages.ts | 8 +- src/commands/panel.ts | 62 +++++++++++- src/commands/role.ts | 7 +- src/commands/ticket.ts | 18 ++-- src/core/api/option.ts | 148 +++++++++++++--------------- src/core/mappings/builder.ts | 15 +-- src/core/mappings/responder.ts | 1 + src/core/mappings/state.ts | 6 +- src/core/startup/migration.ts | 33 ++++++- src/data/framework/codeLoader.ts | 5 +- src/data/openticket/optionLoader.ts | 33 ++++--- src/index.ts | 17 ++-- 14 files changed, 244 insertions(+), 132 deletions(-) diff --git a/src/builders/buttons.ts b/src/builders/buttons.ts index b4ae6cd..81b2fd0 100644 --- a/src/builders/buttons.ts +++ b/src/builders/buttons.ts @@ -123,7 +123,7 @@ const panelButtons = () => { new api.ODWorker("opendiscord:ticket-option",0,async (instance,params) => { const {panel,option} = params - instance.setCustomId("od:ticket-option_"+panel.id.value+"_"+option.id.value) + instance.setCustomId("od:ticket-option|"+option.id.value) instance.setMode("button") instance.setColor(option.get("opendiscord:button-color").value) if (option.get("opendiscord:button-emoji").value) instance.setEmoji(option.get("opendiscord:button-emoji").value) @@ -152,7 +152,22 @@ const panelButtons = () => { new api.ODWorker("opendiscord:role-option",0,async (instance,params) => { const {panel,option} = params - instance.setCustomId("od:role-option_"+panel.id.value+"_"+option.id.value) + instance.setCustomId("od:role-option|"+option.id.value) + instance.setMode("button") + instance.setColor(option.get("opendiscord:button-color").value) + if (option.get("opendiscord:button-emoji").value) instance.setEmoji(option.get("opendiscord:button-emoji").value) + if (option.get("opendiscord:button-label").value) instance.setLabel(option.get("opendiscord:button-label").value) + if (!option.get("opendiscord:button-emoji").value && !option.get("opendiscord:button-label").value) instance.setLabel("<"+option.id.value+">") + }) + ) + + //SUB-PANEL OPTION + buttons.add(new api.ODButton("opendiscord:subpanel-option")) + buttons.get("opendiscord:subpanel-option").workers.add( + new api.ODWorker("opendiscord:subpanel-option",0,async (instance,params) => { + const {panel,option} = params + + instance.setCustomId("od:subpanel-option|"+option.id.value) instance.setMode("button") instance.setColor(option.get("opendiscord:button-color").value) if (option.get("opendiscord:button-emoji").value) instance.setEmoji(option.get("opendiscord:button-emoji").value) diff --git a/src/builders/dropdowns.ts b/src/builders/dropdowns.ts index c83ebd7..61799fd 100644 --- a/src/builders/dropdowns.ts +++ b/src/builders/dropdowns.ts @@ -24,14 +24,14 @@ const panelDropdowns = () => { return { label:(label.length > 0) ? label : "", - value:"od:ticket-option_"+panel.id.value+"_"+option.id.value, + value:"od:ticket-option|"+option.id.value, emoji:(emoji.length > 0) ? emoji : undefined, description:(desc.length > 0) ? desc : undefined, default:false } }) - instance.setCustomId("od:panel-dropdown_"+panel.id.value) + instance.setCustomId("od:panel-dropdown") instance.setType("string") instance.setMaxValues(1) instance.setMinValues(0) diff --git a/src/builders/messages.ts b/src/builders/messages.ts index 7b49b23..0fb6461 100644 --- a/src/builders/messages.ts +++ b/src/builders/messages.ts @@ -274,7 +274,10 @@ const panelMessages = () => { messages.add(new api.ODMessage("opendiscord:panel")) messages.get("opendiscord:panel").workers.add([ new api.ODWorker("opendiscord:panel-layout",1,async (instance,params,origin) => { - const {guild,channel,user,panel} = params + const {guild,channel,user,panel,isSubPanel} = params + + //ephemeral if sub-panel + if (isSubPanel) instance.setEphemeral(true) //add text const text = panel.get("opendiscord:text").value @@ -292,7 +295,7 @@ const panelMessages = () => { //add embed const embedOptions = panel.get("opendiscord:embed").value - if (embedOptions.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel})) + if (embedOptions.enabled) instance.addEmbed(await embeds.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel,isSubPanel})) }), new api.ODWorker("opendiscord:panel-components",0,async (instance,params,origin) => { const {guild,channel,user,panel} = params @@ -315,6 +318,7 @@ const panelMessages = () => { if (option instanceof api.ODTicketOption) instance.addComponent(await buttons.getSafe("opendiscord:ticket-option").build(origin,{guild,channel,user,panel,option})) else if (option instanceof api.ODWebsiteOption) instance.addComponent(await buttons.getSafe("opendiscord:website-option").build(origin,{guild,channel,user,panel,option})) else if (option instanceof api.ODRoleOption) instance.addComponent(await buttons.getSafe("opendiscord:role-option").build(origin,{guild,channel,user,panel,option})) + else if (option instanceof api.ODSubPanelOption) instance.addComponent(await buttons.getSafe("opendiscord:subpanel-option").build(origin,{guild,channel,user,panel,option})) } } }) diff --git a/src/commands/panel.ts b/src/commands/panel.ts index 81430bf..8b9f9b6 100644 --- a/src/commands/panel.ts +++ b/src/commands/panel.ts @@ -36,12 +36,13 @@ export async function registerCommandResponders(){ } await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:panel-ready").build(origin,{guild,channel,user,panel})) - const panelMessage = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel})).message) + const panelMessage = await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:panel").build(origin,{guild,channel,user,panel,isSubPanel:false})).message) if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ messageOrigin:origin, panelId:panel.id.value, panelOptionIds:panel.get("opendiscord:options").value, - panelAutoUpdate:(instance.options.getBoolean("auto-update",false) ?? false) + panelAutoUpdate:(instance.options.getBoolean("auto-update",false) ?? false), + isSubPanel:false },panelMessage.flags.has("Ephemeral")) }), new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { @@ -53,4 +54,61 @@ export async function registerCommandResponders(){ ]) }) ]) +} + +export async function registerButtonResponders(){ + //SUBPANEL OPTION BUTTON RESPONDER + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:subpanel-option",/^od:subpanel-option\|([^|]+)/)) + opendiscord.responders.buttons.get("opendiscord:subpanel-option").workers.add( + new api.ODWorker("opendiscord:subpanel-option",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + const match = /^od:subpanel-option\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] + + //check message state + const state = await panelMsgState.getMsgState({channel,message}) + if (!state){ + //TODO TRANSLATION!!! + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel,user,error:"This panel is no longer valid or has expired. Create a new panel using `{0}` to solve the issue. It is normal to receive this error after a major Open Ticket update.".replace("{0}","/panel"),layout:"simple",customTitle:"Message State Expired"})) + return cancel() + } + + //responder checks + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //get option data + const option = opendiscord.options.get(optionId) + if (!option || !(option instanceof api.ODSubPanelOption)){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return cancel() + } + + //get panel data + const subPanel = opendiscord.panels.get(option.get("opendiscord:panel-id").value) + if (!subPanel){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-panel-unknown").build(origin,{guild,channel,user})) + return cancel() + } + + //send sub-panel message (ephemeral) + const panelMessage = await instance.reply((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("sub-panel",{guild,channel,user,panel:subPanel,isSubPanel:true}))) + if (panelMessage.success) await panelMsgState.setMsgState({channel,message:panelMessage.message},{ + messageOrigin:"sub-panel", + panelId:subPanel.id.value, + panelOptionIds:subPanel.get("opendiscord:options").value, + panelAutoUpdate:false, + isSubPanel:true + },panelMessage.ephemeral) + + opendiscord.log(instance.user.displayName+" created a sub-panel!","info",[ + {key:"user",value:instance.user.username}, + {key:"userid",value:instance.user.id,hidden:true}, + {key:"channelid",value:instance.channel.id,hidden:true}, + {key:"method",value:origin} + ]) + }) + ) } \ No newline at end of file diff --git a/src/commands/role.ts b/src/commands/role.ts index 58dbc17..d626ed8 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -8,17 +8,20 @@ const generalConfig = opendiscord.configs.get("opendiscord:general") export async function registerButtonResponders(){ //ROLE OPTION BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:role-option",/^od:role-option_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:role-option",/^od:role-option\|([^|]+)/)) opendiscord.responders.buttons.get("opendiscord:role-option").workers.add( new api.ODWorker("opendiscord:role-option",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance + + const match = /^od:role-option\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] //responder checks const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) if (!isInGuild || !guild || channel.isDMBased()) return cancel() //get option data - const optionId = instance.interaction.customId.split("_")[2] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODRoleOption)){ //error diff --git a/src/commands/ticket.ts b/src/commands/ticket.ts index 52d643d..2b69c46 100644 --- a/src/commands/ticket.ts +++ b/src/commands/ticket.ts @@ -80,11 +80,15 @@ export async function registerCommandResponders(){ export async function registerButtonResponders(){ //TICKET OPTION BUTTON RESPONDER - opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:ticket-option",/^od:ticket-option_/)) + opendiscord.responders.buttons.add(new api.ODButtonResponder("opendiscord:ticket-option",/^od:ticket-option\|([^|]+)/)) opendiscord.responders.buttons.get("opendiscord:ticket-option").workers.add( new api.ODWorker("opendiscord:ticket-option",0,async (instance,params,origin,cancel) => { const {guild,channel,user,message} = instance + const match = /^od:ticket-option\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] + //check message state const state = await panelMsgState.getMsgState({channel,message}) if (!state){ @@ -98,7 +102,6 @@ export async function registerButtonResponders(){ if (!isInGuild || !guild || channel.isDMBased()) return cancel() //get option data - const optionId = instance.interaction.customId.split("_")[2] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) @@ -130,7 +133,7 @@ export async function registerButtonResponders(){ export async function registerDropdownResponders(){ //PANEL DROPDOWN TICKETS DROPDOWN RESPONDER - opendiscord.responders.dropdowns.add(new api.ODDropdownResponder("opendiscord:panel-dropdown-tickets",/^od:panel-dropdown_/)) + opendiscord.responders.dropdowns.add(new api.ODDropdownResponder("opendiscord:panel-dropdown-tickets",/^od:panel-dropdown/)) opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown-tickets").workers.add( new api.ODWorker("opendiscord:panel-dropdown-tickets",0,async (instance,params,origin,cancel) => { const {guild,channel,user,message} = instance @@ -148,7 +151,9 @@ export async function registerDropdownResponders(){ if (!isInGuild || !guild || channel.isDMBased()) return cancel() //get option data - const optionId = instance.values.getStringValues()[0].split("_")[2] + const match = /^od:ticket-option\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return cancel() + const optionId = match[1] const option = opendiscord.options.get(optionId) if (!option || !(option instanceof api.ODTicketOption)){ instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) @@ -178,12 +183,13 @@ export async function registerDropdownResponders(){ //update panel after dropdown usage (reset panel choice) const panel = opendiscord.panels.get(state.data.panelId) if (panel){ - const panelMessage = await instance.message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild,channel,user,panel})).message) + const panelMessage = await instance.message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild,channel,user,panel,isSubPanel:state.data.isSubPanel})).message) if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ messageOrigin:"auto-update", panelId:panel.id.value, panelOptionIds:panel.get("opendiscord:options").value, - panelAutoUpdate:state.data.panelAutoUpdate //same value + panelAutoUpdate:state.data.panelAutoUpdate, //same value + isSubPanel:state.data.isSubPanel //same value },panelMessage.flags.has("Ephemeral")) } }) diff --git a/src/core/api/option.ts b/src/core/api/option.ts index b5034a6..a8fedad 100644 --- a/src/core/api/option.ts +++ b/src/core/api/option.ts @@ -96,6 +96,21 @@ export interface ODRoleOptionIdMappings extends ODOptionIdConstraint { "opendiscord:add-on-join":ODOptionData } +/**## ODSubPanelOptionIdMappings `interface` + * A list of all available IDs in the default `ODSubPanelOption` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODSubPanelOptionIdMappings extends ODOptionIdConstraint { + "opendiscord:name":ODOptionData, + "opendiscord:description":ODOptionData, + + "opendiscord:button-emoji":ODOptionData, + "opendiscord:button-label":ODOptionData, + "opendiscord:button-color":ODOptionData, + + "opendiscord:panel-id":ODOptionData +} + /**## ODOptionManager `class` * This is an Open Ticket option manager. * @@ -149,21 +164,41 @@ export interface ODOptionJson { * * It's recommended to use `ODTicketOption`, `ODWebsiteOption` or `ODRoleOption` instead! */ -export class ODOption extends api.ODManager> { +export abstract class ODOption extends api.ODManager> { /**The id of this option. (from the config) */ id:api.ODId /**The type of this option. (e.g. `opendiscord:ticket`, `opendiscord:website`, `opendiscord:role`) */ - type: string + abstract readonly type: string - constructor(id:api.ODValidId, type:string, data:ODOptionData[]){ + constructor(id:api.ODValidId, data:ODOptionData[]){ super() this.id = new api.ODId(id) - this.type = type data.forEach((data) => { this.add(data) }) } + get>(id:OptionId): IdList[OptionId] + get(id:api.ODValidId): ODOptionData|null + + get(id:api.ODValidId): ODOptionData|null { + return super.get(id) + } + + remove>(id:OptionId): IdList[OptionId] + remove(id:api.ODValidId): ODOptionData|null + + remove(id:api.ODValidId): ODOptionData|null { + return super.remove(id) + } + + exists(id:keyof api.ODNoGeneric): boolean + exists(id:api.ODValidId): boolean + + exists(id:api.ODValidId): boolean { + return super.exists(id) + } + /**Convert this option to a JSON object for storing this option in the database. */ toJson(version:api.ODVersion): ODOptionJson { const data = this.getAll().map((data) => { @@ -180,11 +215,6 @@ export class ODOption extends api.ODManager> { 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` @@ -224,32 +254,11 @@ export class ODOptionData extends api.ODMa * * Use this option to create a new ticket! */ -export class ODTicketOption extends ODOption { - type: "opendiscord:ticket" = "opendiscord:ticket" +export class ODTicketOption extends ODOption { + readonly type: "opendiscord:ticket" = "opendiscord:ticket" constructor(id:api.ODValidId, data:ODOptionData[]){ - super(id,"opendiscord:ticket",data) - } - - get>(id:OptionId): ODTicketOptionIdMappings[OptionId] - get(id:api.ODValidId): ODOptionData|null - - get(id:api.ODValidId): ODOptionData|null { - return super.get(id) - } - - remove>(id:OptionId): ODTicketOptionIdMappings[OptionId] - remove(id:api.ODValidId): ODOptionData|null - - remove(id:api.ODValidId): ODOptionData|null { - return super.remove(id) - } - - exists(id:keyof api.ODNoGeneric): boolean - exists(id:api.ODValidId): boolean - - exists(id:api.ODValidId): boolean { - return super.exists(id) + super(id,data) } static fromJson(json: ODOptionJson): ODTicketOption { @@ -264,32 +273,11 @@ export class ODTicketOption extends ODOption { * * Use this option to create a button which links to a website! */ -export class ODWebsiteOption extends ODOption { - type: "opendiscord:website" = "opendiscord:website" +export class ODWebsiteOption extends ODOption { + readonly type: "opendiscord:website" = "opendiscord:website" constructor(id:api.ODValidId, data:ODOptionData[]){ - super(id,"opendiscord:website",data) - } - - get>(id:OptionId): ODWebsiteOptionIdMappings[OptionId] - get(id:api.ODValidId): ODOptionData|null - - get(id:api.ODValidId): ODOptionData|null { - return super.get(id) - } - - remove>(id:OptionId): ODWebsiteOptionIdMappings[OptionId] - remove(id:api.ODValidId): ODOptionData|null - - remove(id:api.ODValidId): ODOptionData|null { - return super.remove(id) - } - - exists(id:keyof api.ODNoGeneric): boolean - exists(id:api.ODValidId): boolean - - exists(id:api.ODValidId): boolean { - return super.exists(id) + super(id,data) } static fromJson(json: ODOptionJson): ODWebsiteOption { @@ -304,32 +292,11 @@ export class ODWebsiteOption extends ODOption { * * Use this option to create a button for reaction roles! */ -export class ODRoleOption extends ODOption { - type: "opendiscord:role" = "opendiscord:role" +export class ODRoleOption extends ODOption { + readonly type: "opendiscord:role" = "opendiscord:role" constructor(id:api.ODValidId, data:ODOptionData[]){ - super(id,"opendiscord:role",data) - } - - get>(id:OptionId): ODRoleOptionIdMappings[OptionId] - get(id:api.ODValidId): ODOptionData|null - - get(id:api.ODValidId): ODOptionData|null { - return super.get(id) - } - - remove>(id:OptionId): ODRoleOptionIdMappings[OptionId] - remove(id:api.ODValidId): ODOptionData|null - - remove(id:api.ODValidId): ODOptionData|null { - return super.remove(id) - } - - exists(id:keyof api.ODNoGeneric): boolean - exists(id:api.ODValidId): boolean - - exists(id:api.ODValidId): boolean { - return super.exists(id) + super(id,data) } static fromJson(json:ODOptionJson): ODRoleOption { @@ -337,6 +304,25 @@ export class ODRoleOption extends ODOption { } } +/**## ODSubPanelOption `class` + * This is an Open Ticket sub-panel option. + * + * This class contains all data related to an Open Ticket sub-panel option (parsed from the config). + * + * Use this option to create a button for sub-panels! + */ +export class ODSubPanelOption extends ODOption { + readonly type: "opendiscord:sub-panel" = "opendiscord:sub-panel" + + constructor(id:api.ODValidId, data:ODOptionData[]){ + super(id,data) + } + + static fromJson(json:ODOptionJson): ODSubPanelOption { + return new ODSubPanelOption(json.id,json.data.map((data) => new ODOptionData(data.id,data.value))) + } +} + /**## ODOptionSuffixManager `class` * This is an Open Ticket option suffix manager. * diff --git a/src/core/mappings/builder.ts b/src/core/mappings/builder.ts index a4a2584..a65bd3f 100644 --- a/src/core/mappings/builder.ts +++ b/src/core/mappings/builder.ts @@ -4,7 +4,7 @@ import * as api from "@open-discord-bots/framework/api" import { ODPermissionEmbedType } from "./permission.js" import { ODTranscriptCompiler, ODTranscriptCompilerCompileResult } from "../api/transcript.js" -import { ODRoleOption, ODTicketOption, ODWebsiteOption } from "../api/option.js" +import { ODRoleOption, ODSubPanelOption, ODTicketOption, ODWebsiteOption } from "../api/option.js" import { ODTicket, ODTicketClearFilter } from "../api/ticket.js" import { ODRole, ODRoleUpdateResult } from "../api/role.js" import { ODPriorityLevel } from "../api/priority.js" @@ -25,9 +25,10 @@ export interface ODButtonManagerIdMappings extends api.ODButtonManagerIdConstrai "opendiscord:help-menu-page":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-page"} "opendiscord:help-menu-switch":{origin:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu-switch"}, - "opendiscord:ticket-option":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODTicketOption},workers:"opendiscord:ticket-option"}, - "opendiscord:website-option":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODWebsiteOption},workers:"opendiscord:website-option"}, - "opendiscord:role-option":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODRoleOption},workers:"opendiscord:role-option"} + "opendiscord:ticket-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODTicketOption},workers:"opendiscord:ticket-option"}, + "opendiscord:website-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODWebsiteOption},workers:"opendiscord:website-option"}, + "opendiscord:role-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODRoleOption},workers:"opendiscord:role-option"} + "opendiscord:subpanel-option":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,option:ODSubPanelOption},workers:"opendiscord:subpanel-option"} "opendiscord:visit-ticket":{origin:"ticket-created"|"dm"|"logs"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:visit-ticket"}, @@ -51,7 +52,7 @@ export interface ODButtonManagerIdMappings extends api.ODButtonManagerIdConstrai * It's used to generate typescript declarations for this class. */ export interface ODDropdownManagerIdMappings extends api.ODDropdownManagerIdConstraint { - "opendiscord:panel-dropdown-tickets":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,options:ODTicketOption[]},workers:"opendiscord:panel-dropdown-tickets"} + "opendiscord:panel-dropdown-tickets":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,options:ODTicketOption[]},workers:"opendiscord:panel-dropdown-tickets"} } /**## ODFileManagerIdMappings `interface` @@ -93,7 +94,7 @@ export interface ODEmbedManagerIdMappings extends api.ODEmbedManagerIdConstraint "opendiscord:stats-reset":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, "opendiscord:stats-ticket-unknown":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, - "opendiscord:panel":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel"}, + "opendiscord:panel":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,isSubPanel:boolean},workers:"opendiscord:panel"}, "opendiscord:ticket-created":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, "opendiscord:ticket-created-dm":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-dm"}, "opendiscord:ticket-created-logs":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created-logs"}, @@ -176,7 +177,7 @@ export interface ODMessageManagerIdMappings extends api.ODMessageManagerIdConstr "opendiscord:stats-reset":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,reason:string|null},workers:"opendiscord:stats-reset"}, "opendiscord:stats-ticket-unknown":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,id:string},workers:"opendiscord:stats-ticket-unknown"}, - "opendiscord:panel":{origin:"slash"|"text"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-layout"|"opendiscord:panel-components"}, + "opendiscord:panel":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,isSubPanel:boolean},workers:"opendiscord:panel-layout"|"opendiscord:panel-components"}, "opendiscord:panel-ready":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel},workers:"opendiscord:panel-ready"}, "opendiscord:ticket-created":{origin:"panel-button"|"panel-dropdown"|"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,ticket:ODTicket},workers:"opendiscord:ticket-created"}, diff --git a/src/core/mappings/responder.ts b/src/core/mappings/responder.ts index 036c509..84d91a8 100644 --- a/src/core/mappings/responder.ts +++ b/src/core/mappings/responder.ts @@ -48,6 +48,7 @@ export interface ODButtonResponderManagerIdMappings extends api.ODButtonResponde "opendiscord:ticket-option":{origin:"button",params:{},workers:"opendiscord:ticket-option"}, "opendiscord:role-option":{origin:"button",params:{},workers:"opendiscord:role-option"}, + "opendiscord:subpanel-option":{origin:"button",params:{},workers:"opendiscord:subpanel-option"}, "opendiscord:claim-ticket":{origin:"button",params:{},workers:"opendiscord:claim-ticket"}, "opendiscord:unclaim-ticket":{origin:"button",params:{},workers:"opendiscord:unclaim-ticket"}, diff --git a/src/core/mappings/state.ts b/src/core/mappings/state.ts index a5ce304..e426828 100644 --- a/src/core/mappings/state.ts +++ b/src/core/mappings/state.ts @@ -67,13 +67,15 @@ export class ODClearMessageState extends api.ODState<{ */ export class ODPanelMessageState extends api.ODState<{ /**The method this message was generated with. */ - messageOrigin:"slash"|"text"|"auto-update"|"other", + messageOrigin:"slash"|"text"|"sub-panel"|"auto-update"|"other", /**The Id of the panel associated with this message. */ panelId:string, /**A list of options available in this panel. (buttons or dropdown) */ panelOptionIds:string[] /**Should this panel be auto-updated on restart? */ - panelAutoUpdate:boolean + panelAutoUpdate:boolean, + /**Is this panel a sub-panel? */ + isSubPanel:boolean },false,false> { constructor(id:api.ODValidId,client:api.ODClientManager,database:api.ODDatabase){ super(id,client,database,{}) diff --git a/src/core/startup/migration.ts b/src/core/startup/migration.ts index 9e6cd68..fe929d8 100644 --- a/src/core/startup/migration.ts +++ b/src/core/startup/migration.ts @@ -378,15 +378,42 @@ export const migrations = [ await globalDatabase.delete("opendiscord:panel-update",panelMessage.key) } }) - opendiscord.events.get("afterStatesInitiated").listen(async (states) => { - const panelMsgState = states.get("opendiscord:panel-message") + opendiscord.events.get("beforeReadyForUsage").listen(async () => { + const panelMsgState = opendiscord.states.get("opendiscord:panel-message") for (const {channelId,messageId,panelId,autoUpdate} of migratedPanelStates){ await panelMsgState.setMsgState({channel:channelId,message:messageId},{ messageOrigin:"auto-update", panelId, panelOptionIds:[], - panelAutoUpdate:autoUpdate + panelAutoUpdate:autoUpdate, + isSubPanel:false },false) + + if (!autoUpdate){ + //auto update ALL non auto-update panels once to update button changes from v4.1 -> v4.2 + const panel = opendiscord.panels.get(panelId) + if (!panel) continue + + //fetch panel message + const mainServer = opendiscord.client.mainServer + const message = await opendiscord.client.fetchChannelMessage(channelId,messageId) + if (!message || !mainServer || !message.editable || message.flags.has("Ephemeral")) continue + + const panelMessage = await message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel:message.channel,user:opendiscord.client.client.user,panel,isSubPanel:false})).message) + if (panelMessage) await panelMsgState.setMsgState({channel:message.channel,message:panelMessage},{ + messageOrigin:"auto-update", + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:false, + isSubPanel:false + },panelMessage.flags.has("Ephemeral")) + + opendiscord.log("Panel in server got updated to v4.2!","info",[ + {key:"channelid",value:channelId}, + {key:"messageid",value:messageId}, + {key:"panel",value:panel.id.value} + ]) + } } }) } diff --git a/src/data/framework/codeLoader.ts b/src/data/framework/codeLoader.ts index 0bab5a4..35eec87 100644 --- a/src/data/framework/codeLoader.ts +++ b/src/data/framework/codeLoader.ts @@ -270,12 +270,13 @@ export async function loadPanelAutoUpdateCode(){ const message = await opendiscord.client.fetchChannelMessage(channel,panelState.messageId) if (!message || !message.editable || message.flags.has("Ephemeral")) continue - const panelMessage = await message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel,user:opendiscord.client.client.user,panel})).message) + const panelMessage = await message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel,user:opendiscord.client.client.user,panel,isSubPanel:false})).message) if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ messageOrigin:"auto-update", panelId:panel.id.value, panelOptionIds:panel.get("opendiscord:options").value, - panelAutoUpdate:true + panelAutoUpdate:true, + isSubPanel:false },panelMessage.flags.has("Ephemeral")) opendiscord.log("Panel in server got auto-updated!","info",[ diff --git a/src/data/openticket/optionLoader.ts b/src/data/openticket/optionLoader.ts index de8639d..d39cb77 100644 --- a/src/data/openticket/optionLoader.ts +++ b/src/data/openticket/optionLoader.ts @@ -9,11 +9,9 @@ export async function loadAllOptions(){ const loadedOption = loadTicketOption(option) opendiscord.options.add(loadedOption) opendiscord.options.suffix.add(loadTicketOptionSuffix(loadedOption)) - }else if (option.type == "website"){ - opendiscord.options.add(loadWebsiteOption(option)) - }else if (option.type == "role"){ - opendiscord.options.add(loadRoleOption(option)) - } + }else if (option.type == "website") opendiscord.options.add(loadWebsiteOption(option)) + else if (option.type == "role") opendiscord.options.add(loadRoleOption(option)) + else if (option.type == "sub-panel") opendiscord.options.add(loadSubPanelOption(option)) }) //update options on config reload @@ -23,17 +21,15 @@ export async function loadAllOptions(){ await opendiscord.options.suffix.loopAll((data,id) => {opendiscord.options.suffix.remove(id)}) //add new options - optionConfig.data.forEach((option) => { + for (const option of optionConfig.data){ if (option.type == "ticket"){ const loadedOption = loadTicketOption(option) opendiscord.options.add(loadedOption) opendiscord.options.suffix.add(loadTicketOptionSuffix(loadedOption)) - }else if (option.type == "website"){ - opendiscord.options.add(loadWebsiteOption(option)) - }else if (option.type == "role"){ - opendiscord.options.add(loadRoleOption(option)) - } - }) + }else if (option.type == "website") opendiscord.options.add(loadWebsiteOption(option)) + else if (option.type == "role") opendiscord.options.add(loadRoleOption(option)) + else if (option.type == "sub-panel") opendiscord.options.add(loadSubPanelOption(option)) + } //update options in tickets await opendiscord.tickets.loopAll((ticket) => { @@ -132,6 +128,19 @@ export const loadRoleOption = (opt:api.ODOptionsJsonConfig_RoleOption): api.ODRo ]) } +export const loadSubPanelOption = (opt:api.ODOptionsJsonConfig_SubPanelOption): api.ODSubPanelOption => { + return new api.ODSubPanelOption(opt.id,[ + new api.ODOptionData("opendiscord:name",opt.name), + new api.ODOptionData("opendiscord:description",opt.description), + + new api.ODOptionData("opendiscord:button-emoji",opt.button.emoji), + new api.ODOptionData("opendiscord:button-label",opt.button.label), + new api.ODOptionData("opendiscord:button-color",opt.button.color), + + new api.ODOptionData("opendiscord:panel-id",opt.subPanelId) + ]) +} + export const loadTicketOptionSuffix = (option:api.ODTicketOption): api.ODOptionSuffix => { const mode = option.get("opendiscord:channel-suffix").value const globalDatabase = opendiscord.databases.get("opendiscord:global") diff --git a/src/index.ts b/src/index.ts index 68417bc..10c8b1a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -526,15 +526,13 @@ const main = async () => { await opendiscord.events.get("afterStatesLoaded").emit([opendiscord.states]) //init states (async to prevent blocking startup) - utilities.runAsync(async () => { - opendiscord.log("Initiating states...","system") - await opendiscord.events.get("onStateInit").emit([opendiscord.states]) - if (opendiscord.sharedFuses.getFuse("stateInitiating")){ - await opendiscord.states.init() - await opendiscord.events.get("afterStatesInitiated").emit([opendiscord.states]) - } - opendiscord.log("Message states ready!","info") - }) + opendiscord.log("Initiating states... (may take a while for many tickets)","system") + await opendiscord.events.get("onStateInit").emit([opendiscord.states]) + if (opendiscord.sharedFuses.getFuse("stateInitiating")){ + await opendiscord.states.init() + await opendiscord.events.get("afterStatesInitiated").emit([opendiscord.states]) + } + opendiscord.log("Message states ready!","info") //plugin loading before managers await opendiscord.events.get("onPluginBeforeManagerLoad").emit([]) @@ -727,6 +725,7 @@ const main = async () => { await (await import("./actions/handleVerifyBar.js")).registerButtonResponders() await (await import("./actions/handleTranscriptErrors.js")).registerButtonResponders() await (await import("./commands/help.js")).registerButtonResponders() + await (await import("./commands/panel.js")).registerButtonResponders() await (await import("./commands/ticket.js")).registerButtonResponders() await (await import("./commands/close.js")).registerButtonResponders() await (await import("./commands/reopen.js")).registerButtonResponders()