Finished autocomplete & context menu interactions

Autocomplete & context menu interactions have been fully implemented in the Open Discord framework. They have not yet been implemented in the Open Ticket bot code though.
This commit is contained in:
DJj123dj
2025-06-24 08:43:57 +02:00
parent 8116d4d8dd
commit 6df3498cb5
7 changed files with 432 additions and 7 deletions
+1 -1
View File
@@ -160,7 +160,7 @@ export class ODTextCommandManager_Default extends ODTextCommandManager {
* It's used to generate typescript declarations for this class. * It's used to generate typescript declarations for this class.
*/ */
export interface ODContextMenuManagerIds_Default { export interface ODContextMenuManagerIds_Default {
"opendiscord:test-menu":ODContextMenu //"opendiscord:test-menu":ODContextMenu
} }
/**## ODContextMenuManager_Default `default_class` /**## ODContextMenuManager_Default `default_class`
+5 -1
View File
@@ -21,7 +21,7 @@ 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, ODContextMenuManager_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 { ODAutocompleteResponderManager_Default, ODButtonResponderManager_Default, ODCommandResponderManager_Default, ODContextMenuResponderManager_Default, ODDropdownResponderManager_Default, ODModalResponderManager_Default, ODResponderManager_Default } from "./responder"
import { ODActionManager_Default } from "./action" import { ODActionManager_Default } from "./action"
import { ODPermissionManager_Default } from "./permission" import { ODPermissionManager_Default } from "./permission"
import { ODHelpMenuManager_Default } from "./helpmenu" import { ODHelpMenuManager_Default } from "./helpmenu"
@@ -255,6 +255,10 @@ export interface ODEventIds_Default {
"afterDropdownRespondersLoaded": ODEvent_Default<(dropdowns:ODDropdownResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> "afterDropdownRespondersLoaded": ODEvent_Default<(dropdowns:ODDropdownResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid>
"onModalResponderLoad": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> "onModalResponderLoad": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid>
"afterModalRespondersLoaded": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid> "afterModalRespondersLoaded": ODEvent_Default<(modals:ODModalResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid>
"onContextMenuResponderLoad": ODEvent_Default<(menus:ODContextMenuResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid>
"afterContextMenuRespondersLoaded": ODEvent_Default<(menus:ODContextMenuResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid>
"onAutocompleteResponderLoad": ODEvent_Default<(autocomplete:ODAutocompleteResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid>
"afterAutocompleteRespondersLoaded": ODEvent_Default<(autocomplete:ODAutocompleteResponderManager_Default, responders:ODResponderManager_Default, actions:ODActionManager_Default) => ODPromiseVoid>
//plugin loading before finalizations //plugin loading before finalizations
"onPluginBeforeFinalizationLoad": ODEvent_Default<() => ODPromiseVoid>, "onPluginBeforeFinalizationLoad": ODEvent_Default<() => ODPromiseVoid>,
+97 -1
View File
@@ -2,7 +2,7 @@
//DEFAULT RESPONDER MODULE //DEFAULT RESPONDER MODULE
/////////////////////////////////////// ///////////////////////////////////////
import { ODValidId } from "../modules/base" import { ODValidId } from "../modules/base"
import { ODButtonResponder, ODButtonResponderInstance, ODButtonResponderManager, ODCommandResponder, ODCommandResponderInstance, ODCommandResponderManager, ODDropdownResponder, ODDropdownResponderInstance, ODDropdownResponderManager, ODModalResponder, ODModalResponderInstance, ODModalResponderManager, ODResponderManager } from "../modules/responder" import { ODAutocompleteResponder, ODAutocompleteResponderInstance, ODAutocompleteResponderManager, ODButtonResponder, ODButtonResponderInstance, ODButtonResponderManager, ODCommandResponder, ODCommandResponderInstance, ODCommandResponderManager, ODContextMenuResponder, ODContextMenuResponderInstance, ODContextMenuResponderManager, ODDropdownResponder, ODDropdownResponderInstance, ODDropdownResponderManager, ODModalResponder, ODModalResponderInstance, ODModalResponderManager, ODResponderManager } from "../modules/responder"
import { ODWorkerManager_Default } from "./worker" import { ODWorkerManager_Default } from "./worker"
/**## ODResponderManager_Default `default_class` /**## ODResponderManager_Default `default_class`
@@ -16,6 +16,8 @@ export class ODResponderManager_Default extends ODResponderManager {
declare buttons: ODButtonResponderManager_Default declare buttons: ODButtonResponderManager_Default
declare dropdowns: ODDropdownResponderManager_Default declare dropdowns: ODDropdownResponderManager_Default
declare modals: ODModalResponderManager_Default declare modals: ODModalResponderManager_Default
declare contextMenus: ODContextMenuResponderManager_Default
declare autocomplete: ODAutocompleteResponderManager_Default
} }
/**## ODCommandResponderManagerIds_Default `interface` /**## ODCommandResponderManagerIds_Default `interface`
@@ -252,4 +254,98 @@ export class ODModalResponderManager_Default extends ODModalResponderManager {
*/ */
export class ODModalResponder_Default<Source extends string, Params, WorkerIds extends string> extends ODModalResponder<Source,Params> { export class ODModalResponder_Default<Source extends string, Params, WorkerIds extends string> extends ODModalResponder<Source,Params> {
declare workers: ODWorkerManager_Default<ODModalResponderInstance,Source,Params,WorkerIds> declare workers: ODWorkerManager_Default<ODModalResponderInstance,Source,Params,WorkerIds>
}
/**## ODContextMenuResponderManagerIds_Default `interface`
* This interface is a list of ids available in the `ODContextMenuResponderManager_Default` class.
* It's used to generate typescript declarations for this class.
*/
export interface ODContextMenuResponderManagerIds_Default {
//"opendiscord:example":{source:"context-menu",params:{},workers:"opendiscord:example"},
}
/**## ODContextMenuResponderManager_Default `default_class`
* This is a special class that adds type definitions & typescript to the ODContextMenuResponderManager class.
* It doesn't add any extra features!
*
* This default class is made for the global variable `opendiscord.responders.contextMenus`!
*/
export class ODContextMenuResponderManager_Default extends ODContextMenuResponderManager {
get<ModalResponderId extends keyof ODContextMenuResponderManagerIds_Default>(id:ModalResponderId): ODContextMenuResponder_Default<ODContextMenuResponderManagerIds_Default[ModalResponderId]["source"],ODContextMenuResponderManagerIds_Default[ModalResponderId]["params"],ODContextMenuResponderManagerIds_Default[ModalResponderId]["workers"]>
get(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null
get(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null {
return super.get(id)
}
remove<ModalResponderId extends keyof ODContextMenuResponderManagerIds_Default>(id:ModalResponderId): ODContextMenuResponder_Default<ODContextMenuResponderManagerIds_Default[ModalResponderId]["source"],ODContextMenuResponderManagerIds_Default[ModalResponderId]["params"],ODContextMenuResponderManagerIds_Default[ModalResponderId]["workers"]>
remove(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null
remove(id:ODValidId): ODContextMenuResponder<"context-menu",any>|null {
return super.remove(id)
}
exists(id:keyof ODContextMenuResponderManagerIds_Default): boolean
exists(id:ODValidId): boolean
exists(id:ODValidId): boolean {
return super.exists(id)
}
}
/**## ODContextMenuResponder_Default `default_class`
* This is a special class that adds type definitions & typescript to the ODContextMenuResponder class.
* It doesn't add any extra features!
*
* This default class is made for the default `ODContextMenuResponder`'s!
*/
export class ODContextMenuResponder_Default<Source extends string, Params, WorkerIds extends string> extends ODContextMenuResponder<Source,Params> {
declare workers: ODWorkerManager_Default<ODContextMenuResponderInstance,Source,Params,WorkerIds>
}
/**## ODAutocompleteResponderManagerIds_Default `interface`
* This interface is a list of ids available in the `ODAutocompleteResponderManager_Default` class.
* It's used to generate typescript declarations for this class.
*/
export interface ODAutocompleteResponderManagerIds_Default {
//"opendiscord:example":{source:"autocomplete",params:{},workers:"opendiscord:example"},
}
/**## ODAutocompleteResponderManager_Default `default_class`
* This is a special class that adds type definitions & typescript to the ODAutocompleteResponderManager class.
* It doesn't add any extra features!
*
* This default class is made for the global variable `opendiscord.responders.autocomplete`!
*/
export class ODAutocompleteResponderManager_Default extends ODAutocompleteResponderManager {
get<ModalResponderId extends keyof ODAutocompleteResponderManagerIds_Default>(id:ModalResponderId): ODAutocompleteResponder_Default<ODAutocompleteResponderManagerIds_Default[ModalResponderId]["source"],ODAutocompleteResponderManagerIds_Default[ModalResponderId]["params"],ODAutocompleteResponderManagerIds_Default[ModalResponderId]["workers"]>
get(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null
get(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null {
return super.get(id)
}
remove<ModalResponderId extends keyof ODAutocompleteResponderManagerIds_Default>(id:ModalResponderId): ODAutocompleteResponder_Default<ODAutocompleteResponderManagerIds_Default[ModalResponderId]["source"],ODAutocompleteResponderManagerIds_Default[ModalResponderId]["params"],ODAutocompleteResponderManagerIds_Default[ModalResponderId]["workers"]>
remove(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null
remove(id:ODValidId): ODAutocompleteResponder<"autocomplete",any>|null {
return super.remove(id)
}
exists(id:keyof ODAutocompleteResponderManagerIds_Default): boolean
exists(id:ODValidId): boolean
exists(id:ODValidId): boolean {
return super.exists(id)
}
}
/**## ODAutocompleteResponder_Default `default_class`
* This is a special class that adds type definitions & typescript to the ODAutocompleteResponder class.
* It doesn't add any extra features!
*
* This default class is made for the default `ODAutocompleteResponder`'s!
*/
export class ODAutocompleteResponder_Default<Source extends string, Params, WorkerIds extends string> extends ODAutocompleteResponder<Source,Params> {
declare workers: ODWorkerManager_Default<ODAutocompleteResponderInstance,Source,Params,WorkerIds>
} }
+6
View File
@@ -140,6 +140,10 @@ export interface ODDefaults {
dropdownRespondersLoading:boolean, dropdownRespondersLoading:boolean,
/**Load the default Open Ticket modal responders. */ /**Load the default Open Ticket modal responders. */
modalRespondersLoading:boolean, modalRespondersLoading:boolean,
/**Load the default Open Ticket context menu responders. */
contextMenuRespondersLoading:boolean,
/**Load the default Open Ticket autocomplete responders. */
autocompleteRespondersLoading:boolean,
/**Set the time (in ms) before Open Ticket sends an error message when no reply is sent in a responder. */ /**Set the time (in ms) before Open Ticket sends an error message when no reply is sent in a responder. */
responderTimeoutMs:number, responderTimeoutMs:number,
@@ -311,6 +315,8 @@ export class ODDefaultsManager {
buttonRespondersLoading:true, buttonRespondersLoading:true,
dropdownRespondersLoading:true, dropdownRespondersLoading:true,
modalRespondersLoading:true, modalRespondersLoading:true,
contextMenuRespondersLoading:true,
autocompleteRespondersLoading:true,
responderTimeoutMs:2500, responderTimeoutMs:2500,
actionsLoading:true, actionsLoading:true,
+302 -3
View File
@@ -5,7 +5,7 @@ import { ODId, ODManager, ODValidId, ODSystemError, ODManagerData } from "./base
import * as discord from "discord.js" import * as discord from "discord.js"
import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker" import { ODWorkerManager, ODWorkerCallback, ODWorker } from "./worker"
import { ODDebugger } from "./console" import { ODDebugger } from "./console"
import { ODClientManager, ODSlashCommand, ODTextCommand, ODTextCommandInteractionOption } from "./client" import { ODClientManager, ODContextMenu, ODSlashCommand, ODTextCommand, ODTextCommandInteractionOption } from "./client"
import { ODDropdownData, ODMessageBuildResult, ODMessageBuildSentResult, ODModalBuildResult } from "./builder" import { ODDropdownData, ODMessageBuildResult, ODMessageBuildSentResult, ODModalBuildResult } from "./builder"
/**## ODResponderImplementation `class` /**## ODResponderImplementation `class`
@@ -36,7 +36,7 @@ export class ODResponderImplementation<Instance,Source extends string,Params> ex
/**## ODResponderTimeoutErrorCallback `type` /**## ODResponderTimeoutErrorCallback `type`
* This is the callback for the responder timeout function. It will be executed when something went wrong or the action takes too much time. * This is the callback for the responder timeout function. It will be executed when something went wrong or the action takes too much time.
*/ */
export type ODResponderTimeoutErrorCallback<Instance, Source extends "slash"|"text"|"button"|"dropdown"|"modal"|"other"> = (instance:Instance, source:Source) => void|Promise<void> export type ODResponderTimeoutErrorCallback<Instance, Source extends "slash"|"text"|"button"|"dropdown"|"modal"|"other"|"context-menu"|"autocomplete"> = (instance:Instance, source:Source) => void|Promise<void>
/**## ODResponderManager `class` /**## ODResponderManager `class`
* This is an Open Ticket responder manager. * This is an Open Ticket responder manager.
@@ -61,12 +61,18 @@ export class ODResponderManager {
dropdowns: ODDropdownResponderManager dropdowns: ODDropdownResponderManager
/**A manager for all modal responders. */ /**A manager for all modal responders. */
modals: ODModalResponderManager modals: ODModalResponderManager
/**A manager for all context menu responders. */
contextMenus: ODContextMenuResponderManager
/**A manager for all autocomplete responders. */
autocomplete: ODAutocompleteResponderManager
constructor(debug:ODDebugger, client:ODClientManager){ constructor(debug:ODDebugger, client:ODClientManager){
this.commands = new ODCommandResponderManager(debug,"command responder",client) this.commands = new ODCommandResponderManager(debug,"command responder",client)
this.buttons = new ODButtonResponderManager(debug,"button responder",client) this.buttons = new ODButtonResponderManager(debug,"button responder",client)
this.dropdowns = new ODDropdownResponderManager(debug,"dropdown responder",client) this.dropdowns = new ODDropdownResponderManager(debug,"dropdown responder",client)
this.modals = new ODModalResponderManager(debug,"modal responder",client) this.modals = new ODModalResponderManager(debug,"modal responder",client)
this.contextMenus = new ODContextMenuResponderManager(debug,"context menu responder",client)
this.autocomplete = new ODAutocompleteResponderManager(debug,"autocomplete responder",client)
} }
} }
@@ -1083,7 +1089,7 @@ export class ODModalResponderInstance {
const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags})) const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
this.didReply = true this.didReply = true
return {success:true,message:await sent.fetch()} return {success:true,message:await sent.fetch()}
}else throw new ODSystemError() }else throw new ODSystemError("Unable to update modal interaction!")
}catch{ }catch{
return {success:false,message:null} return {success:false,message:null}
} }
@@ -1113,4 +1119,297 @@ export class ODModalResponder<Source extends string,Params> extends ODResponderI
//wait for workers to finish //wait for workers to finish
await this.workers.executeWorkers(instance,source,params) await this.workers.executeWorkers(instance,source,params)
} }
}
/**## ODContextMenuResponderManager `class`
* This is an Open Ticket context menu responder manager.
*
* It contains all Open Ticket context menu responders. These can respond to user/message context menu interactions.
*
* Using the Open Ticket responder system has a few advantages compared to vanilla discord.js:
* - plugins can extend/edit replies
* - automatically reply on error
* - independent workers (with priority)
* - fail-safe design using try-catch
* - know where the request came from!
* - And so much more!
*/
export class ODContextMenuResponderManager extends ODManager<ODContextMenuResponder<"context-menu",any>> {
/**An alias to the Open Ticket client manager. */
#client: ODClientManager
/**The callback executed when the default workers take too much time to reply. */
#timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODContextMenuResponderInstance,"context-menu">|null = null
/**The amount of milliseconds before the timeout error callback is executed. */
#timeoutMs: number|null = null
constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
super(debug,debugname)
this.#client = client
}
/**Set the message to send when the response times out! */
setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODContextMenuResponderInstance,"context-menu">|null, ms:number|null){
this.#timeoutErrorCallback = callback
this.#timeoutMs = ms
}
add(data:ODContextMenuResponder<"context-menu",any>, overwrite?:boolean){
const res = super.add(data,overwrite)
this.#client.contextMenus.onInteraction(data.match,(interaction,cmd) => {
const newData = this.get(data.id)
if (!newData) return
newData.respond(new ODContextMenuResponderInstance(interaction,cmd,this.#timeoutErrorCallback,this.#timeoutMs),"context-menu",{})
})
return res
}
}
/**## ODContextMenuResponderInstance `class`
* This is an Open Ticket context menu responder instance.
*
* An instance is an active context menu interaction. You can reply to the context menu using `reply()`.
*/
export class ODContextMenuResponderInstance {
/**The interaction which is the source of this instance. */
interaction: discord.ContextMenuCommandInteraction
/**Did a worker already reply to this instance/interaction? */
didReply: boolean = false
/**The context menu wich is the source of this instance. */
menu:ODContextMenu
/**The user who triggered this context menu. */
user: discord.User
/**The guild member who triggered this context menu. */
member: discord.GuildMember|null
/**The guild where this context menu was triggered. */
guild: discord.Guild|null
/**The channel where this context menu was triggered. */
channel: discord.TextBasedChannel
/**The target of this context menu (user or message). */
target: discord.Message|discord.User
constructor(interaction:discord.ContextMenuCommandInteraction, menu:ODContextMenu, errorCallback:ODResponderTimeoutErrorCallback<ODContextMenuResponderInstance,"context-menu">|null, timeoutMs:number|null){
if (!interaction.channel) throw new ODSystemError("ODContextMenuResponderInstance: Unable to find interaction channel!")
this.interaction = interaction
this.menu = menu
this.user = interaction.user
this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
this.guild = interaction.guild
this.channel = interaction.channel
if (interaction.isMessageContextMenuCommand()) this.target = interaction.targetMessage
else if (interaction.isUserContextMenuCommand()) this.target = interaction.targetUser
else throw new ODSystemError("ODContextMenuResponderInstance: Invalid context menu type. Should be of the type User/Message!")
setTimeout(async () => {
if (!this.didReply){
try {
if (!errorCallback){
this.reply({id:new ODId("looks-like-we-got-an-error-here"), ephemeral:true, message:{
content:":x: **Something went wrong while replying to this context menu!**"
}})
}else{
await errorCallback(this,"context-menu")
}
}catch(err){
process.emit("uncaughtException",err)
}
}
},timeoutMs ?? 2500)
}
/**Reply to this context menu. */
async reply(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
try{
const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
if (this.interaction.replied || this.interaction.deferred){
const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
this.didReply = true
return {success:true,message:sent}
}else{
const sent = await this.interaction.reply(Object.assign(msg.message,{flags:msgFlags}))
this.didReply = true
return {success:true,message:await sent.fetch()}
}
}catch{
return {success:false,message:null}
}
}
/**Update the message of this context menu. */
async update(msg:ODMessageBuildResult): Promise<ODMessageBuildSentResult<boolean>> {
try{
const msgFlags: number[] = msg.ephemeral ? [discord.MessageFlags.Ephemeral] : []
if (this.interaction.replied || this.interaction.deferred){
const sent = await this.interaction.editReply(Object.assign(msg.message,{flags:msgFlags}))
this.didReply = true
return {success:true,message:await sent.fetch()}
}else throw new ODSystemError("Unable to update context menu interaction!")
}catch{
return {success:false,message:null}
}
}
/**Defer this context menu. */
async defer(type:"reply", ephemeral:boolean){
if (this.interaction.deferred) return false
if (type == "reply"){
const msgFlags: number[] = ephemeral ? [discord.MessageFlags.Ephemeral] : []
await this.interaction.deferReply({flags:msgFlags})
}
this.didReply = true
return true
}
/**Show a modal as reply to this context menu. */
async modal(modal:ODModalBuildResult){
this.interaction.showModal(modal.modal)
this.didReply = true
return true
}
}
/**## ODContextMenuResponder `class`
* This is an Open Ticket context menu responder.
*
* This class manages all workers which are executed when the related context menu is triggered.
*/
export class ODContextMenuResponder<Source extends string,Params> extends ODResponderImplementation<ODContextMenuResponderInstance,Source,Params> {
/**Respond to this button */
async respond(instance:ODContextMenuResponderInstance, source:Source, params:Params){
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
}
}
/**## ODAutocompleteResponderManager `class`
* This is an Open Ticket autocomplete responder manager.
*
* It contains all Open Ticket autocomplete responders. These can respond to autocomplete interactions.
*
* Using the Open Ticket responder system has a few advantages compared to vanilla discord.js:
* - plugins can extend/edit replies
* - automatically reply on error
* - independent workers (with priority)
* - fail-safe design using try-catch
* - know where the request came from!
* - And so much more!
*/
export class ODAutocompleteResponderManager extends ODManager<ODAutocompleteResponder<"autocomplete",any>> {
/**An alias to the Open Ticket client manager. */
#client: ODClientManager
/**The callback executed when the default workers take too much time to reply. */
#timeoutErrorCallback: ODResponderTimeoutErrorCallback<ODAutocompleteResponderInstance,"autocomplete">|null = null
/**The amount of milliseconds before the timeout error callback is executed. */
#timeoutMs: number|null = null
constructor(debug:ODDebugger, debugname:string, client:ODClientManager){
super(debug,debugname)
this.#client = client
}
/**Set the message to send when the response times out! */
setTimeoutErrorCallback(callback:ODResponderTimeoutErrorCallback<ODAutocompleteResponderInstance,"autocomplete">|null, ms:number|null){
this.#timeoutErrorCallback = callback
this.#timeoutMs = ms
}
add(data:ODAutocompleteResponder<"autocomplete",any>, overwrite?:boolean){
const res = super.add(data,overwrite)
this.#client.autocompletes.onInteraction(data.cmdMatch,data.match,(interaction) => {
const newData = this.get(data.id)
if (!newData) return
newData.respond(new ODAutocompleteResponderInstance(interaction,this.#timeoutErrorCallback,this.#timeoutMs),"autocomplete",{})
})
return res
}
}
/**## ODAutocompleteResponderInstance `class`
* This is an Open Ticket autocomplete responder instance.
*
* An instance is an active autocomplete interaction. You can reply to the autocomplete using `reply()`.
*/
export class ODAutocompleteResponderInstance {
/**The interaction which is the source of this instance. */
interaction: discord.AutocompleteInteraction
/**Did a worker already reply to this instance/interaction? */
didReply: boolean = false
/**The user who triggered this autocomplete. */
user: discord.User
/**The guild member who triggered this autocomplete. */
member: discord.GuildMember|null
/**The guild where this autocomplete was triggered. */
guild: discord.Guild|null
/**The channel where this autocomplete was triggered. */
channel: discord.TextBasedChannel
/**The target slash command option of this autocomplete. */
target: discord.AutocompleteFocusedOption
constructor(interaction:discord.AutocompleteInteraction, errorCallback:ODResponderTimeoutErrorCallback<ODAutocompleteResponderInstance,"autocomplete">|null, timeoutMs:number|null){
if (!interaction.channel) throw new ODSystemError("ODAutocompleteResponderInstance: Unable to find interaction channel!")
this.interaction = interaction
this.user = interaction.user
this.member = (interaction.member instanceof discord.GuildMember) ? interaction.member : null
this.guild = interaction.guild
this.channel = interaction.channel
this.target = interaction.options.getFocused(true)
setTimeout(async () => {
if (!this.didReply){
process.emit("uncaughtException",new ODSystemError("Autocomplete responder instance failed to respond widthin 2.5sec!"))
}
},timeoutMs ?? 2500)
}
/**Reply to this autocomplete. */
async autocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> {
const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => {
if (typeof raw == "string") return {name:raw,value:raw}
else return raw
})
try{
if (this.interaction.responded){
return {success:false}
}else{
await this.interaction.respond(newChoices)
return {success:true}
}
}catch(err){
process.emit("uncaughtException",err)
return {success:false}
}
}
/**Reply to this autocomplete, but filter choices based on the input of the user. */
async filteredAutocomplete(choices:(string|discord.ApplicationCommandOptionChoiceData)[]): Promise<{success:boolean}> {
const newChoices: (discord.ApplicationCommandOptionChoiceData)[] = choices.map((raw) => {
if (typeof raw == "string") return {name:raw,value:raw}
else return raw
})
const filteredChoices = newChoices.filter((choice) => choice.name.startsWith(this.target.value) || choice.value.toString().startsWith(this.target.value))
return await this.autocomplete(filteredChoices)
}
}
/**## ODAutocompleteResponder `class`
* This is an Open Ticket autocomplete responder.
*
* This class manages all workers which are executed when the related autocomplete is triggered.
*/
export class ODAutocompleteResponder<Source extends string,Params> extends ODResponderImplementation<ODAutocompleteResponderInstance,Source,Params> {
/**The slash command of the autocomplete should match the following regex. */
cmdMatch: string|RegExp
constructor(id:ODValidId,cmdMatch:string|RegExp,match:string|RegExp,callback?:ODWorkerCallback<ODAutocompleteResponderInstance,Source,Params>,priority?:number,callbackId?:ODValidId){
super(id,match,callback,priority,callbackId)
this.cmdMatch = cmdMatch
}
/**Respond to this autocomplete interaction. */
async respond(instance:ODAutocompleteResponderInstance, source:Source, params:Params){
//wait for workers to finish
await this.workers.executeWorkers(instance,source,params)
}
} }
+4
View File
@@ -209,6 +209,10 @@ export const loadAllEvents = () => {
"afterDropdownRespondersLoaded", "afterDropdownRespondersLoaded",
"onModalResponderLoad", "onModalResponderLoad",
"afterModalRespondersLoaded", "afterModalRespondersLoaded",
"onContextMenuResponderLoad",
"afterContextMenuRespondersLoaded",
"onAutocompleteResponderLoad",
"afterAutocompleteRespondersLoaded",
//plugin loading before finalizations //plugin loading before finalizations
"onPluginBeforeFinalizationLoad", "onPluginBeforeFinalizationLoad",
+17 -1
View File
@@ -710,6 +710,22 @@ const main = async () => {
await opendiscord.events.get("onModalResponderLoad").emit([opendiscord.responders.modals,opendiscord.responders,opendiscord.actions]) await opendiscord.events.get("onModalResponderLoad").emit([opendiscord.responders.modals,opendiscord.responders,opendiscord.actions])
await opendiscord.events.get("afterModalRespondersLoaded").emit([opendiscord.responders.modals,opendiscord.responders,opendiscord.actions]) await opendiscord.events.get("afterModalRespondersLoaded").emit([opendiscord.responders.modals,opendiscord.responders,opendiscord.actions])
//load context menu responders
opendiscord.log("Loading context menu responders...","system")
if (opendiscord.defaults.getDefault("contextMenuRespondersLoading")){
//TODO!!
}
await opendiscord.events.get("onContextMenuResponderLoad").emit([opendiscord.responders.contextMenus,opendiscord.responders,opendiscord.actions])
await opendiscord.events.get("afterContextMenuRespondersLoaded").emit([opendiscord.responders.contextMenus,opendiscord.responders,opendiscord.actions])
//load autocomplete responders
opendiscord.log("Loading autocomplete responders...","system")
if (opendiscord.defaults.getDefault("autocompleteRespondersLoading")){
//TODO!!
}
await opendiscord.events.get("onAutocompleteResponderLoad").emit([opendiscord.responders.autocomplete,opendiscord.responders,opendiscord.actions])
await opendiscord.events.get("afterAutocompleteRespondersLoaded").emit([opendiscord.responders.autocomplete,opendiscord.responders,opendiscord.actions])
//plugin loading before finalizations //plugin loading before finalizations
await opendiscord.events.get("onPluginBeforeFinalizationLoad").emit([]) await opendiscord.events.get("onPluginBeforeFinalizationLoad").emit([])
await opendiscord.events.get("afterPluginBeforeFinalizationLoaded").emit([]) await opendiscord.events.get("afterPluginBeforeFinalizationLoaded").emit([])
@@ -871,7 +887,7 @@ const main = async () => {
await opendiscord.startscreen.renderAllComponents() await opendiscord.startscreen.renderAllComponents()
if (opendiscord.languages.getLanguageMetadata(false)?.automated){ if (opendiscord.languages.getLanguageMetadata(false)?.automated){
console.log("===================") console.log("===================")
opendiscord.log("You are currently using a language which has been translated by Google Translate!","warning") opendiscord.log("You are using a language which has been translated using Google Translate or AI!","warning")
opendiscord.log("Please help us improve the translation by contributing to our project!","warning") opendiscord.log("Please help us improve the translation by contributing to our project!","warning")
console.log("===================") console.log("===================")
} }