(Open Discord) Added support for context menu's
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
//DEFAULT CLIENT MODULE
|
//DEFAULT CLIENT MODULE
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
import { ODValidId } from "../modules/base"
|
import { ODValidId } from "../modules/base"
|
||||||
import { ODClientManager, ODSlashCommand, ODTextCommand, ODSlashCommandManager, ODTextCommandManager, ODSlashCommandInteractionCallback, ODTextCommandInteractionCallback } from "../modules/client"
|
import { ODClientManager, ODSlashCommand, ODTextCommand, ODSlashCommandManager, ODTextCommandManager, ODSlashCommandInteractionCallback, ODTextCommandInteractionCallback, ODContextMenu, ODContextMenuManager, ODContextMenuInteractionCallback } from "../modules/client"
|
||||||
|
|
||||||
/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS?
|
/** (CONTRIBUTOR GUIDE) HOW TO ADD NEW COMMANDS?
|
||||||
* - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts)
|
* - Register the command in loadAllSlashCommands() & loadAllTextCommands() in (./src/data/framework/commandLoader.ts)
|
||||||
@@ -23,6 +23,7 @@ import { ODClientManager, ODSlashCommand, ODTextCommand, ODSlashCommandManager,
|
|||||||
export class ODClientManager_Default extends ODClientManager {
|
export class ODClientManager_Default extends ODClientManager {
|
||||||
declare slashCommands: ODSlashCommandManager_Default
|
declare slashCommands: ODSlashCommandManager_Default
|
||||||
declare textCommands: ODTextCommandManager_Default
|
declare textCommands: ODTextCommandManager_Default
|
||||||
|
declare contextMenus: ODContextMenuManager_Default
|
||||||
}
|
}
|
||||||
|
|
||||||
/**## ODSlashCommandManagerIds_Default `interface`
|
/**## ODSlashCommandManagerIds_Default `interface`
|
||||||
@@ -153,3 +154,47 @@ export class ODTextCommandManager_Default extends ODTextCommandManager {
|
|||||||
return super.onInteraction(commandPrefix,commandName,callback)
|
return super.onInteraction(commandPrefix,commandName,callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuManagerIds_Default `interface`
|
||||||
|
* This interface is a list of ids available in the `ODContextMenuManager_Default` class.
|
||||||
|
* It's used to generate typescript declarations for this class.
|
||||||
|
*/
|
||||||
|
export interface ODContextMenuManagerIds_Default {
|
||||||
|
"opendiscord:test-menu":ODContextMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuManager_Default `default_class`
|
||||||
|
* This is a special class that adds type definitions & typescript to the ODContextMenuManager class.
|
||||||
|
* It doesn't add any extra features!
|
||||||
|
*
|
||||||
|
* This default class is made for the global variable `opendiscord.client.contextMenus`!
|
||||||
|
*/
|
||||||
|
export class ODContextMenuManager_Default extends ODContextMenuManager {
|
||||||
|
get<ContextMenuId extends keyof ODContextMenuManagerIds_Default>(id:ContextMenuId): ODContextMenuManagerIds_Default[ContextMenuId]
|
||||||
|
get(id:ODValidId): ODContextMenu|null
|
||||||
|
|
||||||
|
get(id:ODValidId): ODContextMenu|null {
|
||||||
|
return super.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
remove<ContextMenuId extends keyof ODContextMenuManagerIds_Default>(id:ContextMenuId): ODContextMenuManagerIds_Default[ContextMenuId]
|
||||||
|
remove(id:ODValidId): ODContextMenu|null
|
||||||
|
|
||||||
|
remove(id:ODValidId): ODContextMenu|null {
|
||||||
|
return super.remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists(id:keyof ODContextMenuManagerIds_Default): boolean
|
||||||
|
exists(id:ODValidId): boolean
|
||||||
|
|
||||||
|
exists(id:ODValidId): boolean {
|
||||||
|
return super.exists(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
onInteraction(menuName:keyof ODContextMenuManagerIds_Default, callback:ODContextMenuInteractionCallback): void
|
||||||
|
onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback): void
|
||||||
|
|
||||||
|
onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback): void {
|
||||||
|
return super.onInteraction(menuName,callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import { ODFlagManager_Default } from "./flag"
|
|||||||
import { ODSessionManager_Default } from "./session"
|
import { ODSessionManager_Default } from "./session"
|
||||||
import { ODLanguageManager_Default } from "./language"
|
import { ODLanguageManager_Default } from "./language"
|
||||||
import { ODCheckerFunctionManager_Default, ODCheckerManager_Default, ODCheckerRenderer_Default, ODCheckerTranslationRegister_Default } from "./checker"
|
import { ODCheckerFunctionManager_Default, ODCheckerManager_Default, ODCheckerRenderer_Default, ODCheckerTranslationRegister_Default } from "./checker"
|
||||||
import { ODClientManager_Default, ODSlashCommandManager_Default, ODTextCommandManager_Default } from "./client"
|
import { ODClientManager_Default, ODContextMenuManager_Default, ODSlashCommandManager_Default, ODTextCommandManager_Default } from "./client"
|
||||||
import { ODBuilderManager_Default, ODButtonManager_Default, ODDropdownManager_Default, ODEmbedManager_Default, ODFileManager_Default, ODMessageManager_Default, ODModalManager_Default } from "./builder"
|
import { ODBuilderManager_Default, ODButtonManager_Default, ODDropdownManager_Default, ODEmbedManager_Default, ODFileManager_Default, ODMessageManager_Default, ODModalManager_Default } from "./builder"
|
||||||
import { ODButtonResponderManager_Default, ODCommandResponderManager_Default, ODDropdownResponderManager_Default, ODModalResponderManager_Default, ODResponderManager_Default } from "./responder"
|
import { ODButtonResponderManager_Default, ODCommandResponderManager_Default, ODDropdownResponderManager_Default, ODModalResponderManager_Default, ODResponderManager_Default } from "./responder"
|
||||||
import { ODActionManager_Default } from "./action"
|
import { ODActionManager_Default } from "./action"
|
||||||
@@ -128,6 +128,12 @@ export interface ODEventIds_Default {
|
|||||||
"onSlashCommandRegister": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
"onSlashCommandRegister": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
||||||
"afterSlashCommandsRegistered": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
"afterSlashCommandsRegistered": ODEvent_Default<(slash:ODSlashCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
||||||
|
|
||||||
|
//client context menus
|
||||||
|
"onContextMenuLoad": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
||||||
|
"afterContextMenusLoaded": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
||||||
|
"onContextMenuRegister": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
||||||
|
"afterContextMenusRegistered": ODEvent_Default<(menu:ODContextMenuManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
||||||
|
|
||||||
//client text commands
|
//client text commands
|
||||||
"onTextCommandLoad": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default,) => ODPromiseVoid>
|
"onTextCommandLoad": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default,) => ODPromiseVoid>
|
||||||
"afterTextCommandsLoaded": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
"afterTextCommandsLoaded": ODEvent_Default<(text:ODTextCommandManager_Default, client:ODClientManager_Default) => ODPromiseVoid>
|
||||||
|
|||||||
@@ -134,6 +134,9 @@ export interface ODProgressBarManagerIds_Default {
|
|||||||
"opendiscord:slash-command-remove":ODManualProgressBar,
|
"opendiscord:slash-command-remove":ODManualProgressBar,
|
||||||
"opendiscord:slash-command-create":ODManualProgressBar,
|
"opendiscord:slash-command-create":ODManualProgressBar,
|
||||||
"opendiscord:slash-command-update":ODManualProgressBar,
|
"opendiscord:slash-command-update":ODManualProgressBar,
|
||||||
|
"opendiscord:context-menu-remove":ODManualProgressBar,
|
||||||
|
"opendiscord:context-menu-create":ODManualProgressBar,
|
||||||
|
"opendiscord:context-menu-update":ODManualProgressBar,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**## ODProgressBarManager_Default `default_class`
|
/**## ODProgressBarManager_Default `default_class`
|
||||||
|
|||||||
@@ -76,12 +76,15 @@ export class ODClientManager {
|
|||||||
slashCommands: ODSlashCommandManager
|
slashCommands: ODSlashCommandManager
|
||||||
/**The text command manager is responsible for all text commands & their events inside the bot. */
|
/**The text command manager is responsible for all text commands & their events inside the bot. */
|
||||||
textCommands: ODTextCommandManager
|
textCommands: ODTextCommandManager
|
||||||
|
/**The context menu manager is responsible for all context menus & their events inside the bot. */
|
||||||
|
contextMenus: ODContextMenuManager
|
||||||
|
|
||||||
constructor(debug:ODDebugger){
|
constructor(debug:ODDebugger){
|
||||||
this.#debug = debug
|
this.#debug = debug
|
||||||
this.activity = new ODClientActivityManager(this.#debug,this)
|
this.activity = new ODClientActivityManager(this.#debug,this)
|
||||||
this.slashCommands = new ODSlashCommandManager(this.#debug,this)
|
this.slashCommands = new ODSlashCommandManager(this.#debug,this)
|
||||||
this.textCommands = new ODTextCommandManager(this.#debug,this)
|
this.textCommands = new ODTextCommandManager(this.#debug,this)
|
||||||
|
this.contextMenus = new ODContextMenuManager(this.#debug,this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**Initiate the `client` variable & add the intents & partials to the bot. */
|
/**Initiate the `client` variable & add the intents & partials to the bot. */
|
||||||
@@ -145,9 +148,10 @@ export class ODClientManager {
|
|||||||
this.client.once("ready",async () => {
|
this.client.once("ready",async () => {
|
||||||
this.ready = true
|
this.ready = true
|
||||||
|
|
||||||
//set slashCommandManager to client applicationCommandManager
|
//set slashCommandManager & contextMenuManager to client applicationCommandManager
|
||||||
if (!this.client.application) throw new ODSystemError("Couldn't get client application! Unable to register slash commands!")
|
if (!this.client.application) throw new ODSystemError("Couldn't get client application for slashCommand & contextMenu managers!")
|
||||||
this.slashCommands.commandManager = this.client.application.commands
|
this.slashCommands.commandManager = this.client.application.commands
|
||||||
|
this.contextMenus.commandManager = this.client.application.commands
|
||||||
|
|
||||||
if (this.readyListener) await this.readyListener()
|
if (this.readyListener) await this.readyListener()
|
||||||
resolve(true)
|
resolve(true)
|
||||||
@@ -522,6 +526,9 @@ export interface ODSlashCommandBuilder extends discord.ChatInputApplicationComma
|
|||||||
contexts:discord.InteractionContextType[]
|
contexts:discord.InteractionContextType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**## ODSlashCommandComparator `class`
|
||||||
|
* A utility class to compare existing slash commands with newly registered ones.
|
||||||
|
*/
|
||||||
export class ODSlashCommandComparator {
|
export class ODSlashCommandComparator {
|
||||||
/**Convert a `discord.ApplicationCommandOptionChoiceData<string>` to a universal Open Ticket slash command option choice object for comparison. */
|
/**Convert a `discord.ApplicationCommandOptionChoiceData<string>` to a universal Open Ticket slash command option choice object for comparison. */
|
||||||
#convertOptionChoice(choice:discord.ApplicationCommandOptionChoiceData<string>): ODSlashCommandUniversalOptionChoice {
|
#convertOptionChoice(choice:discord.ApplicationCommandOptionChoiceData<string>): ODSlashCommandUniversalOptionChoice {
|
||||||
@@ -1789,3 +1796,383 @@ export class ODTextCommandManager extends ODManager<ODTextCommand> {
|
|||||||
else return super.add(data,overwrite)
|
else return super.add(data,overwrite)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuUniversalMenu `interface`
|
||||||
|
* A universal template for a context menu.
|
||||||
|
*
|
||||||
|
* Why universal? Both **existing context menus** & **unregistered templates** can be converted to this type.
|
||||||
|
*/
|
||||||
|
export interface ODContextMenuUniversalMenu {
|
||||||
|
/**The type of this context menu. (required => `Message`|`User`) */
|
||||||
|
type:discord.ApplicationCommandType.Message|discord.ApplicationCommandType.User,
|
||||||
|
/**The name of this context menu. */
|
||||||
|
name:string,
|
||||||
|
/**All localized names of this context menu. */
|
||||||
|
nameLocalizations:readonly ODSlashCommandUniversalTranslation[],
|
||||||
|
/**The id of the guild this context menu is registered in. */
|
||||||
|
guildId:string|null,
|
||||||
|
/**Is this context menu for 18+ users only? */
|
||||||
|
nsfw:boolean,
|
||||||
|
/**A bitfield of the user permissions required to use this context menu. */
|
||||||
|
defaultMemberPermissions:bigint,
|
||||||
|
/**Is this context menu available in DM? */
|
||||||
|
dmPermission:boolean,
|
||||||
|
/**A list of contexts where you can install this context menu. */
|
||||||
|
integrationTypes:readonly discord.ApplicationIntegrationType[],
|
||||||
|
/**A list of contexts where you can use this context menu. */
|
||||||
|
contexts:readonly discord.InteractionContextType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuBuilderMessage `interface`
|
||||||
|
* The builder for message context menus.
|
||||||
|
*/
|
||||||
|
export interface ODContextMenuBuilderMessage extends discord.MessageApplicationCommandData {
|
||||||
|
/**This field is required in Open Ticket for future compatibility. */
|
||||||
|
integrationTypes:discord.ApplicationIntegrationType[],
|
||||||
|
/**This field is required in Open Ticket for future compatibility. */
|
||||||
|
contexts:discord.InteractionContextType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuBuilderUser `interface`
|
||||||
|
* The builder for user context menus.
|
||||||
|
*/
|
||||||
|
export interface ODContextMenuBuilderUser extends discord.UserApplicationCommandData {
|
||||||
|
/**This field is required in Open Ticket for future compatibility. */
|
||||||
|
integrationTypes:discord.ApplicationIntegrationType[],
|
||||||
|
/**This field is required in Open Ticket for future compatibility. */
|
||||||
|
contexts:discord.InteractionContextType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuBuilderUser `interface`
|
||||||
|
* The builder for context menus.
|
||||||
|
*/
|
||||||
|
export type ODContextMenuBuilder = (ODContextMenuBuilderMessage|ODContextMenuBuilderUser)
|
||||||
|
|
||||||
|
/**## ODContextMenuComparator `class`
|
||||||
|
* A utility class to compare existing context menu's with newly registered ones.
|
||||||
|
*/
|
||||||
|
export class ODContextMenuComparator {
|
||||||
|
/**Convert a `ODContextMenuBuilder` to a universal Open Ticket context menu object for comparison. */
|
||||||
|
convertBuilder(builder:ODContextMenuBuilder,guildId:string|null): ODContextMenuUniversalMenu|null {
|
||||||
|
if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User) return null
|
||||||
|
const nameLoc = builder.nameLocalizations ?? {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type:builder.type,
|
||||||
|
name:builder.name,
|
||||||
|
nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}),
|
||||||
|
guildId:guildId,
|
||||||
|
nsfw:builder.nsfw ?? false,
|
||||||
|
defaultMemberPermissions:discord.PermissionsBitField.resolve(builder.defaultMemberPermissions ?? ["ViewChannel"]),
|
||||||
|
dmPermission:(builder.contexts && builder.contexts.includes(discord.InteractionContextType.BotDM)) ?? false,
|
||||||
|
integrationTypes:builder.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall],
|
||||||
|
contexts:builder.contexts ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**Convert a `discord.ApplicationCommand` to a universal Open Ticket context menu object for comparison. */
|
||||||
|
convertMenu(cmd:discord.ApplicationCommand): ODContextMenuUniversalMenu|null {
|
||||||
|
if (cmd.type != discord.ApplicationCommandType.Message && cmd.type != discord.ApplicationCommandType.User) return null
|
||||||
|
const nameLoc = cmd.nameLocalizations ?? {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type:cmd.type,
|
||||||
|
name:cmd.name,
|
||||||
|
nameLocalizations:Object.keys(nameLoc).map((key) => {return {language:key as `${discord.Locale}`,value:nameLoc[key]}}),
|
||||||
|
guildId:cmd.guildId,
|
||||||
|
nsfw:cmd.nsfw,
|
||||||
|
defaultMemberPermissions:discord.PermissionsBitField.resolve(cmd.defaultMemberPermissions ?? ["ViewChannel"]),
|
||||||
|
dmPermission:(cmd.contexts && cmd.contexts.includes(discord.InteractionContextType.BotDM)) ? true : false,
|
||||||
|
integrationTypes:cmd.integrationTypes ?? [discord.ApplicationIntegrationType.GuildInstall],
|
||||||
|
contexts:cmd.contexts ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**Returns `true` when the 2 context menus are the same. */
|
||||||
|
compare(ctxA:ODContextMenuUniversalMenu,ctxB:ODContextMenuUniversalMenu): boolean {
|
||||||
|
if (ctxA.name != ctxB.name) return false
|
||||||
|
if (ctxA.type != ctxB.type) return false
|
||||||
|
if (ctxA.nsfw != ctxB.nsfw) return false
|
||||||
|
if (ctxA.guildId != ctxB.guildId) return false
|
||||||
|
if (ctxA.dmPermission != ctxB.dmPermission) return false
|
||||||
|
if (ctxA.defaultMemberPermissions != ctxB.defaultMemberPermissions) return false
|
||||||
|
|
||||||
|
//nameLocalizations
|
||||||
|
if (ctxA.nameLocalizations.length != ctxB.nameLocalizations.length) return false
|
||||||
|
if (!ctxA.nameLocalizations.every((nameA) => {
|
||||||
|
const nameB = ctxB.nameLocalizations.find((nameB) => nameB.language == nameA.language)
|
||||||
|
if (!nameB || nameA.value != nameB.value) return false
|
||||||
|
else return true
|
||||||
|
})) return false
|
||||||
|
|
||||||
|
//contexts
|
||||||
|
if (ctxA.contexts.length != ctxB.contexts.length) return false
|
||||||
|
if (!ctxA.contexts.every((contextA) => {
|
||||||
|
return ctxB.contexts.includes(contextA)
|
||||||
|
})) return false
|
||||||
|
|
||||||
|
//integrationTypes
|
||||||
|
if (ctxA.integrationTypes.length != ctxB.integrationTypes.length) return false
|
||||||
|
if (!ctxA.integrationTypes.every((integrationA) => {
|
||||||
|
return ctxB.integrationTypes.includes(integrationA)
|
||||||
|
})) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuInteractionCallback `type`
|
||||||
|
* Callback for the message context menu interaction listener.
|
||||||
|
*/
|
||||||
|
export type ODContextMenuInteractionCallback = (interaction:discord.ContextMenuCommandInteraction,cmd:ODContextMenu) => void
|
||||||
|
|
||||||
|
/**## ODContextMenuRegisteredResult `type`
|
||||||
|
* The result which will be returned when getting all (un)registered user context menu's from the manager.
|
||||||
|
*/
|
||||||
|
export type ODContextMenuRegisteredResult = {
|
||||||
|
/**A list of all registered message context menus. */
|
||||||
|
registered:{
|
||||||
|
/**The instance (`ODContextMenu`) from this message context menu. */
|
||||||
|
instance:ODContextMenu,
|
||||||
|
/**The universal object/template/builder of this message context menu. */
|
||||||
|
menu:ODContextMenuUniversalMenu,
|
||||||
|
/**Does this message context menu require an update? */
|
||||||
|
requiresUpdate:boolean
|
||||||
|
}[],
|
||||||
|
/**A list of all unregistered message context menus. */
|
||||||
|
unregistered:{
|
||||||
|
/**The instance (`ODContextMenu`) from this message context menu. */
|
||||||
|
instance:ODContextMenu,
|
||||||
|
/**The universal object/template/builder of this message context menu. */
|
||||||
|
menu:null,
|
||||||
|
/**Does this message context menu require an update? */
|
||||||
|
requiresUpdate:true
|
||||||
|
}[],
|
||||||
|
/**A list of all unused message context menus (not found in `ODContextMenuManager`). */
|
||||||
|
unused:{
|
||||||
|
/**The instance (`ODContextMenu`) from this message context menu. */
|
||||||
|
instance:null,
|
||||||
|
/**The universal object/template/builder of this message context menu. */
|
||||||
|
menu:ODContextMenuUniversalMenu,
|
||||||
|
/**Does this context menu require an update? */
|
||||||
|
requiresUpdate:false
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuManager `class`
|
||||||
|
* This is an Open Ticket client message context menu manager.
|
||||||
|
*
|
||||||
|
* It's responsible for managing all the message context interactions from the client.
|
||||||
|
*
|
||||||
|
* Here, you can add & remove message context interactions & the bot will do the (de)registering.
|
||||||
|
*/
|
||||||
|
export class ODContextMenuManager extends ODManager<ODContextMenu> {
|
||||||
|
/**Alias to Open Ticket debugger. */
|
||||||
|
#debug: ODDebugger
|
||||||
|
|
||||||
|
/**Refrerence to discord.js client. */
|
||||||
|
manager: ODClientManager
|
||||||
|
/**Discord.js application commands manager. */
|
||||||
|
commandManager: discord.ApplicationCommandManager|null
|
||||||
|
/**Collection of all interaction listeners. */
|
||||||
|
#interactionListeners: {name:string|RegExp, callback:ODContextMenuInteractionCallback}[] = []
|
||||||
|
/**Set the soft limit for maximum amount of listeners. A warning will be shown when there are more listeners than this limit. */
|
||||||
|
listenerLimit: number = 100
|
||||||
|
/**A utility class used to compare 2 context menus with each other. */
|
||||||
|
comparator: ODContextMenuComparator = new ODContextMenuComparator()
|
||||||
|
|
||||||
|
constructor(debug:ODDebugger, manager:ODClientManager){
|
||||||
|
super(debug,"context menu")
|
||||||
|
this.#debug = debug
|
||||||
|
this.manager = manager
|
||||||
|
this.commandManager = (manager.client.application) ? manager.client.application.commands : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**Get all registered & unregistered message context menu commands. */
|
||||||
|
async getAllRegisteredMenus(guildId?:string): Promise<ODContextMenuRegisteredResult> {
|
||||||
|
if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register message context menus!")
|
||||||
|
|
||||||
|
const menus = (await this.commandManager.fetch({guildId})).toJSON()
|
||||||
|
const registered: {instance:ODContextMenu, menu:ODContextMenuUniversalMenu, requiresUpdate:boolean}[] = []
|
||||||
|
const unregistered: {instance:ODContextMenu, menu:null, requiresUpdate:true}[] = []
|
||||||
|
const unused: {instance:null, menu:ODContextMenuUniversalMenu, requiresUpdate:false}[] = []
|
||||||
|
|
||||||
|
await this.loopAll((instance) => {
|
||||||
|
if (guildId && instance.guildId != guildId) return
|
||||||
|
|
||||||
|
const index = menus.findIndex((menu) => menu.name == instance.name)
|
||||||
|
const menu = menus[index]
|
||||||
|
menus.splice(index,1)
|
||||||
|
if (menu){
|
||||||
|
//menu is registered (and may need to be updated)
|
||||||
|
const universalBuilder = this.comparator.convertBuilder(instance.builder,instance.guildId)
|
||||||
|
const universalMenu = this.comparator.convertMenu(menu)
|
||||||
|
|
||||||
|
//menu is not of the type 'message'|'user'
|
||||||
|
if (!universalBuilder || !universalMenu) return
|
||||||
|
|
||||||
|
const didChange = !this.comparator.compare(universalBuilder,universalMenu)
|
||||||
|
const requiresUpdate = didChange || (instance.requiresUpdate ? instance.requiresUpdate(universalMenu) : false)
|
||||||
|
registered.push({instance,menu:universalMenu,requiresUpdate})
|
||||||
|
|
||||||
|
//menu is not registered
|
||||||
|
}else unregistered.push({instance,menu:null,requiresUpdate:true})
|
||||||
|
})
|
||||||
|
|
||||||
|
menus.forEach((menu) => {
|
||||||
|
//menu does not exist in the manager (only append to unused when type == 'message'|'user')
|
||||||
|
const universalCmd = this.comparator.convertMenu(menu)
|
||||||
|
if (!universalCmd) return
|
||||||
|
unused.push({instance:null,menu:universalCmd,requiresUpdate:false})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {registered,unregistered,unused}
|
||||||
|
}
|
||||||
|
/**Create all context menus that are not registered yet.*/
|
||||||
|
async createNewMenus(instances:ODContextMenu[],progress?:ODManualProgressBar){
|
||||||
|
if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!")
|
||||||
|
if (instances.length > 0 && progress){
|
||||||
|
progress.max = instances.length
|
||||||
|
progress.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const instance of instances){
|
||||||
|
await this.createMenu(instance)
|
||||||
|
this.#debug.debug("Created new context menu",[
|
||||||
|
{key:"id",value:instance.id.value},
|
||||||
|
{key:"name",value:instance.name},
|
||||||
|
{key:"type",value:(instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"}
|
||||||
|
])
|
||||||
|
if (progress) progress.increase(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**Update all context menus that are already registered. */
|
||||||
|
async updateExistingMenus(instances:ODContextMenu[],progress?:ODManualProgressBar){
|
||||||
|
if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!")
|
||||||
|
if (instances.length > 0 && progress){
|
||||||
|
progress.max = instances.length
|
||||||
|
progress.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const instance of instances){
|
||||||
|
await this.createMenu(instance)
|
||||||
|
this.#debug.debug("Updated existing context menu",[
|
||||||
|
{key:"id",value:instance.id.value},
|
||||||
|
{key:"name",value:instance.name},
|
||||||
|
{key:"type",value:(instance.builder.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"}
|
||||||
|
])
|
||||||
|
if (progress) progress.increase(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**Remove all context menus that are registered but unused by Open Ticket. */
|
||||||
|
async removeUnusedMenus(instances:ODContextMenuUniversalMenu[],guildId?:string,progress?:ODManualProgressBar){
|
||||||
|
if (!this.manager.ready) throw new ODSystemError("Client isn't ready yet! Unable to register context menus!")
|
||||||
|
if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register context menus!")
|
||||||
|
if (instances.length > 0 && progress){
|
||||||
|
progress.max = instances.length
|
||||||
|
progress.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
const menus = await this.commandManager.fetch({guildId})
|
||||||
|
|
||||||
|
for (const instance of instances){
|
||||||
|
const menu = menus.find((menu) => menu.name == instance.name)
|
||||||
|
if (menu){
|
||||||
|
try {
|
||||||
|
await menu.delete()
|
||||||
|
this.#debug.debug("Removed existing context menu",[
|
||||||
|
{key:"name",value:menu.name},
|
||||||
|
{key:"guildId",value:guildId ?? "/"},
|
||||||
|
{key:"type",value:(instance.type == discord.ApplicationCommandType.Message) ? "message-context" : "user-context"}
|
||||||
|
])
|
||||||
|
}catch(err){
|
||||||
|
process.emit("uncaughtException",err)
|
||||||
|
throw new ODSystemError("Failed to delete context menu '"+menu.name+"'!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (progress) progress.increase(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**Create a context menu. **(SYSTEM ONLY)** => Use `ODContextMenuManager` for registering context menu's the default way! */
|
||||||
|
async createMenu(menu:ODContextMenu){
|
||||||
|
if (!this.commandManager) throw new ODSystemError("Couldn't get client application to register context menu's!")
|
||||||
|
try {
|
||||||
|
await this.commandManager.create(menu.builder,(menu.guildId ?? undefined))
|
||||||
|
}catch(err){
|
||||||
|
process.emit("uncaughtException",err)
|
||||||
|
throw new ODSystemError("Failed to register context menu '"+menu.name+"'!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**Start listening to the discord.js client `interactionCreate` event. */
|
||||||
|
startListeningToInteractions(){
|
||||||
|
this.manager.client.on("interactionCreate",(interaction) => {
|
||||||
|
//return when not in main server or DM
|
||||||
|
if (!this.manager.mainServer || (interaction.guild && interaction.guild.id != this.manager.mainServer.id)) return
|
||||||
|
|
||||||
|
if (!interaction.isContextMenuCommand()) return
|
||||||
|
const menu = this.getFiltered((menu) => menu.name == interaction.commandName)[0]
|
||||||
|
if (!menu) return
|
||||||
|
|
||||||
|
this.#interactionListeners.forEach((listener) => {
|
||||||
|
if (typeof listener.name == "string" && (interaction.commandName != listener.name)) return
|
||||||
|
else if (listener.name instanceof RegExp && !listener.name.test(interaction.commandName)) return
|
||||||
|
|
||||||
|
//this is a valid listener
|
||||||
|
listener.callback(interaction,menu)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/**Callback on interaction from one or multiple context menu's. */
|
||||||
|
onInteraction(menuName:string|RegExp, callback:ODContextMenuInteractionCallback){
|
||||||
|
this.#interactionListeners.push({
|
||||||
|
name:menuName,
|
||||||
|
callback
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.#interactionListeners.length > this.listenerLimit){
|
||||||
|
this.#debug.console.log("Possible context menu interaction memory leak detected!","warning",[
|
||||||
|
{key:"listeners",value:this.#interactionListeners.length.toString()}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**## ODContextMenuUpdateFunction `type`
|
||||||
|
* The function responsible for updating context menu's when they already exist.
|
||||||
|
*/
|
||||||
|
export type ODContextMenuUpdateFunction = (menu:ODContextMenuUniversalMenu) => boolean
|
||||||
|
|
||||||
|
/**## ODContextMenu `class`
|
||||||
|
* This is an Open Ticket context menu.
|
||||||
|
*
|
||||||
|
* When registered, you can listen for this context menu using the `ODContextResponder`. The advantages of using this class for creating a context menu are:
|
||||||
|
* - automatic registration in discord.js
|
||||||
|
* - error reporting to the user when the bot fails to respond
|
||||||
|
* - plugins can extend this context menu
|
||||||
|
* - the bot won't re-register the context menu when it already exists (except when requested)!
|
||||||
|
*
|
||||||
|
* And more!
|
||||||
|
*/
|
||||||
|
export class ODContextMenu extends ODManagerData {
|
||||||
|
/**The discord.js builder for this context menu. */
|
||||||
|
builder: ODContextMenuBuilder
|
||||||
|
/**The id of the guild this context menu is for. `null` when not set. */
|
||||||
|
guildId: string|null
|
||||||
|
/**Function to check if the context menu requires to be updated (when it already exists). */
|
||||||
|
requiresUpdate: ODContextMenuUpdateFunction|null = null
|
||||||
|
|
||||||
|
constructor(id:ODValidId, builder:ODContextMenuBuilder, requiresUpdate?:ODContextMenuUpdateFunction, guildId?:string){
|
||||||
|
super(id)
|
||||||
|
if (builder.type != discord.ApplicationCommandType.Message && builder.type != discord.ApplicationCommandType.User) throw new ODSystemError("ApplicationCommandData is required to be the 'Message'|'User' type!")
|
||||||
|
|
||||||
|
this.builder = builder
|
||||||
|
this.guildId = guildId ?? null
|
||||||
|
this.requiresUpdate = requiresUpdate ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**The name of this context menu. */
|
||||||
|
get name(): string {
|
||||||
|
return this.builder.name
|
||||||
|
}
|
||||||
|
set name(name:string){
|
||||||
|
this.builder.name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,6 +91,14 @@ export interface ODDefaults {
|
|||||||
forceSlashCommandRegistration:boolean,
|
forceSlashCommandRegistration:boolean,
|
||||||
/**When enabled, the bot is allowed to unregister all slash commands which aren't used in Open Ticket. Disable this if you don't want to use the Open Ticket `ODSlashCommand` classes. */
|
/**When enabled, the bot is allowed to unregister all slash commands which aren't used in Open Ticket. Disable this if you don't want to use the Open Ticket `ODSlashCommand` classes. */
|
||||||
allowSlashCommandRemoval:boolean,
|
allowSlashCommandRemoval:boolean,
|
||||||
|
/**Load the default Open Ticket context menus. */
|
||||||
|
contextMenuLoading:boolean,
|
||||||
|
/**Load the default Open Ticket context menu registerer (register menus in discord). */
|
||||||
|
contextMenuRegistering:boolean,
|
||||||
|
/**When enabled, the bot is forced to re-register all context menus in the server. This can be used in case of a auto-update malfunction. */
|
||||||
|
forceContextMenuRegistration:boolean,
|
||||||
|
/**When enabled, the bot is allowed to unregister all context menus which aren't used in Open Ticket. Disable this if you don't want to use the Open Ticket `ODContextMenu` classes. */
|
||||||
|
allowContextMenuRemoval:boolean,
|
||||||
/**Load the default Open Ticket text commands. */
|
/**Load the default Open Ticket text commands. */
|
||||||
textCommandLoading:boolean,
|
textCommandLoading:boolean,
|
||||||
|
|
||||||
@@ -277,6 +285,10 @@ export class ODDefaultsManager {
|
|||||||
slashCommandRegistering:true,
|
slashCommandRegistering:true,
|
||||||
forceSlashCommandRegistration:false,
|
forceSlashCommandRegistration:false,
|
||||||
allowSlashCommandRemoval:true,
|
allowSlashCommandRemoval:true,
|
||||||
|
contextMenuLoading:true,
|
||||||
|
contextMenuRegistering:true,
|
||||||
|
forceContextMenuRegistration:false,
|
||||||
|
allowContextMenuRemoval:true,
|
||||||
textCommandLoading:true,
|
textCommandLoading:true,
|
||||||
|
|
||||||
questionLoading:true,
|
questionLoading:true,
|
||||||
|
|||||||
@@ -1095,3 +1095,12 @@ export const loadAllTextCommands = async () => {
|
|||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const loadAllContextMenus = async () => {
|
||||||
|
const menus = opendiscord.client.contextMenus
|
||||||
|
const generalConfig = opendiscord.configs.get("opendiscord:general")
|
||||||
|
if (!generalConfig) return
|
||||||
|
|
||||||
|
const act = discord.ApplicationCommandType
|
||||||
|
if (!generalConfig.data.slashCommands) return
|
||||||
|
}
|
||||||
@@ -82,6 +82,12 @@ export const loadAllEvents = () => {
|
|||||||
"onSlashCommandRegister",
|
"onSlashCommandRegister",
|
||||||
"afterSlashCommandsRegistered",
|
"afterSlashCommandsRegistered",
|
||||||
|
|
||||||
|
//client context menus
|
||||||
|
"onContextMenuLoad",
|
||||||
|
"afterContextMenusLoaded",
|
||||||
|
"onContextMenuRegister",
|
||||||
|
"afterContextMenusRegistered",
|
||||||
|
|
||||||
//client text commands
|
//client text commands
|
||||||
"onTextCommandLoad",
|
"onTextCommandLoad",
|
||||||
"afterTextCommandsLoaded",
|
"afterTextCommandsLoaded",
|
||||||
|
|||||||
@@ -64,4 +64,13 @@ export const loadAllProgressBars = async () => {
|
|||||||
|
|
||||||
//SLASH COMMAND UPDATE (doesn't have correct amount yet)
|
//SLASH COMMAND UPDATE (doesn't have correct amount yet)
|
||||||
opendiscord.progressbars.add(new api.ODManualProgressBar("opendiscord:slash-command-update",fractRenderer.withAdditionalSettings({filledBarColor:"openticket"}),0,"max",null,"Commands Updated"))
|
opendiscord.progressbars.add(new api.ODManualProgressBar("opendiscord:slash-command-update",fractRenderer.withAdditionalSettings({filledBarColor:"openticket"}),0,"max",null,"Commands Updated"))
|
||||||
|
|
||||||
|
//CONTEXT MENU REMOVE (doesn't have correct amount yet)
|
||||||
|
opendiscord.progressbars.add(new api.ODManualProgressBar("opendiscord:context-menu-remove",fractRenderer.withAdditionalSettings({filledBarColor:"red"}),0,"max",null,"Context Menus Removed"))
|
||||||
|
|
||||||
|
//CONTEXT MENU CREATE (doesn't have correct amount yet)
|
||||||
|
opendiscord.progressbars.add(new api.ODManualProgressBar("opendiscord:context-menu-create",fractRenderer.withAdditionalSettings({filledBarColor:"green"}),0,"max",null,"Context Menus Created"))
|
||||||
|
|
||||||
|
//CONTEXT MENU UPDATE (doesn't have correct amount yet)
|
||||||
|
opendiscord.progressbars.add(new api.ODManualProgressBar("opendiscord:context-menu-update",fractRenderer.withAdditionalSettings({filledBarColor:"openticket"}),0,"max",null,"Context Menus Updated"))
|
||||||
}
|
}
|
||||||
@@ -456,6 +456,38 @@ const main = async () => {
|
|||||||
await opendiscord.events.get("afterSlashCommandsRegistered").emit([opendiscord.client.slashCommands,opendiscord.client])
|
await opendiscord.events.get("afterSlashCommandsRegistered").emit([opendiscord.client.slashCommands,opendiscord.client])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//load context menus
|
||||||
|
opendiscord.log("Loading context menus...","system")
|
||||||
|
if (opendiscord.defaults.getDefault("contextMenuLoading")){
|
||||||
|
await (await import("./data/framework/commandLoader.js")).loadAllContextMenus()
|
||||||
|
}
|
||||||
|
await opendiscord.events.get("onContextMenuLoad").emit([opendiscord.client.contextMenus,opendiscord.client])
|
||||||
|
await opendiscord.events.get("afterContextMenusLoaded").emit([opendiscord.client.contextMenus,opendiscord.client])
|
||||||
|
|
||||||
|
//register context menus (create, update & remove)
|
||||||
|
if (opendiscord.defaults.getDefault("forceContextMenuRegistration")) opendiscord.log("Forcing all context menus to be re-registered...","system")
|
||||||
|
opendiscord.log("Registering context menus... (this can take up to a minute)","system")
|
||||||
|
await opendiscord.events.get("onContextMenuRegister").emit([opendiscord.client.contextMenus,opendiscord.client])
|
||||||
|
if (opendiscord.defaults.getDefault("contextMenuRegistering")){
|
||||||
|
//get all context menus that are already registered in the bot
|
||||||
|
const menus = await opendiscord.client.contextMenus.getAllRegisteredMenus()
|
||||||
|
const removableMenus = menus.unused.map((menu) => menu.menu)
|
||||||
|
const newMenus = menus.unregistered.map((menu) => menu.instance)
|
||||||
|
const updatableMenus = menus.registered.filter((menu) => menu.requiresUpdate || opendiscord.defaults.getDefault("forceContextMenuRegistration")).map((menu) => menu.instance)
|
||||||
|
|
||||||
|
//init progress bars
|
||||||
|
const removeProgress = opendiscord.progressbars.get("opendiscord:context-menu-remove")
|
||||||
|
const createProgress = opendiscord.progressbars.get("opendiscord:context-menu-create")
|
||||||
|
const updateProgress = opendiscord.progressbars.get("opendiscord:context-menu-update")
|
||||||
|
|
||||||
|
//remove unused menus, create new menus & update existing menus
|
||||||
|
if (opendiscord.defaults.getDefault("allowContextMenuRemoval")) await opendiscord.client.contextMenus.removeUnusedMenus(removableMenus,undefined,removeProgress)
|
||||||
|
await opendiscord.client.contextMenus.createNewMenus(newMenus,createProgress)
|
||||||
|
await opendiscord.client.contextMenus.updateExistingMenus(updatableMenus,updateProgress)
|
||||||
|
|
||||||
|
await opendiscord.events.get("afterContextMenusRegistered").emit([opendiscord.client.contextMenus,opendiscord.client])
|
||||||
|
}
|
||||||
|
|
||||||
//load text commands
|
//load text commands
|
||||||
opendiscord.log("Loading text commands...","system")
|
opendiscord.log("Loading text commands...","system")
|
||||||
if (opendiscord.defaults.getDefault("allowDumpCommand")){
|
if (opendiscord.defaults.getDefault("allowDumpCommand")){
|
||||||
|
|||||||
Reference in New Issue
Block a user