diff --git a/src/commands/panel.ts b/src/commands/panel.ts index ff5356d..81430bf 100644 --- a/src/commands/panel.ts +++ b/src/commands/panel.ts @@ -5,6 +5,7 @@ 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 registerCommandResponders(){ //PANEL COMMAND RESPONDER @@ -36,15 +37,12 @@ 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) - - //add panel to database (this way, the bot knows where all panels are located) - const globalDatabase = opendiscord.databases.get("opendiscord:global") - await globalDatabase.set("opendiscord:panel-message",panelMessage.channel.id+"_"+panelMessage.id,panel.id.value) - - //add panel to database for auto-update - if (instance.options.getBoolean("auto-update",false)){ - await globalDatabase.set("opendiscord:panel-update",panelMessage.channel.id+"_"+panelMessage.id,panel.id.value) - } + 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) + },panelMessage.flags.has("Ephemeral")) }), new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { opendiscord.log(instance.user.displayName+" used the 'panel' command!","info",[ diff --git a/src/commands/ticket.ts b/src/commands/ticket.ts index 19f4192..52d643d 100644 --- a/src/commands/ticket.ts +++ b/src/commands/ticket.ts @@ -6,6 +6,7 @@ import * as discord from "discord.js" 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 @@ -82,7 +83,15 @@ export async function registerButtonResponders(){ 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} = instance + 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) @@ -124,8 +133,16 @@ export async function registerDropdownResponders(){ 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} = instance + 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() @@ -159,11 +176,15 @@ export async function registerDropdownResponders(){ } //update panel after dropdown usage (reset panel choice) - const globalDatabase = opendiscord.databases.get("opendiscord:global") - const rawPanelId = await globalDatabase.get("opendiscord:panel-message",instance.message.channel.id+"_"+instance.message.id) - if (rawPanelId){ - const panel = opendiscord.panels.get(rawPanelId) - if (panel) await instance.message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild,channel,user,panel})).message) + 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) + 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 + },panelMessage.flags.has("Ephemeral")) } }) ) @@ -171,17 +192,21 @@ export async function registerDropdownResponders(){ export async function registerModalResponders(){ //TICKET QUESTIONS RESPONDER - opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:ticket-questions",/^od:ticket-questions_/)) + opendiscord.responders.modals.add(new api.ODModalResponder("opendiscord:ticket-questions",/^od:ticket-questions\|([^|]+)\|([^|]+)/)) opendiscord.responders.modals.get("opendiscord:ticket-questions").workers.add([ new api.ODWorker("opendiscord:ticket-questions",0,async (instance,params,origin,cancel) => { const {guild,channel,user} = instance + + const match = /^od:ticket-questions\|([^|]+)\|([^|]+)/.exec(instance.interaction.customId) + if (!match) return cancel() + const optionId = match[1] + const originalOrigin = match[2] as ("panel-button"|"panel-dropdown"|"slash"|"text"|"other") //responder checks const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) if (!isInGuild || !guild || !channel || channel.isDMBased()) return cancel() //get option data - const optionId = instance.interaction.customId.split("_")[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,user:instance.user})) @@ -189,7 +214,6 @@ export async function registerModalResponders(){ } //check ticket permissions (modals need check after submit) - const originalOrigin = instance.interaction.customId.split("_")[2] as ("panel-button"|"panel-dropdown"|"slash"|"text"|"other") if (!(await checkTicketCreationPerms(instance,originalOrigin,guild,user,option))) return cancel() //get answers diff --git a/src/core/mappings/state.ts b/src/core/mappings/state.ts index 9136c41..a5ce304 100644 --- a/src/core/mappings/state.ts +++ b/src/core/mappings/state.ts @@ -12,6 +12,7 @@ import { ODTicketClearFilter } from "../api/ticket.js" export interface ODStateManagerIdMappings extends api.ODStateManagerIdConstraint { "opendiscord:interactive-message":ODInteractiveMessageState, "opendiscord:clear-message":ODClearMessageState, + "opendiscord:panel-message":ODPanelMessageState, } ///////////////////////////// @@ -59,4 +60,22 @@ export class ODClearMessageState extends api.ODState<{ autodeleteOnRestart:true }) } +} + +/**## ODPanelMessageState `class + * A special class with state types for the Open Ticket panel message. + */ +export class ODPanelMessageState extends api.ODState<{ + /**The method this message was generated with. */ + messageOrigin:"slash"|"text"|"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 +},false,false> { + constructor(id:api.ODValidId,client:api.ODClientManager,database:api.ODDatabase){ + super(id,client,database,{}) + } } \ No newline at end of file diff --git a/src/core/startup/migration.ts b/src/core/startup/migration.ts index 3108fa6..4d1b9bf 100644 --- a/src/core/startup/migration.ts +++ b/src/core/startup/migration.ts @@ -339,9 +339,11 @@ export const migrations = [ } //migrate database + const globalDatabase = opendiscord.databases.get("opendiscord:global") const optionDatabase = opendiscord.databases.get("opendiscord:options") const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") - + const stateDatabase = opendiscord.databases.get("opendiscord:message-states") + for (const option of (await optionDatabase.getCategory("opendiscord:used-option") ?? [])){ const optionData = option.value optionData.data = optionData.data.filter((data) => ( @@ -359,6 +361,34 @@ export const migrations = [ ticketDatabase.set("opendiscord:ticket",ticket.key,ticketData) } + + //migrate old panel messages from global.json to the new message states + const migratedPanelStates: {channelId:string,messageId:string,panelId:string,autoUpdate:boolean}[] = [] + opendiscord.events.get("afterClientReady").listen(async () => { + for (const panelMessage of (await globalDatabase.getCategory("opendiscord:panel-message") ?? [])){ + const splittedId = panelMessage.key.split("_") + const channelId = splittedId[0] + const messageId = splittedId[1] + const message = await opendiscord.client.fetchChannelMessage(channelId,messageId) + const autoUpdate = typeof (await globalDatabase.get("opendiscord:panel-update",panelMessage.key)) == "string" + //if message still exists + if (message) migratedPanelStates.push({channelId,messageId,panelId:panelMessage.value,autoUpdate}) + + await globalDatabase.delete("opendiscord:panel-message",panelMessage.key) + await globalDatabase.delete("opendiscord:panel-update",panelMessage.key) + } + }) + opendiscord.events.get("afterStatesInitiated").listen(async (states) => { + const panelMsgState = 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 + },false) + } + }) } }), ] \ No newline at end of file diff --git a/src/data/framework/codeLoader.ts b/src/data/framework/codeLoader.ts index af57c14..0bab5a4 100644 --- a/src/data/framework/codeLoader.ts +++ b/src/data/framework/codeLoader.ts @@ -68,39 +68,7 @@ export async function loadStartListeningInteractionsCode(){ export async function loadDatabaseCleanersCode(){ if (!mainServer) return - - //PANEL DATABASE CLEANER - opendiscord.code.add(new api.ODCode("opendiscord:panel-database-cleaner",12,async () => { - const validPanels: string[] = [] - - //check global database for valid panel embeds - for (const panel of (await globalDatabase.getCategory("opendiscord:panel-message") ?? [])){ - if (!validPanels.includes(panel.key)){ - try{ - const splittedId = panel.key.split("_") - const message = await opendiscord.client.fetchChannelMessage(splittedId[0],splittedId[1]) - if (message) validPanels.push(panel.key) - }catch{} - } - } - - //remove all unused panels - for (const panel of (await globalDatabase.getCategory("opendiscord:panel-message") ?? [])){ - if (!validPanels.includes(panel.key)){ - await globalDatabase.delete("opendiscord:panel-message",panel.key) - await globalDatabase.delete("opendiscord:panel-update",panel.key) - } - } - - //delete panel from database on delete - opendiscord.client.client.on("messageDelete",async (msg) => { - if (await globalDatabase.exists("opendiscord:panel-message",msg.channel.id+"_"+msg.id)){ - await globalDatabase.delete("opendiscord:panel-message",msg.channel.id+"_"+msg.id) - await globalDatabase.delete("opendiscord:panel-update",msg.channel.id+"_"+msg.id) - } - }) - })) - + //SUFFIX DATABASE CLEANER opendiscord.code.add(new api.ODCode("opendiscord:suffix-database-cleaner",11,async () => { const validSuffixCounters: string[] = [] @@ -284,34 +252,43 @@ export async function loadDatabaseCleanersCode(){ export async function loadPanelAutoUpdateCode(){ //PANEL AUTO UPDATE + const panelMsgState = opendiscord.states.get("opendiscord:panel-message") opendiscord.code.add(new api.ODCode("opendiscord:panel-auto-update",7,async () => { - const globalDatabase = opendiscord.databases.get("opendiscord:global") - const panelIds = await globalDatabase.getCategory("opendiscord:panel-update") ?? [] if (!mainServer) return - for (const panelId of panelIds){ - const panel = opendiscord.panels.get(panelId.value) - - //panel doesn't exist anymore in config and needs to be removed - if (!panel){ - globalDatabase.delete("opendiscord:panel-update",panelId.key) - return - } - + for (const panelState of (await panelMsgState.listMsgStates()).map((rawState) => rawState.value)){ try{ - const splittedId = panelId.key.split("_") - const channel = await opendiscord.client.fetchGuildTextChannel(mainServer,splittedId[0]) - if (!channel) return - const message = await opendiscord.client.fetchChannelMessage(channel,splittedId[1]) - if (!message || !message.editable) return + //fetch panel (& check if auto-update is required) + const panel = opendiscord.panels.get(panelState.data.panelId) + if (!panel || !panelState.data.panelAutoUpdate) continue - message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel,user:opendiscord.client.client.user,panel})).message) + //fetch panel channel + const channel = await opendiscord.client.fetchGuildTextChannel(mainServer,panelState.channelId) + if (!channel) continue + + //fetch panel message + 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) + if (panelMessage) await panelMsgState.setMsgState({channel,message:panelMessage},{ + messageOrigin:"auto-update", + panelId:panel.id.value, + panelOptionIds:panel.get("opendiscord:options").value, + panelAutoUpdate:true + },panelMessage.flags.has("Ephemeral")) + opendiscord.log("Panel in server got auto-updated!","info",[ - {key:"channelid",value:splittedId[0]}, - {key:"messageid",value:splittedId[1]}, - {key:"panel",value:panelId.value} + {key:"channelid",value:panelState.channelId}, + {key:"messageid",value:panelState.messageId}, + {key:"panel",value:panel.id.value} ]) - }catch{} + }catch{ + opendiscord.log("Failed to auto-update panel","error",[ + {key:"channelid",value:panelState.channelId}, + {key:"messageid",value:panelState.messageId} + ]) + } } })) } diff --git a/src/data/framework/stateLoader.ts b/src/data/framework/stateLoader.ts index 7654feb..15167f5 100644 --- a/src/data/framework/stateLoader.ts +++ b/src/data/framework/stateLoader.ts @@ -5,4 +5,5 @@ export async function loadAllStates(){ opendiscord.states.add(new api.ODInteractiveMessageState("opendiscord:interactive-message",opendiscord.client,stateDatabase)) opendiscord.states.add(new api.ODClearMessageState("opendiscord:clear-message",opendiscord.client,stateDatabase)) + opendiscord.states.add(new api.ODPanelMessageState("opendiscord:panel-message",opendiscord.client,stateDatabase)) } \ No newline at end of file