From 6c41aecfa79d4138dd47061bf721ba15c0237147 Mon Sep 17 00:00:00 2001 From: DJj123dj <80536295+DJj123dj@users.noreply.github.com> Date: Sat, 23 May 2026 16:45:01 +0200 Subject: [PATCH] Bugfixes & Improvements Added "maximum buttons per row" in panels. Fixed reaction role reply glitching when replyOnReactionRole was disabled. Panel dropdowns now support reaction role & sub-panel options. --- src/actions/utilities.ts | 20 +++ src/builders/dropdowns.ts | 54 ++++++-- src/builders/messages.ts | 13 +- src/commands/panel.ts | 198 +++++++++++++++++++++++++++- src/commands/role.ts | 13 +- src/commands/ticket.ts | 89 +------------ src/core/api/panel.ts | 1 + src/core/mappings/builder.ts | 4 +- src/core/mappings/responder.ts | 2 +- src/data/framework/checkerLoader.ts | 7 +- src/data/openticket/panelLoader.ts | 3 + src/index.ts | 2 +- 12 files changed, 289 insertions(+), 117 deletions(-) diff --git a/src/actions/utilities.ts b/src/actions/utilities.ts index 8f87003..25f9b45 100644 --- a/src/actions/utilities.ts +++ b/src/actions/utilities.ts @@ -4,6 +4,26 @@ import {opendiscord, api, utilities} from "../index.js" import * as discord from "discord.js" +/**Check the ticket creation permissions like: cooldown, limits, blacklist, ... */ +export async function checkTicketCreationPerms(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance|api.ODCommandResponderInstance,origin:api.ODActionManagerIdMappings["opendiscord:create-ticket-permissions"]["origin"],guild:discord.Guild,user:discord.User,option:api.ODTicketOption){ + //check ticket permissions + const lang = opendiscord.languages + const permsRes = await opendiscord.actions.get("opendiscord:create-ticket-permissions").run(origin,{guild,user,option}) + if (!permsRes.valid && instance.channel){ + //error + const newOrigin = (origin === "slash" || origin === "text") ? origin : "other" + if (permsRes.reason == "blacklist") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-blacklisted").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + else if (permsRes.reason == "cooldown") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-cooldown").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,until:permsRes.cooldownUntil})) + else if (permsRes.reason == "global-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global"})) + else if (permsRes.reason == "global-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"})) + else if (permsRes.reason == "option-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"})) + else if (permsRes.reason == "option-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"})) + else if (permsRes.reason == "custom") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:permsRes.customReason ?? lang.getTranslation("errors.descriptions.unableToCreateTicket")+" `Unknown invalid_permission_reason => no reason specified by plugin`",customTitle:lang.getTranslation("errors.titles.permissionError")})) + else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"})) + return false + }else return true +} + /**Fetch the interactive message state. If not present, auto replies with error and returns `null`. When `null` is received, the worker should be returned and canceled. */ export async function replyInteractiveMessageState(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance,origin:"slash"|"text"|"button"|"dropdown"|"modal"|"other",channel:discord.TextBasedChannel,message:discord.Message|null,replacementCommandName:string){ //check message state diff --git a/src/builders/dropdowns.ts b/src/builders/dropdowns.ts index 61799fd..2bb2c04 100644 --- a/src/builders/dropdowns.ts +++ b/src/builders/dropdowns.ts @@ -12,23 +12,49 @@ export async function registerAllDropdowns(){ const panelDropdowns = () => { //TICKET OPTION - dropdowns.add(new api.ODDropdown("opendiscord:panel-dropdown-tickets")) - dropdowns.get("opendiscord:panel-dropdown-tickets").workers.add( - new api.ODWorker("opendiscord:panel-dropdown-tickets",0,async (instance,params) => { + dropdowns.add(new api.ODDropdown("opendiscord:panel-dropdown")) + dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:panel-dropdown",0,async (instance,params) => { const {panel,options} = params const parsedOptions: api.ODDropdownData["options"] = options.map((option) => { - const label = option.get("opendiscord:button-label").value.substring(0,100) - const desc = option.get("opendiscord:description").value.substring(0,100) - const emoji = option.get("opendiscord:button-emoji").value - - return { - label:(label.length > 0) ? label : "", - value:"od:ticket-option|"+option.id.value, - emoji:(emoji.length > 0) ? emoji : undefined, - description:(desc.length > 0) ? desc : undefined, - default:false - } + if (option instanceof api.ODTicketOption){ + const label = option.get("opendiscord:button-label").value.substring(0,100) + const desc = option.get("opendiscord:description").value.substring(0,100) + const emoji = option.get("opendiscord:button-emoji").value + + return { + label:(label.length > 0) ? label : "", + value:"od:ticket-option|"+option.id.value, + emoji:(emoji.length > 0) ? emoji : undefined, + description:(desc.length > 0) ? desc : undefined, + default:false + } + }else if (option instanceof api.ODRoleOption){ + const label = option.get("opendiscord:button-label").value.substring(0,100) + const desc = option.get("opendiscord:description").value.substring(0,100) + const emoji = option.get("opendiscord:button-emoji").value + + return { + label:(label.length > 0) ? label : "", + value:"od:role-option|"+option.id.value, + emoji:(emoji.length > 0) ? emoji : undefined, + description:(desc.length > 0) ? desc : undefined, + default:false + } + }else if (option instanceof api.ODSubPanelOption){ + const label = option.get("opendiscord:button-label").value.substring(0,100) + const desc = option.get("opendiscord:description").value.substring(0,100) + const emoji = option.get("opendiscord:button-emoji").value + + return { + label:(label.length > 0) ? label : "", + value:"od:subpanel-option|"+option.id.value, + emoji:(emoji.length > 0) ? emoji : undefined, + description:(desc.length > 0) ? desc : undefined, + default:false + } + }else throw new api.ODSystemError("Unable to create panel dropdown with options that don't match: ticket, role, sub-panel!") }) instance.setCustomId("od:panel-dropdown") diff --git a/src/builders/messages.ts b/src/builders/messages.ts index 0fb6461..7d6665a 100644 --- a/src/builders/messages.ts +++ b/src/builders/messages.ts @@ -307,18 +307,27 @@ const panelMessages = () => { if (panel.get("opendiscord:dropdown").value){ //dropdown - const ticketOptions: api.ODTicketOption[] = [] + const ticketOptions: (api.ODTicketOption|api.ODRoleOption|api.ODSubPanelOption)[] = [] options.forEach((option) => { if (option instanceof api.ODTicketOption) ticketOptions.push(option) + if (option instanceof api.ODRoleOption) ticketOptions.push(option) + if (option instanceof api.ODSubPanelOption) ticketOptions.push(option) }) - instance.addComponent(await dropdowns.getSafe("opendiscord:panel-dropdown-tickets").build(origin,{guild,channel,user,panel,options:ticketOptions})) + instance.addComponent(await dropdowns.getSafe("opendiscord:panel-dropdown").build(origin,{guild,channel,user,panel,options:ticketOptions})) }else{ //buttons + let rowButtonCount: number = 0 for (const option of options){ + if (rowButtonCount >= panel.get("opendiscord:maximum-buttons-per-row").value){ + instance.addComponent({id:new api.ODId("opendiscord:new-row"),component:"\n"}) + rowButtonCount = 0 + } 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})) + + rowButtonCount++ } } }) diff --git a/src/commands/panel.ts b/src/commands/panel.ts index 8b9f9b6..434b62b 100644 --- a/src/commands/panel.ts +++ b/src/commands/panel.ts @@ -20,12 +20,6 @@ export async function registerCommandResponders(){ const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) if (!isInGuild || !guild || channel.isDMBased()) return cancel() - - const ticket = await openticketUtils.replyIsTicket(instance,origin) - if (!ticket) return cancel() - - const isAvailable = await openticketUtils.replyTicketIsAvailable(instance,origin,ticket) - if (!isAvailable) return cancel() //fetch data const id = instance.options.getString("id",true) @@ -111,4 +105,196 @@ export async function registerButtonResponders(){ ]) }) ) +} + +export async function registerDropdownResponders(){ + opendiscord.responders.dropdowns.add(new api.ODDropdownResponder("opendiscord:panel-dropdown",/^od:panel-dropdown/)) + + //TICKET DROPDOWN RESPONDER + opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:dropdown-ticket",2,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //RETURN WHEN NOT OF TYPE TICKET (role or sub-panel responders might catch it) + const match = /^od:ticket-option\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return + 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.ODTicketOption)){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return cancel() + } + + //start ticket creation + if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ + //SEND MODAL + instance.modal(await opendiscord.components.modals.get("opendiscord:ticket-questions").build("panel-dropdown",{guild,channel,user,option})) + }else{ + //check ticket permissions (modals need check after submit) + if (!(await openticketUtils.checkTicketCreationPerms(instance,"panel-dropdown",guild,user,option))) return cancel() + + //CREATE TICKET + await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) + + const res = await opendiscord.actions.get("opendiscord:create-ticket").run("panel-dropdown",{guild,user,answers:[],option}) + if (!res.channel || !res.ticket){ + //error + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) + return cancel() + } + if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-dropdown",{guild,channel:res.channel,user,ticket:res.ticket})) + } + + //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,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 + isSubPanel:state.data.isSubPanel //same value + },panelMessage.flags.has("Ephemeral")) + } + }) + ) + + //ROLE DROPDOWN RESPONDER + opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:dropdown-role",1,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //RETURN WHEN NOT OF TYPE ROLE (ticket or sub-panel responders might catch it) + const match = /^od:role-option\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return + 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.ODRoleOption)){ + //error + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-option-unknown").build(origin,{guild:instance.guild,channel:instance.channel,user:instance.user})) + return cancel() + } + + //reaction role + await instance.defer(generalConfig.data.ticketSystem.replyOnReactionRole ? "reply" : "update",true) + const res = await opendiscord.actions.get("opendiscord:reaction-role").run("panel-button",{guild,user,option,overwriteMode:null}) + if (!res.result || !res.role){ + //error + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive role update data from worker!",layout:"advanced"})) + return cancel() + } + if (generalConfig.data.ticketSystem.replyOnReactionRole) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:reaction-role").build("panel-button",{guild,user,role:res.role,result:res.result})) + + //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,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 + isSubPanel:state.data.isSubPanel //same value + },panelMessage.flags.has("Ephemeral")) + } + }) + ) + + //SUB-PANEL DROPDOWN RESPONDER + opendiscord.responders.dropdowns.get("opendiscord:panel-dropdown").workers.add( + new api.ODWorker("opendiscord:dropdown-subpanel",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,message} = instance + + //RETURN WHEN NOT OF TYPE SUB-PANEL (ticket or role responders might catch it) + const match = /^od:subpanel-option\|([^|]+)/.exec(instance.values.getStringValues()[0]) + if (!match) return + 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} + ]) + + //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,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 + isSubPanel:state.data.isSubPanel //same value + },panelMessage.flags.has("Ephemeral")) + } + }) + ) } \ No newline at end of file diff --git a/src/commands/role.ts b/src/commands/role.ts index d626ed8..f0cc7b7 100644 --- a/src/commands/role.ts +++ b/src/commands/role.ts @@ -5,17 +5,26 @@ import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const generalConfig = opendiscord.configs.get("opendiscord:general") +const panelMsgState = opendiscord.states.get("opendiscord:panel-message") export async function registerButtonResponders(){ //ROLE OPTION BUTTON RESPONDER 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 {guild,channel,user,message} = instance const match = /^od:role-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) @@ -30,7 +39,7 @@ export async function registerButtonResponders(){ } //reaction role - await instance.defer("reply",true) + await instance.defer(generalConfig.data.ticketSystem.replyOnReactionRole ? "reply" : "update",true) const res = await opendiscord.actions.get("opendiscord:reaction-role").run("panel-button",{guild,user,option,overwriteMode:null}) if (!res.result || !res.role){ //error diff --git a/src/commands/ticket.ts b/src/commands/ticket.ts index 2b69c46..de83f8c 100644 --- a/src/commands/ticket.ts +++ b/src/commands/ticket.ts @@ -8,24 +8,6 @@ const generalConfig = opendiscord.configs.get("opendiscord:general") const lang = opendiscord.languages const panelMsgState = opendiscord.states.get("opendiscord:panel-message") -async function checkTicketCreationPerms(instance:api.ODButtonResponderInstance|api.ODDropdownResponderInstance|api.ODModalResponderInstance|api.ODCommandResponderInstance,origin:api.ODActionManagerIdMappings["opendiscord:create-ticket-permissions"]["origin"],guild:discord.Guild,user:discord.User,option:api.ODTicketOption){ - //check ticket permissions - const permsRes = await opendiscord.actions.get("opendiscord:create-ticket-permissions").run(origin,{guild,user,option}) - if (!permsRes.valid && instance.channel){ - //error - const newOrigin = (origin === "slash" || origin === "text") ? origin : "other" - if (permsRes.reason == "blacklist") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-blacklisted").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user})) - else if (permsRes.reason == "cooldown") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-cooldown").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,until:permsRes.cooldownUntil})) - else if (permsRes.reason == "global-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global"})) - else if (permsRes.reason == "global-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"global-user"})) - else if (permsRes.reason == "option-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option"})) - else if (permsRes.reason == "option-user-limit") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions-limits").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,limit:"option-user"})) - else if (permsRes.reason == "custom") await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,layout:"simple",error:permsRes.customReason ?? lang.getTranslation("errors.descriptions.unableToCreateTicket")+" `Unknown invalid_permission_reason => no reason specified by plugin`",customTitle:lang.getTranslation("errors.titles.permissionError")})) - else await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(newOrigin,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Unknown invalid_permission reason => calculation failed #1",layout:"advanced"})) - return false - }else return true -} - export async function registerCommandResponders(){ //TICKET COMMAND RESPONDER opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:ticket",generalConfig.data.prefix,/^ticket/)) @@ -54,7 +36,7 @@ export async function registerCommandResponders(){ instance.modal(await opendiscord.components.modals.get("opendiscord:ticket-questions").build(origin,{guild,channel,user,option})) }else{ //check ticket permissions (modals need check after submit) - if (!(await checkTicketCreationPerms(instance,origin,guild,user,option))) return cancel() + if (!(await openticketUtils.checkTicketCreationPerms(instance,origin,guild,user,option))) return cancel() //CREATE TICKET await instance.defer(true) @@ -114,7 +96,7 @@ export async function registerButtonResponders(){ instance.modal(await opendiscord.components.modals.get("opendiscord:ticket-questions").build("panel-button",{guild,channel,user,option})) }else{ //check ticket permissions (modals need check after submit) - if (!(await checkTicketCreationPerms(instance,"panel-button",guild,user,option))) return cancel() + if (!(await openticketUtils.checkTicketCreationPerms(instance,"panel-button",guild,user,option))) return cancel() //CREATE TICKET await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) @@ -131,71 +113,6 @@ 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.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 - - //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 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})) - return cancel() - } - - //start ticket creation - if (option.exists("opendiscord:questions") && option.get("opendiscord:questions").value.length > 0){ - //SEND MODAL - instance.modal(await opendiscord.components.modals.get("opendiscord:ticket-questions").build("panel-dropdown",{guild,channel,user,option})) - }else{ - //check ticket permissions (modals need check after submit) - if (!(await checkTicketCreationPerms(instance,"panel-dropdown",guild,user,option))) return cancel() - - //CREATE TICKET - await instance.defer((generalConfig.data.ticketSystem.replyOnTicketCreation) ? "reply" : "update",true) - - const res = await opendiscord.actions.get("opendiscord:create-ticket").run("panel-dropdown",{guild,user,answers:[],option}) - if (!res.channel || !res.ticket){ - //error - await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(origin,{guild,channel:instance.channel,user,error:"Unable to receive ticket or channel from callback! #1",layout:"advanced"})) - return cancel() - } - if (generalConfig.data.ticketSystem.replyOnTicketCreation) await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:ticket-created").build("panel-dropdown",{guild,channel:res.channel,user,ticket:res.ticket})) - } - - //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,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 - isSubPanel:state.data.isSubPanel //same value - },panelMessage.flags.has("Ephemeral")) - } - }) - ) -} - export async function registerModalResponders(){ //TICKET QUESTIONS RESPONDER opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:ticket-questions",/^od:ticket-questions\|([^|]+)\|([^|]+)/)) @@ -220,7 +137,7 @@ export async function registerModalResponders(){ } //check ticket permissions (modals need check after submit) - if (!(await checkTicketCreationPerms(instance,originalOrigin,guild,user,option))) return cancel() + if (!(await openticketUtils.checkTicketCreationPerms(instance,originalOrigin,guild,user,option))) return cancel() //get answers const answers: api.ODQuestionAnswer[] = [] diff --git a/src/core/api/panel.ts b/src/core/api/panel.ts index 8f5886b..2180f54 100644 --- a/src/core/api/panel.ts +++ b/src/core/api/panel.ts @@ -23,6 +23,7 @@ export interface ODPanelIdMappings extends ODPanelIdConstraint { "opendiscord:embed":ODPanelData, "opendiscord:dropdown-placeholder":ODPanelData, + "opendiscord:maximum-buttons-per-row":ODPanelData, "opendiscord:enable-max-tickets-warning-text":ODPanelData, "opendiscord:enable-max-tickets-warning-embed":ODPanelData, diff --git a/src/core/mappings/builder.ts b/src/core/mappings/builder.ts index a65bd3f..0d409d1 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, ODSubPanelOption, ODTicketOption, ODWebsiteOption } from "../api/option.js" +import { ODOption, 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" @@ -52,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"|"sub-panel"|"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":{origin:"slash"|"text"|"sub-panel"|"auto-update"|"other",params:{guild:discord.Guild,channel:discord.TextBasedChannel,user:discord.User,panel:ODPanel,options:(ODTicketOption|ODRoleOption|ODSubPanelOption)[]},workers:"opendiscord:panel-dropdown"} } /**## ODFileManagerIdMappings `interface` diff --git a/src/core/mappings/responder.ts b/src/core/mappings/responder.ts index 84d91a8..82c88b3 100644 --- a/src/core/mappings/responder.ts +++ b/src/core/mappings/responder.ts @@ -68,7 +68,7 @@ export interface ODButtonResponderManagerIdMappings extends api.ODButtonResponde * It's used to generate typescript declarations for this class. */ export interface ODDropdownResponderManagerIdMappings extends api.ODDropdownResponderManagerIdConstraint { - "opendiscord:panel-dropdown-tickets":{origin:"dropdown",params:{},workers:"opendiscord:panel-dropdown-tickets"}, + "opendiscord:panel-dropdown":{origin:"dropdown",params:{},workers:"opendiscord:dropdown-ticket"|"opendiscord:dropdown-role"|"opendiscord:dropdown-subpanel"}, } /**## ODModalResponderManagerIdMappings `interface` diff --git a/src/data/framework/checkerLoader.ts b/src/data/framework/checkerLoader.ts index a0b1237..acd3b4a 100644 --- a/src/data/framework/checkerLoader.ts +++ b/src/data/framework/checkerLoader.ts @@ -123,7 +123,8 @@ export function registerDefaultCheckerCustomTranslations(tm:api.ODMappedCheckerT tm.quickTranslate(lm,"checker.messages.invalidButton","message","opendiscord:invalid-button") // This button needs to have at least an {0} or {1}! tm.quickTranslate(lm,"checker.messages.unusedOption","message","opendiscord:unused-option") // The option {0} isn't used anywhere! tm.quickTranslate(lm,"checker.messages.unusedQuestion","message","opendiscord:unused-question") // The question {0} isn't used anywhere! - tm.quickTranslate(lm,"checker.messages.dropdownOption","message","opendiscord:dropdown-option") // A panel with dropdown enabled can only contain options of the 'ticket' type! + //TODO TRANSLATION!!! (changed) + //tm.quickTranslate(lm,"checker.messages.dropdownOption","message","opendiscord:dropdown-option") // A panel with dropdown can only contain options of the types: 'ticket', 'role' or 'sub-panel'. tm.quickTranslate(lm,"checker.messages.customInvalidVersion","message","opendiscord:invalid-version") // The version specified in your config does not match! Make sure you have updated the config to the latest version! } @@ -828,11 +829,11 @@ export const defaultDropdownOptionsFunction = (manager:api.ODCheckerManager, fun if (panel.options.some((optId) => { const option = optionConfig.data.find((option) => option.id == optId) if (!option) return false - if (option.type != "ticket") return true + if (option.type != "ticket" && option.type != "role" && option.type != "sub-panel") return true else return false })){ //give error when non-ticket options exist in dropdown panel! - final.push(functions.createMessage("opendiscord:panels","opendiscord:dropdown-option",panelConfig.file,"error","A panel with dropdown enabled can only contain options of the 'ticket' type!",[index,"options"],null,[],new api.ODId("opendiscord:dropdown-options"),null)) + final.push(functions.createMessage("opendiscord:panels","opendiscord:dropdown-option",panelConfig.file,"error","A panel with dropdown can only contain options of the types: 'ticket', 'role' or 'sub-panel'.",[index,"options"],null,[],new api.ODId("opendiscord:dropdown-options"),null)) } }) diff --git a/src/data/openticket/panelLoader.ts b/src/data/openticket/panelLoader.ts index df9b150..2b9315f 100644 --- a/src/data/openticket/panelLoader.ts +++ b/src/data/openticket/panelLoader.ts @@ -33,8 +33,11 @@ export const loadPanel = (panel:api.ODPanelsJsonConfig_Panel) => { new api.ODPanelData("opendiscord:embed",panel.embed), new api.ODPanelData("opendiscord:dropdown-placeholder",panel.settings.dropdownPlaceholder), + new api.ODPanelData("opendiscord:maximum-buttons-per-row",panel.settings.maximumButtonsPerRow), + new api.ODPanelData("opendiscord:enable-max-tickets-warning-text",panel.settings.enableMaxTicketsWarningInText), new api.ODPanelData("opendiscord:enable-max-tickets-warning-embed",panel.settings.enableMaxTicketsWarningInEmbed), + new api.ODPanelData("opendiscord:describe-options-layout",panel.settings.describeOptionsLayout), new api.ODPanelData("opendiscord:describe-options-custom-title",panel.settings.describeOptionsCustomTitle), new api.ODPanelData("opendiscord:describe-options-in-text",panel.settings.describeOptionsInText), diff --git a/src/index.ts b/src/index.ts index 10c8b1a..24b67dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -743,7 +743,7 @@ const main = async () => { //load dropdown responders opendiscord.log("Loading dropdown responders...","system") if (opendiscord.sharedFuses.getFuse("dropdownRespondersLoading")){ - await (await import("./commands/ticket.js")).registerDropdownResponders() + await (await import("./commands/panel.js")).registerDropdownResponders() } await opendiscord.events.get("onDropdownResponderLoad").emit([opendiscord.responders.dropdowns,opendiscord.responders,opendiscord.actions]) await opendiscord.events.get("afterDropdownRespondersLoaded").emit([opendiscord.responders.dropdowns,opendiscord.responders,opendiscord.actions])