(v4.2.0) Panel auto-update now uses message states

This commit is contained in:
DJj123dj
2026-05-23 14:32:22 +02:00
parent 4af9b9e17f
commit c442e50cb4
6 changed files with 123 additions and 74 deletions
+7 -9
View File
@@ -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",[
+34 -10
View File
@@ -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,7 +133,15 @@ 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)
@@ -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
+19
View File
@@ -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,
}
/////////////////////////////
@@ -60,3 +61,21 @@ export class ODClearMessageState extends api.ODState<{
})
}
}
/**## 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,{})
}
}
+30
View File
@@ -339,8 +339,10 @@ 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
@@ -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)
}
})
}
}),
]
+30 -53
View File
@@ -69,38 +69,6 @@ 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
//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"))
message.edit((await opendiscord.builders.messages.getSafe("opendiscord:panel").build("auto-update",{guild:mainServer,channel,user:opendiscord.client.client.user,panel})).message)
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}
])
}
}
}))
}
+1
View File
@@ -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))
}