diff --git a/package.json b/package.json index 0c2b077..1c47527 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "license": "GPL-3.0-only", "dependencies": { "@discordjs/rest": "^2.6.1", - "@open-discord-bots/framework": "^0.5.3", + "@open-discord-bots/framework": "^0.5.4", "@types/node": "^22.5.0", "@types/terminal-kit": "^2.5.7", "ansis": "^4.2.0", diff --git a/src/actions/createTranscript.ts b/src/actions/createTranscript.ts index 2bbcb3f..6eac994 100644 --- a/src/actions/createTranscript.ts +++ b/src/actions/createTranscript.ts @@ -5,6 +5,7 @@ import {opendiscord, api, utilities, openticketUtils} from "../index.js" import * as discord from "discord.js" const transcriptConfig = opendiscord.configs.get("opendiscord:transcripts") +const transcriptDatabase = opendiscord.databases.get("opendiscord:transcripts") export async function registerActions(){ opendiscord.actions.add(new api.ODAction("opendiscord:create-transcript")) @@ -127,11 +128,24 @@ export async function registerActions(){ cancel() throw new api.ODSystemError("ODAction(ot:create-transcript):ODWorker(ot:ready-transcript) => Instance is missing transcript result! (2)") } + const result = instance.result as api.ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}|{contents:string}> - await opendiscord.events.get("onTranscriptReady").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user]) + const historyData: api.ODTranscriptHistoryData = { + ticketId:result.channel.id, + ticketName:"#"+result.channel.name, + ticketCreatorId:result.user.id, + ticketCreatedDate:result.ticket.get("opendiscord:opened-on").value, + ticketDeletedDate:Date.now(), + transcriptType:(result.data && "contents" in result.data) ? "localContents" : "remoteUrl", + transcriptContents:(result.data && "contents" in result.data) ? result.data.contents : null, + transcriptUrl:(result.data && "url" in result.data) ? result.data.url : null, + } + transcriptDatabase.set("opendiscord:transcript","C:"+result.channel.id+",U:"+result.user.id,historyData) + + await opendiscord.events.get("onTranscriptReady").emit([opendiscord.transcripts,result.ticket,result.channel,result.user]) if (instance.compiler.ready){ try{ - const {channelMessage,creatorDmMessage,participantDmMessage,activeAdminDmMessage,everyAdminDmMessage} = await instance.compiler.ready(instance.result) + const {channelMessage,creatorDmMessage,participantDmMessage,activeAdminDmMessage,everyAdminDmMessage} = await instance.compiler.ready(result) //send channel message if (transcriptConfig.data.general.enableChannel && channelMessage){ @@ -154,7 +168,7 @@ export async function registerActions(){ }else if (p.role == "participant" && transcriptConfig.data.general.enableParticipantDM && participantDmMessage){ //send participant dm message await opendiscord.client.sendUserDm(p.user,participantDmMessage) - }else if (p.role == "admin" && transcriptConfig.data.general.enableActiveAdminDM && instance.result.success && instance.result.messages && instance.result.messages.some((msg) => msg.author.id == p.user.id) && activeAdminDmMessage){ + }else if (p.role == "admin" && transcriptConfig.data.general.enableActiveAdminDM && result.success && result.messages && result.messages.some((msg) => msg.author.id == p.user.id) && activeAdminDmMessage){ //send active admin dm message await opendiscord.client.sendUserDm(p.user,activeAdminDmMessage) }else if (p.role == "admin" && transcriptConfig.data.general.enableEveryAdminDM && everyAdminDmMessage){ @@ -171,7 +185,7 @@ export async function registerActions(){ throw new api.ODSystemError("ODAction(ot:create-transcript) => Failed transcript compiler ready()! (see error above)") } } - await opendiscord.events.get("afterTranscriptReady").emit([opendiscord.transcripts,instance.result.ticket,instance.result.channel,instance.result.user]) + await opendiscord.events.get("afterTranscriptReady").emit([opendiscord.transcripts,result.ticket,result.channel,result.user]) }) //update stats diff --git a/src/builders/embeds.ts b/src/builders/embeds.ts index 1693999..ca3eb1b 100644 --- a/src/builders/embeds.ts +++ b/src/builders/embeds.ts @@ -1131,6 +1131,27 @@ const transcriptEmbeds = () => { if (reason || generalConfig.data.ticketSystem.alwaysShowReason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+(reason ?? "/")+"```"}) }) ) + + //TRANSCRIPT HISTORY + embeds.add(new api.ODEmbed("opendiscord:transcript-history")) + embeds.get("opendiscord:transcript-history").workers.add( + new api.ODWorker("opendiscord:transcript-history",0,async (instance,params,origin) => { + const {transcriptUser,transcriptList} = params + + //transcript history only supports URL (html) transcripts at the moment + const filteredList = transcriptList.filter((t) => t.transcriptType == "remoteUrl") + const renderedList = filteredList.map((t) => "- ["+t.ticketName+"]("+t.transcriptUrl+") ("+discord.time(new Date(t.ticketDeletedDate ?? 0),"R")+")").join("\n") + //TODO TRANSLATION!!! + const contents = (transcriptList.length < 1) ? "This user does not yet have any transcripts." : ((filteredList.length > 0) ? renderedList : "*⚠️ Transcript history is currently only supported with HTML Transcripts.\nText transcript history will be available soon.*") + + instance.setAuthor(transcriptUser.displayName,transcriptUser.displayAvatarURL()) + instance.setThumbnail(transcriptUser.displayAvatarURL()) + instance.setColor(generalConfig.data.mainColor) + //TODO TRANSLATION!!! + instance.setTitle(utilities.emojiTitle("📄","Transcript History")) + instance.setDescription(contents) + }) + ) } const roleEmbeds = () => { diff --git a/src/builders/messages.ts b/src/builders/messages.ts index 4250347..fee0f36 100644 --- a/src/builders/messages.ts +++ b/src/builders/messages.ts @@ -677,6 +677,16 @@ const transcriptMessages = () => { instance.addComponent(await buttons.getSafe("opendiscord:transcript-error-continue").build(origin,{guild,channel,user,ticket,compiler,reason})) }) ) + + //TRANSCRIPT HISTORY + messages.add(new api.ODMessage("opendiscord:transcript-history")) + messages.get("opendiscord:transcript-history").workers.add( + new api.ODWorker("opendiscord:transcript-history",0,async (instance,params,origin) => { + const {guild,channel,user,transcriptUser,transcriptList} = params + instance.addEmbed(await embeds.getSafe("opendiscord:transcript-history").build(origin,{guild,channel,user,transcriptUser,transcriptList})) + instance.setEphemeral(true) + }) + ) } const roleMessages = () => { diff --git a/src/commands/transcripts.ts b/src/commands/transcripts.ts new file mode 100644 index 0000000..23bc821 --- /dev/null +++ b/src/commands/transcripts.ts @@ -0,0 +1,44 @@ +/////////////////////////////////////// +//TRANSCRIPTS COMMAND +/////////////////////////////////////// +import {opendiscord, api, utilities, openticketUtils} from "../index.js" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") +const transcriptsDatabase = opendiscord.databases.get("opendiscord:transcripts") + +export async function registerCommandResponders(){ + //TRANSCRIPTS COMMAND RESPONDER + opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:transcripts",generalConfig.data.prefix,"transcripts")) + opendiscord.responders.commands.get("opendiscord:transcripts").workers.add([ + new api.ODWorker("opendiscord:transcripts",0,async (instance,params,origin,cancel) => { + const {guild,channel,user,member} = instance + + //responder checks + const hasPerms = await openticketUtils.replyHasPermissions(instance,origin,"transcripts") + if (!hasPerms) return cancel() + + const isInGuild = await openticketUtils.replyIsInGuild(instance,origin) + if (!isInGuild || !guild || channel.isDMBased()) return cancel() + + //fetch data + const transcriptUser = instance.options.getUser("user",true) + + const transcriptList = (await transcriptsDatabase.getCategory("opendiscord:transcript") ?? []) + .filter((t) => t.value.ticketCreatorId === transcriptUser.id) + .map((t) => t.value) + .sort((a,b) => (b.ticketDeletedDate ?? 0)-(a.ticketDeletedDate ?? 0)) + .slice(0,20) + + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transcript-history").build(origin,{guild,channel,user,transcriptUser,transcriptList})) + }), + new api.ODWorker("opendiscord:logs",-1,(instance,params,origin,cancel) => { + opendiscord.log(instance.user.displayName+" used the 'transcripts' command!","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/core/api/transcript.ts b/src/core/api/transcript.ts index 9e4615b..4bff58d 100644 --- a/src/core/api/transcript.ts +++ b/src/core/api/transcript.ts @@ -60,6 +60,20 @@ export class ODTranscriptManager,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, "opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, "opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, + "opendiscord:transcript-history":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,transcriptUser:discord.User,transcriptList:ODTranscriptHistoryData[]},workers:"opendiscord:transcript-history"}, "opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, "opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, @@ -210,6 +211,7 @@ export interface ODMessageManagerIdMappings extends api.ODMessageManagerIdConstr "opendiscord:transcript-html-ready":{origin:"channel"|"creator-dm"|"participant-dm"|"active-admin-dm"|"every-admin-dm"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,result:ODTranscriptCompilerCompileResult<{url:string,availableUntil:Date}>},workers:"opendiscord:transcript-html-ready"}, "opendiscord:transcript-html-progress":{origin:"channel"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler<{url:string,availableUntil:Date},{auth:string}>,remaining:number},workers:"opendiscord:transcript-html-progress"}, "opendiscord:transcript-error":{origin:"slash"|"text"|"ticket-message"|"reopen-message"|"close-message"|"autoclose-message"|"autodelete"|"clear"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,compiler:ODTranscriptCompiler,reason:string|null},workers:"opendiscord:transcript-error"}, + "opendiscord:transcript-history":{origin:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,transcriptUser:discord.User,transcriptList:ODTranscriptHistoryData[]},workers:"opendiscord:transcript-history"}, "opendiscord:reaction-role":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role"}, "opendiscord:reaction-role-dm":{origin:"panel-button"|"other",params:{guild:discord.Guild,user:discord.User,role:ODRole,result:ODRoleUpdateResult[]},workers:"opendiscord:reaction-role-dm"}, diff --git a/src/core/mappings/client.ts b/src/core/mappings/client.ts index 4d37d8a..67c48cd 100644 --- a/src/core/mappings/client.ts +++ b/src/core/mappings/client.ts @@ -30,6 +30,7 @@ export interface ODSlashCommandManagerIdMappings extends api.ODSlashCommandManag "opendiscord:topic":api.ODSlashCommand, "opendiscord:priority":api.ODSlashCommand, "opendiscord:transfer":api.ODSlashCommand, + "opendiscord:transcripts":api.ODSlashCommand, } /**## ODTextCommandManagerIdMappings `interface` @@ -68,6 +69,7 @@ export interface ODTextCommandManagerIdMappings extends api.ODTextCommandManager "opendiscord:priority-set":api.ODTextCommand, "opendiscord:priority-get":api.ODTextCommand, "opendiscord:transfer":api.ODTextCommand, + "opendiscord:transcripts":api.ODTextCommand, } /**## ODContextMenuManagerIdMappings `interface` diff --git a/src/core/mappings/database.ts b/src/core/mappings/database.ts index 11ad62d..026a794 100644 --- a/src/core/mappings/database.ts +++ b/src/core/mappings/database.ts @@ -4,6 +4,7 @@ import * as api from "@open-discord-bots/framework/api" import { ODTicketJson } from "../api/ticket.js" import { ODOptionJson } from "../api/option.js" +import { ODTranscriptHistoryData } from "../api/transcript.js" /**## ODDatabaseManagerIdMappings `interface` * A list of all available IDs in the default `ODDatabaseManager` class in `opendiscord`. @@ -15,6 +16,7 @@ export interface ODDatabaseManagerIdMappings extends api.ODDatabaseManagerIdCons "opendiscord:tickets":ODTicketsDatabase, "opendiscord:users":ODUsersDatabase, "opendiscord:options":ODOptionsDatabase, + "opendiscord:transcripts":ODTranscriptsDatabase, "opendiscord:message-states":ODMessageStatesDatabase, } @@ -58,6 +60,15 @@ export interface ODOptionsDatabaseIdMappings extends api.ODDatabaseIdConstraint "opendiscord:used-option":ODOptionJson } +/**## ODTranscriptsDatabaseIdMappings `interface` + * A list of all available IDs in the default `ODTranscriptsDatabase` class in `opendiscord`. + * It's used to generate typescript declarations for this class. + */ +export interface ODTranscriptsDatabaseIdMappings extends api.ODDatabaseIdConstraint { + "opendiscord:transcript":ODTranscriptHistoryData, +} + + ///////////////////////////// ////// MAPPED MANAGERS ////// ///////////////////////////// @@ -92,6 +103,11 @@ export class ODUsersDatabase extends api.ODFormattedJsonDatabase {} +/**## ODTranscriptsDatabase `class + * A special class with types for the Open Ticket `database/transcripts.json` database file + */ +export class ODTranscriptsDatabase extends api.ODFormattedJsonDatabase {} + /**## ODMessageStatesDatabase `class * A special class with types for the Open Ticket `database/states.json` database file */ diff --git a/src/core/mappings/helpmenu.ts b/src/core/mappings/helpmenu.ts index 1b8ddfc..d8316df 100644 --- a/src/core/mappings/helpmenu.ts +++ b/src/core/mappings/helpmenu.ts @@ -90,6 +90,7 @@ export interface ODAdvancedHelpMenuCategoryIdMappings extends api.ODHelpMenuCate "opendiscord:autodelete-enable":api.ODHelpMenuCommandComponent, "opendiscord:topic-set":api.ODHelpMenuCommandComponent, "opendiscord:priority-set":api.ODHelpMenuCommandComponent, + "opendiscord:transcripts":api.ODHelpMenuCommandComponent, } /**## ODExtraHelpMenuCategoryIdMappings `interface` diff --git a/src/core/mappings/responder.ts b/src/core/mappings/responder.ts index dc758fb..0b5832b 100644 --- a/src/core/mappings/responder.ts +++ b/src/core/mappings/responder.ts @@ -30,6 +30,7 @@ export interface ODCommandResponderManagerIdMappings extends api.ODCommandRespon "opendiscord:topic":{origin:"slash"|"text",params:{},workers:"opendiscord:topic"|"opendiscord:logs"}, "opendiscord:priority":{origin:"slash"|"text",params:{},workers:"opendiscord:priority"|"opendiscord:logs"}, "opendiscord:transfer":{origin:"slash"|"text",params:{},workers:"opendiscord:transfer"|"opendiscord:logs"}, + "opendiscord:transcripts":{origin:"slash"|"text",params:{},workers:"opendiscord:transcripts"|"opendiscord:logs"}, "opendiscord:autoclose":{origin:"slash"|"text",params:{},workers:"opendiscord:autoclose"|"opendiscord:logs"}, "opendiscord:autodelete":{origin:"slash"|"text",params:{},workers:"opendiscord:autodelete"|"opendiscord:logs"}, diff --git a/src/core/mappings/task.ts b/src/core/mappings/task.ts index 7837db9..ab9d79e 100644 --- a/src/core/mappings/task.ts +++ b/src/core/mappings/task.ts @@ -15,6 +15,7 @@ export interface ODTaskManagerIdMappings extends api.ODTaskManagerIdConstraint { "opendiscord:option-database-cleaner":api.ODTask, "opendiscord:user-database-cleaner":api.ODTask, "opendiscord:ticket-database-cleaner":api.ODTask, + "opendiscord:transcript-database-cleaner":api.ODTask, "opendiscord:panel-auto-update":api.ODTask, "opendiscord:ticket-saver":api.ODTask, "opendiscord:blacklist-saver":api.ODTask, diff --git a/src/data/framework/commandLoader.ts b/src/data/framework/commandLoader.ts index 161a33d..fbae27d 100644 --- a/src/data/framework/commandLoader.ts +++ b/src/data/framework/commandLoader.ts @@ -637,6 +637,25 @@ export async function loadAllSlashCommands(){ } ] })) + + //TRANSCRIPTS + if (allowedCommands.includes("transcripts")) commands.add(new api.ODSlashCommand("opendiscord:transcripts",{ + type:act.ChatInput, + name:"transcripts", + //TODO TRANSLATION!!! + description:"View ticket transcript history of a user.", + contexts:[discord.InteractionContextType.Guild], + integrationTypes:[discord.ApplicationIntegrationType.GuildInstall], + options:[ + { + name:"user", + //TODO TRANSLATION!!! + description:"The user to view.", + type:acot.User, + required:true + } + ] + })) } export async function loadAllTextCommands(){ @@ -1211,6 +1230,22 @@ export async function loadAllTextCommands(){ } ] })) + + //TRANSCRIPTS + if (allowedCommands.includes("transcripts")) commands.add(new api.ODTextCommand("opendiscord:transcripts",{ + name:"transcripts", + prefix, + dmPermission:false, + guildPermission:true, + allowBots:false, + options:[ + { + name:"user", + type:"user", + required:true + } + ] + })) } export async function loadAllContextMenus(){ diff --git a/src/data/framework/databaseLoader.ts b/src/data/framework/databaseLoader.ts index 1f01732..0f46a55 100644 --- a/src/data/framework/databaseLoader.ts +++ b/src/data/framework/databaseLoader.ts @@ -10,6 +10,7 @@ export async function loadAllDatabases(){ opendiscord.databases.add(defaultTicketsDatabase) opendiscord.databases.add(defaultUsersDatabase) opendiscord.databases.add(defaultOptionsDatabase) + opendiscord.databases.add(defaultTranscriptsDatabase) opendiscord.databases.add(defaultMessageStatesDatabase) } @@ -52,4 +53,5 @@ export const defaultStatsDatabase = new api.ODStatsDatabase("opendiscord:stats", export const defaultTicketsDatabase = new api.ODTicketsDatabase("opendiscord:tickets","tickets.json",defaultTicketFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") export const defaultUsersDatabase = new api.ODUsersDatabase("opendiscord:users","users.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") export const defaultOptionsDatabase = new api.ODOptionsDatabase("opendiscord:options","options.json",defaultOptionFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") +export const defaultTranscriptsDatabase = new api.ODTranscriptsDatabase("opendiscord:transcripts","transcripts.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") export const defaultMessageStatesDatabase = new api.ODMessageStatesDatabase("opendiscord:message-states","states.json",defaultInlineFormatter,(isDevdatabase) ? "./devdatabase/" : "./database/") \ No newline at end of file diff --git a/src/data/framework/helpMenuLoader.ts b/src/data/framework/helpMenuLoader.ts index ab29a72..afc605c 100644 --- a/src/data/framework/helpMenuLoader.ts +++ b/src/data/framework/helpMenuLoader.ts @@ -283,5 +283,13 @@ export async function loadAllHelpMenuComponents(){ textOptions:[{name:"priority",optional:false},{name:"reason",optional:true}], slashOptions:[{name:"priority",optional:false},{name:"reason",optional:true}] })) + if (allowedCommands.includes("transcripts")) advanced.add(new api.ODHelpMenuCommandComponent("opendiscord:transcripts",0,{ + textName:prefix+"transcripts", + textDescription:"View ticket transcript history of a user.", //TODO TRANSLATION!!! + slashName:"/transcripts", + slashDescription:"View ticket transcript history of a user.", //TODO TRANSLATION!!! + textOptions:[{name:"user",optional:false}], + slashOptions:[{name:"user",optional:false}] + })) } } \ No newline at end of file diff --git a/src/data/framework/taskLoader.ts b/src/data/framework/taskLoader.ts index 6e68ca9..bc74ec7 100644 --- a/src/data/framework/taskLoader.ts +++ b/src/data/framework/taskLoader.ts @@ -7,6 +7,7 @@ const userDatabase = opendiscord.databases.get("opendiscord:users") const ticketDatabase = opendiscord.databases.get("opendiscord:tickets") const statsDatabase = opendiscord.databases.get("opendiscord:stats") const optionDatabase = opendiscord.databases.get("opendiscord:options") +const transcriptsDatabase = opendiscord.databases.get("opendiscord:transcripts") const mainServer = opendiscord.client.mainServer export async function loadAllTasks(){ @@ -248,6 +249,18 @@ export async function loadDatabaseCleanersTasks(){ } }) })) + + //TRANSCRIPT DATABASE CLEANER + opendiscord.tasks.add(new api.ODTask("opendiscord:transcript-database-cleaner",8,async () => { + //preserve max 20 transcripts per user (async) + const userCount: Map = new Map() + for (const {key,value:transcript} of (await transcriptsDatabase.getCategory("opendiscord:transcript") ?? []).sort((a,b) => (b.value.ticketDeletedDate ?? 0)-(a.value.ticketDeletedDate ?? 0))){ + const currentAmount = userCount.get(transcript.ticketCreatorId) ?? 0 + userCount.set(transcript.ticketCreatorId,currentAmount+1) + + if (currentAmount > 20) transcriptsDatabase.delete("opendiscord:transcript",key) + } + })) } export async function loadPanelAutoUpdateTasks(){ diff --git a/src/index.ts b/src/index.ts index 29d6bb2..8d5f8b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -715,6 +715,7 @@ const main = async () => { await (await import("./commands/topic.js")).registerCommandResponders() await (await import("./commands/priority.js")).registerCommandResponders() await (await import("./commands/transfer.js")).registerCommandResponders() + await (await import("./commands/transcripts.js")).registerCommandResponders() } await opendiscord.events.get("onCommandResponderLoad").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions]) await opendiscord.events.get("afterCommandRespondersLoaded").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions])