mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-03 05:26:07 +00:00
wip: interactions
This commit is contained in:
parent
948cd2f717
commit
971f541f83
36
:x
Normal file
36
:x
Normal file
@ -0,0 +1,36 @@
|
||||
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Snowflake } from "../../util/Snowflake.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { ApplicationCommandTypes, DiscordInteraction } from "../../vendor/external.ts";
|
||||
import { InteractionResponseTypes } from "../../vendor/external.ts";
|
||||
import BaseInteraction from "./BaseInteraction.ts";
|
||||
import * as Routes from "../../util/Routes.ts";
|
||||
|
||||
export class PingInteraction extends BaseInteraction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
super(session, data);
|
||||
this.commandId = data.data!.id;
|
||||
this.commandName = data.data!.name;
|
||||
this.commandType = data.data!.type;
|
||||
this.commandGuildId = data.data!.guild_id;
|
||||
}
|
||||
|
||||
commandId: Snowflake;
|
||||
commandName: string;
|
||||
commandType: ApplicationCommandTypes;
|
||||
commandGuildId?: Snowflake;
|
||||
|
||||
async pong() {
|
||||
await this.session.rest.runMethod<undefined>(
|
||||
this.session.rest,
|
||||
"POST",
|
||||
Routes.INTERACTION_ID_TOKEN(this.id, this.token),
|
||||
{
|
||||
type: InteractionResponseTypes.Pong,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PingInteraction;
|
@ -38,7 +38,7 @@ import Member from "../structures/Member.ts";
|
||||
import Message from "../structures/Message.ts";
|
||||
import User from "../structures/User.ts";
|
||||
import Guild from "../structures/guilds/Guild.ts";
|
||||
import Interaction from "../structures/interactions/Interaction.ts";
|
||||
import InteractionFactory from "../structures/interactions/interactions/InteractionFactory.ts";
|
||||
|
||||
export type RawHandler<T> = (...args: [Session, number, T]) => void;
|
||||
export type Handler<T extends unknown[]> = (...args: T) => unknown;
|
||||
@ -106,12 +106,7 @@ export const GUILD_ROLE_DELETE: RawHandler<DiscordGuildRoleDelete> = (session, _
|
||||
}
|
||||
|
||||
export const INTERACTION_CREATE: RawHandler<DiscordInteraction> = (session, _shardId, interaction) => {
|
||||
session.unrepliedInteractions.add(BigInt(interaction.id));
|
||||
|
||||
// could be improved
|
||||
setTimeout(() => session.unrepliedInteractions.delete(BigInt(interaction.id)), 15 * 60 * 1000);
|
||||
|
||||
session.emit("interactionCreate", new Interaction(session, interaction));
|
||||
session.emit("interactionCreate", InteractionFactory.from(session, interaction));
|
||||
};
|
||||
|
||||
export const CHANNEL_CREATE: RawHandler<DiscordChannel> = (session, _shardId, channel) => {
|
||||
|
9
mod.ts
9
mod.ts
@ -37,7 +37,14 @@ export * from "./structures/guilds/InviteGuild.ts";
|
||||
|
||||
export * from "./structures/builders/EmbedBuilder.ts";
|
||||
|
||||
export * from "./structures/interactions/Interaction.ts";
|
||||
export * from "./structures/interactions/AutoCompleteInteraction.ts";
|
||||
export * from "./structures/interactions/BaseInteraction.ts";
|
||||
export * from "./structures/interactions/CommandInteraction.ts";
|
||||
export * from "./structures/interactions/CommandInteractionOptionResolver.ts";
|
||||
export * from "./structures/interactions/ComponentInteraction.ts";
|
||||
export * from "./structures/interactions/InteractionFactory.ts";
|
||||
export * from "./structures/interactions/ModalSubmitInteraction.ts";
|
||||
export * from "./structures/interactions/PingInteraction.ts";
|
||||
|
||||
export * from "./session/Session.ts";
|
||||
|
||||
|
@ -39,8 +39,6 @@ export class Session extends EventEmitter {
|
||||
rest: ReturnType<typeof createRestManager>;
|
||||
gateway: ReturnType<typeof createGatewayManager>;
|
||||
|
||||
unrepliedInteractions: Set<bigint> = new Set();
|
||||
|
||||
#botId: Snowflake;
|
||||
#applicationId?: Snowflake;
|
||||
|
||||
|
40
structures/interactions/AutoCompleteInteraction.ts
Normal file
40
structures/interactions/AutoCompleteInteraction.ts
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Snowflake } from "../../util/Snowflake.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { ApplicationCommandTypes, DiscordInteraction, InteractionTypes } from "../../vendor/external.ts";
|
||||
import type { ApplicationCommandOptionChoice } from "./BaseInteraction.ts";
|
||||
import { InteractionResponseTypes } from "../../vendor/external.ts";
|
||||
import BaseInteraction from "./BaseInteraction.ts";
|
||||
import * as Routes from "../../util/Routes.ts";
|
||||
|
||||
export class AutoCompleteInteraction extends BaseInteraction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
super(session, data);
|
||||
this.type = data.type as number;
|
||||
this.commandId = data.data!.id;
|
||||
this.commandName = data.data!.name;
|
||||
this.commandType = data.data!.type;
|
||||
this.commandGuildId = data.data!.guild_id;
|
||||
}
|
||||
|
||||
override type: InteractionTypes.ApplicationCommandAutocomplete;
|
||||
commandId: Snowflake;
|
||||
commandName: string;
|
||||
commandType: ApplicationCommandTypes;
|
||||
commandGuildId?: Snowflake;
|
||||
|
||||
async respond(choices: ApplicationCommandOptionChoice[]) {
|
||||
await this.session.rest.runMethod<undefined>(
|
||||
this.session.rest,
|
||||
"POST",
|
||||
Routes.INTERACTION_ID_TOKEN(this.id, this.token),
|
||||
{
|
||||
data: { choices },
|
||||
type: InteractionResponseTypes.ApplicationCommandAutocompleteResult,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AutoCompleteInteraction;
|
84
structures/interactions/BaseInteraction.ts
Normal file
84
structures/interactions/BaseInteraction.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { DiscordInteraction } from "../../vendor/external.ts";
|
||||
import type CommandInteraction from "./CommandInteraction.ts";
|
||||
import type PingInteraction from "./PingInteraction.ts";
|
||||
import { InteractionTypes } from "../../vendor/external.ts";
|
||||
import { Snowflake } from "../../util/Snowflake.ts";
|
||||
import User from "../User.ts";
|
||||
import Member from "../Member.ts";
|
||||
import Permsisions from "../Permissions.ts";
|
||||
|
||||
export abstract class BaseInteraction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
this.session = session;
|
||||
this.id = data.id;
|
||||
this.token = data.token;
|
||||
this.type = data.type;
|
||||
this.guildId = data.guild_id;
|
||||
this.channelId = data.channel_id;
|
||||
this.applicationId = data.application_id;
|
||||
this.version = data.version;
|
||||
|
||||
// @ts-expect-error: vendor error
|
||||
const perms = data.app_permissions as string;
|
||||
|
||||
if (perms) {
|
||||
this.appPermissions = new Permsisions(BigInt(perms));
|
||||
}
|
||||
|
||||
if (!data.guild_id) {
|
||||
this.user = new User(session, data.user!);
|
||||
} else {
|
||||
this.member = new Member(session, data.member!, data.guild_id);
|
||||
}
|
||||
}
|
||||
|
||||
readonly session: Session;
|
||||
readonly id: Snowflake;
|
||||
readonly token: string;
|
||||
|
||||
type: InteractionTypes;
|
||||
guildId?: Snowflake;
|
||||
channelId?: Snowflake;
|
||||
applicationId?: Snowflake;
|
||||
user?: User;
|
||||
member?: Member;
|
||||
appPermissions?: Permsisions;
|
||||
|
||||
readonly version: 1;
|
||||
|
||||
get createdTimestamp() {
|
||||
return Snowflake.snowflakeToTimestamp(this.id);
|
||||
}
|
||||
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
isCommand(): this is CommandInteraction {
|
||||
return this.type === InteractionTypes.ApplicationCommand;
|
||||
}
|
||||
|
||||
isAutoComplete() {
|
||||
return this.type === InteractionTypes.ApplicationCommandAutocomplete;
|
||||
}
|
||||
|
||||
isComponent() {
|
||||
return this.type === InteractionTypes.MessageComponent;
|
||||
}
|
||||
|
||||
isPing(): this is PingInteraction {
|
||||
return this.type === InteractionTypes.Ping;
|
||||
}
|
||||
|
||||
isModalSubmit() {
|
||||
return this.type === InteractionTypes.ModalSubmit;
|
||||
}
|
||||
|
||||
inGuild() {
|
||||
return !!this.guildId;
|
||||
}
|
||||
}
|
||||
|
||||
export default BaseInteraction;
|
108
structures/interactions/CommandInteraction.ts
Normal file
108
structures/interactions/CommandInteraction.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Snowflake } from "../../util/Snowflake.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { ApplicationCommandTypes, DiscordMemberWithUser, DiscordInteraction, InteractionTypes } from "../../vendor/external.ts";
|
||||
import type { CreateMessage } from "../Message.ts";
|
||||
import { InteractionResponseTypes } from "../../vendor/external.ts";
|
||||
import BaseInteraction from "./BaseInteraction.ts";
|
||||
import CommandInteractionOptionResolver from "./CommandInteractionOptionResolver.ts";
|
||||
import Attachment from "../Attachment.ts";
|
||||
import User from "../User.ts";
|
||||
import Member from "../Member.ts";
|
||||
import Message from "../Message.ts";
|
||||
import Role from "../Role.ts";
|
||||
|
||||
/**
|
||||
* @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response
|
||||
* */
|
||||
export interface InteractionResponse {
|
||||
type: InteractionResponseTypes;
|
||||
data?: InteractionApplicationCommandCallbackData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://discord.com/developers/docs/interactions/slash-commands#interaction-response-interactionapplicationcommandcallbackdata
|
||||
* */
|
||||
export interface InteractionApplicationCommandCallbackData extends Omit<CreateMessage, "messageReference"> {
|
||||
customId?: string;
|
||||
title?: string;
|
||||
// TODO: use builder
|
||||
// components?: MessageComponents;
|
||||
flags?: number;
|
||||
choices?: ApplicationCommandOptionChoice[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice
|
||||
* */
|
||||
export interface ApplicationCommandOptionChoice {
|
||||
name: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export class CommandInteraction extends BaseInteraction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
super(session, data);
|
||||
this.type = data.type as number;
|
||||
this.commandId = data.data!.id;
|
||||
this.commandName = data.data!.name;
|
||||
this.commandType = data.data!.type;
|
||||
this.commandGuildId = data.data!.guild_id;
|
||||
this.options = new CommandInteractionOptionResolver(data.data!.options ?? []);
|
||||
|
||||
this.resolved = {
|
||||
users: new Map(),
|
||||
members: new Map(),
|
||||
roles: new Map(),
|
||||
attachments: new Map(),
|
||||
messages: new Map(),
|
||||
};
|
||||
|
||||
if (data.data!.resolved?.users) {
|
||||
for (const [id, u] of Object.entries(data.data!.resolved.users)) {
|
||||
this.resolved.users.set(id, new User(session, u));
|
||||
}
|
||||
}
|
||||
|
||||
if (data.data!.resolved?.members && !!super.guildId) {
|
||||
for (const [id, m] of Object.entries(data.data!.resolved.members)) {
|
||||
this.resolved.members.set(id, new Member(session, m as DiscordMemberWithUser, super.guildId!));
|
||||
}
|
||||
}
|
||||
|
||||
if (data.data!.resolved?.roles && !!super.guildId) {
|
||||
for (const [id, r] of Object.entries(data.data!.resolved.roles)) {
|
||||
this.resolved.roles.set(id, new Role(session, r, super.guildId!));
|
||||
}
|
||||
}
|
||||
|
||||
if (data.data!.resolved?.attachments) {
|
||||
for (const [id, a] of Object.entries(data.data!.resolved.attachments)) {
|
||||
this.resolved.attachments.set(id, new Attachment(session, a));
|
||||
}
|
||||
}
|
||||
|
||||
if (data.data!.resolved?.messages) {
|
||||
for (const [id, m] of Object.entries(data.data!.resolved.messages)) {
|
||||
this.resolved.messages.set(id, new Message(session, m));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override type: InteractionTypes.ApplicationCommand;
|
||||
commandId: Snowflake;
|
||||
commandName: string;
|
||||
commandType: ApplicationCommandTypes;
|
||||
commandGuildId?: Snowflake;
|
||||
resolved: {
|
||||
users: Map<Snowflake, User>;
|
||||
members: Map<Snowflake, Member>;
|
||||
roles: Map<Snowflake, Role>;
|
||||
attachments: Map<Snowflake, Attachment>;
|
||||
messages: Map<Snowflake, Message>;
|
||||
};
|
||||
options: CommandInteractionOptionResolver;
|
||||
responded = false;
|
||||
}
|
||||
|
||||
export default CommandInteraction;
|
233
structures/interactions/CommandInteractionOptionResolver.ts
Normal file
233
structures/interactions/CommandInteractionOptionResolver.ts
Normal file
@ -0,0 +1,233 @@
|
||||
import type { DiscordInteractionDataOption, DiscordInteractionDataResolved } from '../../vendor/external.ts';
|
||||
import { ApplicationCommandOptionTypes } from "../../vendor/external.ts";
|
||||
|
||||
export function transformOasisInteractionDataOption(o: DiscordInteractionDataOption): CommandInteractionOption {
|
||||
const output: CommandInteractionOption = { ...o, Otherwise: o.value as string | boolean | number | undefined };
|
||||
|
||||
switch (o.type) {
|
||||
case ApplicationCommandOptionTypes.String:
|
||||
output.String = o.value as string;
|
||||
break;
|
||||
case ApplicationCommandOptionTypes.Number:
|
||||
output.Number = o.value as number;
|
||||
break;
|
||||
case ApplicationCommandOptionTypes.Integer:
|
||||
output.Integer = o.value as number;
|
||||
break;
|
||||
case ApplicationCommandOptionTypes.Boolean:
|
||||
output.Boolean = o.value as boolean;
|
||||
break;
|
||||
case ApplicationCommandOptionTypes.Role:
|
||||
output.Role = BigInt(o.value as string);
|
||||
break;
|
||||
case ApplicationCommandOptionTypes.User:
|
||||
output.User = BigInt(o.value as string);
|
||||
break;
|
||||
case ApplicationCommandOptionTypes.Channel:
|
||||
output.Channel = BigInt(o.value as string);
|
||||
break;
|
||||
|
||||
case ApplicationCommandOptionTypes.Mentionable:
|
||||
case ApplicationCommandOptionTypes.SubCommand:
|
||||
case ApplicationCommandOptionTypes.SubCommandGroup:
|
||||
default:
|
||||
output.Otherwise = o.value as string | boolean | number | undefined;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export interface CommandInteractionOption extends Omit<DiscordInteractionDataOption, 'value'> {
|
||||
Attachment?: string;
|
||||
Boolean?: boolean;
|
||||
User?: bigint;
|
||||
Role?: bigint;
|
||||
Number?: number;
|
||||
Integer?: number;
|
||||
Channel?: bigint;
|
||||
String?: string;
|
||||
Mentionable?: string;
|
||||
Otherwise: string | number | boolean | bigint | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to get the resolved options for a command
|
||||
* It is really typesafe
|
||||
* @example const option = ctx.options.getStringOption("name");
|
||||
*/
|
||||
export class CommandInteractionOptionResolver {
|
||||
#subcommand?: string;
|
||||
#group?: string;
|
||||
|
||||
hoistedOptions: CommandInteractionOption[];
|
||||
resolved?: DiscordInteractionDataResolved;
|
||||
|
||||
constructor(options?: DiscordInteractionDataOption[], resolved?: DiscordInteractionDataResolved) {
|
||||
this.hoistedOptions = options?.map(transformOasisInteractionDataOption) ?? [];
|
||||
|
||||
// warning: black magic do not edit and thank djs authors
|
||||
|
||||
if (this.hoistedOptions[0]?.type === ApplicationCommandOptionTypes.SubCommandGroup) {
|
||||
this.#group = this.hoistedOptions[0].name;
|
||||
this.hoistedOptions = (this.hoistedOptions[0].options ?? []).map(transformOasisInteractionDataOption);
|
||||
}
|
||||
|
||||
if (this.hoistedOptions[0]?.type === ApplicationCommandOptionTypes.SubCommand) {
|
||||
this.#subcommand = this.hoistedOptions[0].name;
|
||||
this.hoistedOptions = (this.hoistedOptions[0].options ?? []).map(transformOasisInteractionDataOption);
|
||||
}
|
||||
|
||||
this.resolved = resolved;
|
||||
}
|
||||
|
||||
private getTypedOption(
|
||||
name: string | number,
|
||||
type: ApplicationCommandOptionTypes,
|
||||
properties: Array<keyof CommandInteractionOption>,
|
||||
required: boolean,
|
||||
) {
|
||||
const option = this.get(name, required);
|
||||
|
||||
if (!option) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (option.type !== type) {
|
||||
// pass
|
||||
}
|
||||
|
||||
if (required === true && properties.every((prop) => typeof option[prop] === "undefined")) {
|
||||
throw new TypeError(`Properties ${properties.join(', ')} are missing in option ${name}`);
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
get(name: string | number, required: true): CommandInteractionOption;
|
||||
get(name: string | number, required: boolean): CommandInteractionOption | undefined;
|
||||
get(name: string | number, required?: boolean) {
|
||||
const option = this.hoistedOptions.find((o) =>
|
||||
typeof name === 'number' ? o.name === name.toString() : o.name === name
|
||||
);
|
||||
|
||||
if (!option) {
|
||||
if (required && name in this.hoistedOptions.map((o) => o.name)) {
|
||||
throw new TypeError('Option marked as required was undefined');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
||||
/** searches for a string option */
|
||||
getString(name: string | number, required: true): string;
|
||||
getString(name: string | number, required?: boolean): string | undefined;
|
||||
getString(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.String, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for a number option */
|
||||
getNumber(name: string | number, required: true): number;
|
||||
getNumber(name: string | number, required?: boolean): number | undefined;
|
||||
getNumber(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.Number, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searhces for an integer option */
|
||||
getInteger(name: string | number, required: true): number;
|
||||
getInteger(name: string | number, required?: boolean): number | undefined;
|
||||
getInteger(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.Integer, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for a boolean option */
|
||||
getBoolean(name: string | number, required: true): boolean;
|
||||
getBoolean(name: string | number, required?: boolean): boolean | undefined;
|
||||
getBoolean(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.Boolean, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for a user option */
|
||||
getUser(name: string | number, required: true): bigint;
|
||||
getUser(name: string | number, required?: boolean): bigint | undefined;
|
||||
getUser(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.User, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for a channel option */
|
||||
getChannel(name: string | number, required: true): bigint;
|
||||
getChannel(name: string | number, required?: boolean): bigint | undefined;
|
||||
getChannel(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.Channel, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for a mentionable-based option */
|
||||
getMentionable(name: string | number, required: true): string;
|
||||
getMentionable(name: string | number, required?: boolean): string | undefined;
|
||||
getMentionable(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.Mentionable, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for a mentionable-based option */
|
||||
getRole(name: string | number, required: true): bigint;
|
||||
getRole(name: string | number, required?: boolean): bigint | undefined;
|
||||
getRole(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.Role, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for an attachment option */
|
||||
getAttachment(name: string | number, required: true): string;
|
||||
getAttachment(name: string | number, required?: boolean): string | undefined;
|
||||
getAttachment(name: string | number, required = false) {
|
||||
const option = this.getTypedOption(name, ApplicationCommandOptionTypes.Attachment, ['Otherwise'], required);
|
||||
|
||||
return option?.Otherwise ?? undefined;
|
||||
}
|
||||
|
||||
/** searches for the focused option */
|
||||
getFocused(full = false) {
|
||||
const focusedOption = this.hoistedOptions.find((option) => option.focused);
|
||||
|
||||
if (!focusedOption) {
|
||||
throw new TypeError('No option found');
|
||||
}
|
||||
|
||||
return full ? focusedOption : focusedOption.Otherwise;
|
||||
}
|
||||
|
||||
getSubCommand(required = true) {
|
||||
if (required && !this.#subcommand) {
|
||||
throw new TypeError('Option marked as required was undefined');
|
||||
}
|
||||
|
||||
return [this.#subcommand, this.hoistedOptions];
|
||||
}
|
||||
|
||||
getSubCommandGroup(required = false) {
|
||||
if (required && !this.#group) {
|
||||
throw new TypeError('Option marked as required was undefined');
|
||||
}
|
||||
|
||||
return [this.#group, this.hoistedOptions];
|
||||
}
|
||||
}
|
||||
|
||||
export default CommandInteractionOptionResolver;
|
45
structures/interactions/ComponentInteraction.ts
Normal file
45
structures/interactions/ComponentInteraction.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Snowflake } from "../../util/Snowflake.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { DiscordInteraction, InteractionTypes } from "../../vendor/external.ts";
|
||||
import { MessageComponentTypes } from "../../vendor/external.ts";
|
||||
import BaseInteraction from "./BaseInteraction.ts";
|
||||
import Message from "../Message.ts";
|
||||
|
||||
export class ComponentInteraction extends BaseInteraction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
super(session, data);
|
||||
this.type = data.type as number;
|
||||
this.componentType = data.data!.component_type!;
|
||||
this.customId = data.data!.custom_id;
|
||||
this.targetId = data.data!.target_id;
|
||||
this.values = data.data!.values;
|
||||
this.message = new Message(session, data.message!);
|
||||
}
|
||||
|
||||
override type: InteractionTypes.MessageComponent;
|
||||
componentType: MessageComponentTypes;
|
||||
customId?: string;
|
||||
targetId?: Snowflake;
|
||||
values?: string[];
|
||||
message: Message;
|
||||
responded = false;
|
||||
|
||||
isButton() {
|
||||
return this.componentType === MessageComponentTypes.Button;
|
||||
}
|
||||
|
||||
isActionRow() {
|
||||
return this.componentType === MessageComponentTypes.ActionRow;
|
||||
}
|
||||
|
||||
isTextInput() {
|
||||
return this.componentType === MessageComponentTypes.InputText;
|
||||
}
|
||||
|
||||
isSelectMenu() {
|
||||
return this.componentType === MessageComponentTypes.SelectMenu;
|
||||
}
|
||||
}
|
||||
|
||||
export default ComponentInteraction;
|
@ -1,145 +0,0 @@
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Snowflake } from "../../util/Snowflake.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type {
|
||||
DiscordInteraction,
|
||||
DiscordMessage,
|
||||
FileContent,
|
||||
InteractionResponseTypes,
|
||||
InteractionTypes,
|
||||
} from "../../vendor/external.ts";
|
||||
import type { MessageFlags } from "../../util/shared/flags.ts";
|
||||
import type { AllowedMentions } from "../Message.ts";
|
||||
import User from "../User.ts";
|
||||
import Message from "../Message.ts";
|
||||
import Member from "../Member.ts";
|
||||
import * as Routes from "../../util/Routes.ts";
|
||||
|
||||
export interface InteractionResponse {
|
||||
type: InteractionResponseTypes;
|
||||
data?: InteractionApplicationCommandCallbackData;
|
||||
}
|
||||
|
||||
export interface InteractionApplicationCommandCallbackData {
|
||||
content?: string;
|
||||
tts?: boolean;
|
||||
allowedMentions?: AllowedMentions;
|
||||
files?: FileContent[];
|
||||
customId?: string;
|
||||
title?: string;
|
||||
// components?: Component[];
|
||||
flags?: MessageFlags;
|
||||
choices?: ApplicationCommandOptionChoice[];
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice */
|
||||
export interface ApplicationCommandOptionChoice {
|
||||
name: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
// TODO: abstract Interaction, CommandInteraction, ComponentInteraction, PingInteraction, etc
|
||||
|
||||
export class Interaction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
this.session = session;
|
||||
this.id = data.id;
|
||||
this.token = data.token;
|
||||
this.type = data.type;
|
||||
this.guildId = data.guild_id;
|
||||
this.channelId = data.channel_id;
|
||||
this.applicationId = data.application_id;
|
||||
this.locale = data.locale;
|
||||
this.data = data.data;
|
||||
|
||||
if (!data.guild_id) {
|
||||
this.user = new User(session, data.user!);
|
||||
} else {
|
||||
this.member = new Member(session, data.member!, data.guild_id);
|
||||
}
|
||||
}
|
||||
|
||||
readonly session: Session;
|
||||
readonly id: Snowflake;
|
||||
readonly token: string;
|
||||
|
||||
type: InteractionTypes;
|
||||
guildId?: Snowflake;
|
||||
channelId?: Snowflake;
|
||||
applicationId?: Snowflake;
|
||||
locale?: string;
|
||||
// deno-lint-ignore no-explicit-any
|
||||
data: any;
|
||||
user?: User;
|
||||
member?: Member;
|
||||
|
||||
async respond({ type, data }: InteractionResponse) {
|
||||
const toSend = {
|
||||
tts: data?.tts,
|
||||
title: data?.title,
|
||||
flags: data?.flags,
|
||||
content: data?.content,
|
||||
choices: data?.choices,
|
||||
custom_id: data?.customId,
|
||||
allowed_mentions: data?.allowedMentions
|
||||
? {
|
||||
users: data.allowedMentions.users,
|
||||
roles: data.allowedMentions.roles,
|
||||
parse: data.allowedMentions.parse,
|
||||
replied_user: data.allowedMentions.repliedUser,
|
||||
}
|
||||
: { parse: [] },
|
||||
};
|
||||
|
||||
if (this.session.unrepliedInteractions.delete(BigInt(this.id))) {
|
||||
await this.session.rest.sendRequest<undefined>(
|
||||
this.session.rest,
|
||||
{
|
||||
url: Routes.INTERACTION_ID_TOKEN(this.id, this.token),
|
||||
method: "POST",
|
||||
payload: this.session.rest.createRequestBody(this.session.rest, {
|
||||
method: "POST",
|
||||
body: {
|
||||
type: type,
|
||||
data: toSend,
|
||||
file: data?.files,
|
||||
},
|
||||
headers: {
|
||||
// remove authorization header
|
||||
Authorization: "",
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await this.session.rest.sendRequest<DiscordMessage>(
|
||||
this.session.rest,
|
||||
{
|
||||
url: Routes.WEBHOOK(this.session.applicationId ?? this.session.botId, this.token),
|
||||
method: "POST",
|
||||
payload: this.session.rest.createRequestBody(this.session.rest, {
|
||||
method: "POST",
|
||||
body: {
|
||||
...toSend,
|
||||
file: data?.files,
|
||||
},
|
||||
headers: {
|
||||
// remove authorization header
|
||||
Authorization: "",
|
||||
},
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
return new Message(this.session, result);
|
||||
}
|
||||
|
||||
inGuild(): this is Interaction & { user: undefined; guildId: Snowflake; member: Member } {
|
||||
return !!this.guildId;
|
||||
}
|
||||
}
|
||||
|
||||
export default Interaction;
|
32
structures/interactions/InteractionFactory.ts
Normal file
32
structures/interactions/InteractionFactory.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { DiscordInteraction } from "../../vendor/external.ts";
|
||||
import { InteractionTypes } from "../../vendor/external.ts";
|
||||
import CommandInteraction from "./CommandInteraction.ts";
|
||||
import ComponentInteraction from "./ComponentInteraction.ts";
|
||||
import PingInteraction from "./PingInteraction.ts";
|
||||
import AutoCompleteInteraction from "./AutoCompleteInteraction.ts";
|
||||
import ModalSubmitInteraction from "./ModalSubmitInteraction.ts";
|
||||
|
||||
export type Interaction =
|
||||
| CommandInteraction
|
||||
| ComponentInteraction
|
||||
| PingInteraction
|
||||
| AutoCompleteInteraction
|
||||
| ModalSubmitInteraction;
|
||||
|
||||
export class InteractionFactory {
|
||||
static from(session: Session, interaction: DiscordInteraction): Interaction {
|
||||
switch (interaction.type) {
|
||||
case InteractionTypes.Ping:
|
||||
return new PingInteraction(session, interaction);
|
||||
case InteractionTypes.ApplicationCommand:
|
||||
return new CommandInteraction(session, interaction);
|
||||
case InteractionTypes.MessageComponent:
|
||||
return new ComponentInteraction(session, interaction);
|
||||
case InteractionTypes.ApplicationCommandAutocomplete:
|
||||
return new AutoCompleteInteraction(session, interaction);
|
||||
case InteractionTypes.ModalSubmit:
|
||||
return new ModalSubmitInteraction(session, interaction);
|
||||
}
|
||||
}
|
||||
}
|
50
structures/interactions/ModalSubmitInteraction.ts
Normal file
50
structures/interactions/ModalSubmitInteraction.ts
Normal file
@ -0,0 +1,50 @@
|
||||
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Snowflake } from "../../util/Snowflake.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { DiscordInteraction, InteractionTypes, MessageComponentTypes, DiscordMessageComponents } from "../../vendor/external.ts";
|
||||
import BaseInteraction from "./BaseInteraction.ts";
|
||||
import Message from "../Message.ts";
|
||||
|
||||
export class ModalSubmitInteraction extends BaseInteraction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
super(session, data);
|
||||
this.type = data.type as number;
|
||||
this.componentType = data.data!.component_type!;
|
||||
this.customId = data.data!.custom_id;
|
||||
this.targetId = data.data!.target_id;
|
||||
this.values = data.data!.values;
|
||||
|
||||
this.components = data.data?.components?.map(ModalSubmitInteraction.transformComponent);
|
||||
|
||||
if (data.message) {
|
||||
this.message = new Message(session, data.message);
|
||||
}
|
||||
}
|
||||
|
||||
override type: InteractionTypes.MessageComponent;
|
||||
componentType: MessageComponentTypes;
|
||||
customId?: string;
|
||||
targetId?: Snowflake;
|
||||
values?: string[];
|
||||
message?: Message;
|
||||
components;
|
||||
|
||||
static transformComponent(component: DiscordMessageComponents[number]) {
|
||||
return {
|
||||
type: component.type,
|
||||
components: component.components.map((component) => {
|
||||
return {
|
||||
customId: component.custom_id,
|
||||
value: (component as typeof component & { value: string }).value,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
inMessage(): this is ModalSubmitInteraction & { message: Message } {
|
||||
return !!this.message;
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalSubmitInteraction;
|
38
structures/interactions/PingInteraction.ts
Normal file
38
structures/interactions/PingInteraction.ts
Normal file
@ -0,0 +1,38 @@
|
||||
|
||||
import type { Model } from "../Base.ts";
|
||||
import type { Snowflake } from "../../util/Snowflake.ts";
|
||||
import type { Session } from "../../session/Session.ts";
|
||||
import type { ApplicationCommandTypes, DiscordInteraction, InteractionTypes } from "../../vendor/external.ts";
|
||||
import { InteractionResponseTypes } from "../../vendor/external.ts";
|
||||
import BaseInteraction from "./BaseInteraction.ts";
|
||||
import * as Routes from "../../util/Routes.ts";
|
||||
|
||||
export class PingInteraction extends BaseInteraction implements Model {
|
||||
constructor(session: Session, data: DiscordInteraction) {
|
||||
super(session, data);
|
||||
this.type = data.type as number;
|
||||
this.commandId = data.data!.id;
|
||||
this.commandName = data.data!.name;
|
||||
this.commandType = data.data!.type;
|
||||
this.commandGuildId = data.data!.guild_id;
|
||||
}
|
||||
|
||||
override type: InteractionTypes.Ping;
|
||||
commandId: Snowflake;
|
||||
commandName: string;
|
||||
commandType: ApplicationCommandTypes;
|
||||
commandGuildId?: Snowflake;
|
||||
|
||||
async pong() {
|
||||
await this.session.rest.runMethod<undefined>(
|
||||
this.session.rest,
|
||||
"POST",
|
||||
Routes.INTERACTION_ID_TOKEN(this.id, this.token),
|
||||
{
|
||||
type: InteractionResponseTypes.Pong,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PingInteraction;
|
Loading…
x
Reference in New Issue
Block a user