-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add interaction handling and ping component
- Loading branch information
Showing
11 changed files
with
905 additions
and
34 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { Collection } from '@discordjs/collection'; | ||
import { RESTPutAPIApplicationCommandsJSONBody } from '@discordjs/core/http-only'; | ||
import EventEmitter from 'node:events'; | ||
import { inspect } from 'node:util'; | ||
import { rest } from '../utils/env.js'; | ||
import { isStatefulInteraction } from '../utils/stateful.js'; | ||
import ping from './ping/index.js'; | ||
import { | ||
ApplicationCommand, | ||
Component, | ||
EventName, | ||
EventsMap, | ||
MessageComponent, | ||
Modal, | ||
} from './types.js'; | ||
|
||
export const interactions = { | ||
commands: new Collection<string, ApplicationCommand>(), | ||
messageComponents: new Collection<string, MessageComponent>(), | ||
modals: new Collection<string, Modal>(), | ||
}; | ||
|
||
export const commands: RESTPutAPIApplicationCommandsJSONBody = []; | ||
|
||
export const statefuls = { | ||
messageComponents: [], | ||
modals: [], | ||
} as { messageComponents: string[]; modals: string[] }; | ||
|
||
function registerEvent(emitter: EventEmitter, event: EventsMap[EventName]) { | ||
emitter[event.type](event.name, async (...args) => { | ||
try { | ||
await event.execute(...args); | ||
} catch (err) { | ||
console.error(inspect(err)); | ||
} | ||
}); | ||
} | ||
|
||
function registerEvents(emitter: EventEmitter, events: EventsMap[EventName][]) { | ||
for (const event of events) { | ||
registerEvent(emitter, event); | ||
} | ||
} | ||
|
||
function loadComponent({ | ||
restEvents, | ||
commands: componentCommands, | ||
messageComponents, | ||
modals, | ||
}: Component) { | ||
restEvents && registerEvents(rest, restEvents); | ||
|
||
componentCommands?.map((command) => { | ||
interactions.commands.set(command.data.name, command); | ||
commands.push(command.data); | ||
}); | ||
messageComponents?.map((messageComponent) => { | ||
const customId = messageComponent.data.custom_id; | ||
interactions.messageComponents.set(customId, messageComponent); | ||
|
||
if (isStatefulInteraction(messageComponent)) | ||
statefuls.messageComponents.push(customId); | ||
}); | ||
modals?.map((modal) => { | ||
const customId = modal.data.custom_id; | ||
interactions.modals.set(customId, modal); | ||
|
||
if (isStatefulInteraction(modal)) statefuls.modals.push(customId); | ||
}); | ||
} | ||
|
||
export function loadComponents() { | ||
loadComponent(ping); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { ChatInputCommand } from '../types.js'; | ||
|
||
function pingMessage(p: string) { | ||
return `🏓 Pong! \`${p}\``; | ||
} | ||
|
||
export const command = { | ||
data: { | ||
name: 'ping', | ||
description: 'Ping command', | ||
}, | ||
async execute({ api, data: interaction, cb }) { | ||
cb(); | ||
|
||
const first = Date.now(); | ||
await api.interactions.reply(interaction.id, interaction.token, { | ||
content: pingMessage('fetching...'), | ||
}); | ||
|
||
const ping = Math.ceil((Date.now() - first) / 2); | ||
await api.interactions.editReply( | ||
interaction.application_id, | ||
interaction.token, | ||
{ | ||
content: pingMessage(`${ping}ms`), | ||
}, | ||
); | ||
}, | ||
} satisfies ChatInputCommand; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Component } from '../types.js'; | ||
import { command } from './command.js'; | ||
|
||
export default { | ||
commands: [command], | ||
} satisfies Component; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { | ||
API, | ||
APIApplicationCommandAutocompleteInteraction, | ||
APIApplicationCommandInteraction, | ||
APIButtonComponentWithCustomId, | ||
APIChannelSelectComponent, | ||
APIChatInputApplicationCommandInteraction, | ||
APIContextMenuInteraction, | ||
APIInteraction, | ||
APIInteractionResponse, | ||
APIMentionableSelectComponent, | ||
APIMessageApplicationCommandInteraction, | ||
APIMessageComponentButtonInteraction, | ||
APIMessageComponentInteraction, | ||
APIMessageComponentSelectMenuInteraction, | ||
APIModalInteractionResponseCallbackData, | ||
APIModalSubmitInteraction, | ||
APIRoleSelectComponent, | ||
APIStringSelectComponent, | ||
APIUserApplicationCommandInteraction, | ||
APIUserSelectComponent, | ||
ApplicationCommandType, | ||
ComponentType, | ||
RESTPostAPIChatInputApplicationCommandsJSONBody, | ||
RESTPostAPIContextMenuApplicationCommandsJSONBody, | ||
} from '@discordjs/core/http-only'; | ||
import { RestEvents } from '@discordjs/rest'; | ||
import { Awaitable } from '@discordjs/util'; | ||
|
||
export type EventName = keyof RestEvents; | ||
|
||
export type EventExecuteArgs<T extends EventName> = T extends keyof RestEvents | ||
? RestEvents[T] | ||
: never; | ||
|
||
export interface IEvent<T extends EventName> { | ||
readonly type: 'on' | 'once'; | ||
readonly name: T; | ||
readonly execute: (...args: EventExecuteArgs<T>) => Awaitable<void>; | ||
} | ||
|
||
export type ContextMenuInteractionType<T extends APIContextMenuInteraction> = | ||
T extends APIUserApplicationCommandInteraction | ||
? ApplicationCommandType.User | ||
: T extends APIMessageApplicationCommandInteraction | ||
? ApplicationCommandType.Message | ||
: never; | ||
|
||
export interface MessageComponentDataMap { | ||
[ComponentType.ActionRow]: never; | ||
[ComponentType.Button]: APIButtonComponentWithCustomId; | ||
[ComponentType.StringSelect]: APIStringSelectComponent; | ||
[ComponentType.TextInput]: never; | ||
[ComponentType.UserSelect]: APIUserSelectComponent; | ||
[ComponentType.RoleSelect]: APIRoleSelectComponent; | ||
[ComponentType.MentionableSelect]: APIMentionableSelectComponent; | ||
[ComponentType.ChannelSelect]: APIChannelSelectComponent; | ||
} | ||
|
||
export type InteractionData<T extends APIInteraction> = | ||
T extends APIApplicationCommandInteraction | ||
? T extends APIChatInputApplicationCommandInteraction | ||
? RESTPostAPIChatInputApplicationCommandsJSONBody | ||
: T extends APIContextMenuInteraction | ||
? RESTPostAPIContextMenuApplicationCommandsJSONBody & { | ||
type: ContextMenuInteractionType<T>; | ||
} | ||
: never | ||
: T extends APIMessageComponentInteraction | ||
? MessageComponentDataMap[T['data']['component_type']] | ||
: T extends APIModalSubmitInteraction | ||
? APIModalInteractionResponseCallbackData | ||
: never; | ||
|
||
export type InteractionExecuteArgs<T extends APIInteraction> = { | ||
api: API; | ||
data: T; | ||
cb: (response?: APIInteractionResponse) => void; | ||
}; | ||
|
||
export interface IInteraction<T extends APIInteraction> { | ||
readonly data: InteractionData<T>; | ||
readonly execute: (props: InteractionExecuteArgs<T>) => Awaitable<void>; | ||
readonly autocomplete?: T extends APIChatInputApplicationCommandInteraction | ||
? ( | ||
props: InteractionExecuteArgs<APIApplicationCommandAutocompleteInteraction>, | ||
) => Awaitable<void> | ||
: never; | ||
} | ||
|
||
export type SelectMenuInteractionWithType<T extends ComponentType> = | ||
APIMessageComponentSelectMenuInteraction & { data: { component_type: T } }; | ||
|
||
export type RestEvent<T extends keyof RestEvents> = IEvent<T>; | ||
|
||
export type RestEventsMap = { | ||
[T in keyof RestEvents]: RestEvent<T>; | ||
}; | ||
export type EventsMap = RestEventsMap; | ||
|
||
export type ChatInputCommand = | ||
IInteraction<APIChatInputApplicationCommandInteraction>; | ||
export type UserCommand = IInteraction<APIUserApplicationCommandInteraction>; | ||
export type MessageCommand = | ||
IInteraction<APIMessageApplicationCommandInteraction>; | ||
export type ContextMenuCommand = UserCommand | MessageCommand; | ||
export type ApplicationCommand = ChatInputCommand | ContextMenuCommand; | ||
|
||
export type Button = IInteraction<APIMessageComponentButtonInteraction>; | ||
export type StringSelect = IInteraction< | ||
SelectMenuInteractionWithType<ComponentType.StringSelect> | ||
>; | ||
export type UserSelect = IInteraction< | ||
SelectMenuInteractionWithType<ComponentType.UserSelect> | ||
>; | ||
export type RoleSelect = IInteraction< | ||
SelectMenuInteractionWithType<ComponentType.RoleSelect> | ||
>; | ||
export type MentionableSelect = IInteraction< | ||
SelectMenuInteractionWithType<ComponentType.MentionableSelect> | ||
>; | ||
export type ChannelSelect = IInteraction< | ||
SelectMenuInteractionWithType<ComponentType.ChannelSelect> | ||
>; | ||
export type SelectMenu = | ||
| StringSelect | ||
| UserSelect | ||
| RoleSelect | ||
| MentionableSelect | ||
| ChannelSelect; | ||
export type MessageComponent = Button | SelectMenu; | ||
|
||
export type Modal = IInteraction<APIModalSubmitInteraction>; | ||
|
||
export interface Component { | ||
readonly restEvents?: RestEventsMap[keyof RestEvents][]; | ||
readonly commands?: ApplicationCommand[]; | ||
readonly messageComponents?: MessageComponent[]; | ||
readonly modals?: Modal[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
Oops, something went wrong.