UNPKG

disci

Version:

A HTTP Request handler for discord interactions

1,635 lines (1,611 loc) 45.2 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { ApplicationCommand: () => ApplicationCommand, AutoCompleteInteraction: () => AutoCompleteInteraction, BaseInteraction: () => BaseInteraction, BaseReplyInteraction: () => BaseReplyInteraction, ChatInputInteraction: () => ChatInputInteraction, ComponentInteraction: () => ComponentInteraction, InteractionHandler: () => InteractionHandler, InteractionOptions: () => InteractionOptions, MessageCommandInteraction: () => MessageCommandInteraction, PartialGuild: () => PartialGuild, PartialUser: () => PartialUser, Rest: () => Rest, UserCommandInteraction: () => UserCommandInteraction, Webhook: () => Webhook, WebhookPartial: () => WebhookPartial }); module.exports = __toCommonJS(src_exports); // src/InteractionHandler.ts var import_eventemitter3 = require("eventemitter3"); var import_v1012 = require("discord-api-types/v10"); // src/utils/constants.ts var DiscordEpoch = 14200704e5; var defaultOptions = { rest: { token: typeof process !== "undefined" && process.env.TOKEN || "" } }; // src/utils/helpers.ts var convertSnowflakeToTimeStamp = (id) => { const milliseconds = BigInt(id) >> 22n; return Number(milliseconds) + DiscordEpoch; }; function tryAndValue(fn) { try { return fn(); } catch { return null; } } var serializeObject = (obj) => { const newObj = {}; for (const [key, value] of Object.entries(obj)) { if (key === null || key === void 0) continue; if (value === null || value === void 0) continue; Object.defineProperty(newObj, key, { value, enumerable: true }); } return newObj; }; function isBufferLike(value) { return value instanceof ArrayBuffer || value instanceof Uint8Array || value instanceof Uint8ClampedArray; } // src/utils/Factories.ts var import_v1011 = require("discord-api-types/v10"); // src/structures/ApplicationCommand.ts var import_v108 = require("discord-api-types/v10"); // src/structures/Bitfield.ts var import_v10 = require("discord-api-types/v10"); var BitField = class _BitField { static Flags = {}; static None = 0n; bitfield; /** * Create a new Bitfield Instance * @param baseBits - Base bits to institate the class with */ constructor(baseBits = _BitField.None) { this.bitfield = _BitField.resolve(baseBits); } /** * Adds bits to the bitfield * @param bits New bits to add * @returns */ add(bits) { let resolvedBits = 0n; for (const bit of bits) { resolvedBits |= _BitField.resolve(bit); } this.bitfield |= resolvedBits; return this; } /** * Removes bits from the bitfield * @param bits bits to remove * @returns */ remove(bits) { let resolvedBits = 0n; for (const bit of bits) { resolvedBits |= _BitField.resolve(bit); } this.bitfield &= ~resolvedBits; return this; } /** * checks if all bits are present in the bitfield * @param bits bits to check * @returns */ has(bits) { const resolvedBits = _BitField.resolve(bits); return (this.bitfield & resolvedBits) === resolvedBits; } equals(bit) { return !!(this.bitfield & _BitField.resolve(bit)); } static resolve(bit) { switch (typeof bit) { case "bigint": return bit; case "number": return BigInt(bit); default: if (Array.isArray(bit)) { return bit.map((b) => _BitField.resolve(b)).reduce((prev, cur) => prev | cur, _BitField.None); } else if (bit instanceof _BitField) { return bit.bitfield; } else throw new TypeError(`Expected a bitfieldResolvable`); } } }; var PermissionsBitField = class _PermissionsBitField extends BitField { static Flags = import_v10.PermissionFlagsBits; *[Symbol.iterator]() { yield* this.array; } get array() { return Object.keys(import_v10.PermissionFlagsBits).filter( (flag) => this.has( _PermissionsBitField.Flags[flag] ) ); } }; var MessageFlagsBitField = class extends BitField { static Flags = import_v10.MessageFlags; }; var ChannelFlagsBitField = class extends BitField { static Flags = import_v10.ChannelFlags; }; // src/structures/BaseInteraction.ts var import_v107 = require("discord-api-types/v10"); // src/structures/primitives/User.ts var import_v102 = require("discord-api-types/v10"); var PartialUser = class { /** * The handler than initiated this class */ handler; /** * The user's id */ id; constructor(handler, data) { Object.defineProperty(this, "handler", { value: handler }); this.id = data.id; } /** * Fetch the user this partial belongs to */ async fetch() { const user = await this.handler.api.get(import_v102.Routes.user(this.id)); return new User(this.handler, user); } toString() { return `<@${this.id}>`; } }; var User = class extends PartialUser { /** * Create a new user from discord data * @param apiData - data from discord api */ constructor(handler, apiData) { super(handler, { id: apiData.id }); this.apiData = apiData; this.username = apiData.username; } /** * The username of this user.Not unique */ username; /** * Tag of this user. * @deprecated tag is no longer relevant */ get tag() { return `${this.username}`; } }; // src/structures/primitives/Member.ts var Member = class { handler; /** * User instance belonging to this member */ user; constructor(handler, apiMember) { Reflect.defineProperty(this, "handler", { value: handler }); this.user = new User(handler, apiMember.user); } toString() { return `<@${this.user.id}>`; } }; // src/structures/primitives/Webhook.ts var import_v105 = require("discord-api-types/v10"); // src/structures/primitives/Message.ts var import_v104 = require("discord-api-types/v10"); // src/structures/primitives/Channel.ts var import_v103 = require("discord-api-types/v10"); var GenericPartialChannel = class { handler; /** * Id of this channel */ id; constructor(handler, data) { this.handler = handler; this.id = data.id; } toString() { return `<#${this.id}>`; } /** * TimeStamp of when this channel was created */ get createdTimestamp() { return convertSnowflakeToTimeStamp(this.id); } /** * The time the channel was created */ get createdAt() { return new Date(this.createdTimestamp); } /** * Fetch the channel represented by the partial */ async fetch() { const apiChannel = await this.handler.api.get( import_v103.Routes.channel(this.id) ); return ChannelFactory.from(this.handler, apiChannel); } }; var BaseChannel = class extends GenericPartialChannel { /** * Type of this channel */ type; /** * Name of this channel */ name; flags; /** * * @param handler * @param apiChannel */ constructor(handler, apiChannel) { super(handler, apiChannel); this.type = apiChannel.type; this.name = apiChannel.name; this.flags = new ChannelFlagsBitField(apiChannel.flags); } isGuildChannel() { return "guildId" in this; } isTextBased() { return [import_v103.ChannelType.GuildText, import_v103.ChannelType.DM].includes(this.type); } isThreadChannel() { return [ import_v103.ChannelType.PrivateThread, import_v103.ChannelType.PublicThread, import_v103.ChannelType.AnnouncementThread ].includes(this.type); } }; var BaseTextChannel = class extends BaseChannel { constructor(handler, apiChannel) { super(handler, apiChannel); } }; var GuildTextChannel = class extends BaseTextChannel { type = import_v103.ChannelType.GuildText; guildId; nsfw; constructor(handler, apiChannel) { super(handler, apiChannel); this.guildId = apiChannel.guild_id; this.nsfw = Boolean(apiChannel.nsfw); } }; var DMTextChannel = class extends BaseTextChannel { type = import_v103.ChannelType.DM; constructor(handler, apiChannel) { super(handler, apiChannel); } }; var ThreadChannel = class extends BaseChannel { rawThread; /** * ID of the Parent channel of this thread */ parentId; /** * Parent channel of this thread */ parent; /** * ID of the thread creator */ ownerId; /** * The thread creator as a user */ owner; /** * Number of messages (not including the initial message or deleted messages) in a thread * */ messageCount; /** * Total count of messages ever sent in this thread */ totalSentMessages; /** * Whether the thread is archived */ archived; /** * Duration in minutes to automatically archive the thread after recent activity, can be set to: 60, 1440, 4320, 10080 */ autoArchiveDuration; /** * Whether the thread is locked; when a thread is locked, only users with `MANAGE_THREADS` can unarchive it */ locked; /** * Whether non-moderators can add other non-moderators to the thread; only available on private threads */ invitable; archivedTimeStamp; constructor(handler, apiThread) { super(handler, apiThread); this.rawThread = apiThread; this.parentId = apiThread.parent_id ?? null; this.ownerId = apiThread.owner_id ?? null; this.messageCount = apiThread.message_count ?? 0; this.totalSentMessages = apiThread.total_message_sent ?? null; this.archived = apiThread.thread_metadata?.archived ?? false; this.autoArchiveDuration = apiThread.thread_metadata?.auto_archive_duration ?? import_v103.ThreadAutoArchiveDuration.OneDay; this.locked = apiThread.thread_metadata?.locked ?? false; this.invitable = apiThread.thread_metadata?.invitable ?? false; this.archivedTimeStamp = apiThread.thread_metadata?.archive_timestamp ?? (/* @__PURE__ */ new Date()).toISOString(); if (this.parentId) this.parent = new GenericPartialChannel(handler, { id: this.parentId }); if (this.ownerId) this.owner = new PartialUser(handler, { id: this.ownerId }); } get archivedAt() { return new Date(this.archivedTimeStamp); } get createdAt() { return new Date(this.rawThread.thread_metadata?.create_timestamp ?? 0); } }; // src/structures/primitives/Message.ts var Message = class { handler; /** * Id of this message */ id; /** * Embeds for this message */ embeds; /** * Content of this message */ content; /** * Timestamp of the message was sent at */ createdTimestamp; /** * TImestamp of when this message was last edited (if applicable) */ editedTimestamp; /** * The user who created this message (if created by a user) */ author; /** * Webhook that created this message (if created by webhook) */ webhook; /** * Channel this message was created in */ channelId; channel; /** * Flags for this message */ flags; constructor(handler, apiData) { Object.defineProperty(this, "handler", { value: handler }); this.id = apiData.id; this.embeds = apiData.embeds ?? []; this.createdTimestamp = Date.parse(apiData.timestamp); this.editedTimestamp = apiData.edited_timestamp ? Date.parse(apiData.edited_timestamp) : null; this.channelId = apiData.channel_id; this.channel = new GenericPartialChannel(this.handler, { id: this.channelId }); if ("flags" in apiData) { this.flags = new MessageFlagsBitField(apiData.flags); } else this.flags = new MessageFlagsBitField(); if ("content" in apiData) { this.content = apiData.content; } if (apiData.webhook_id) { this.webhook = new WebhookPartial(handler, { id: apiData.webhook_id }); } else { this.author = new User(this.handler, apiData.author); } } /** * The time the message was sent at */ get createdAt() { return new Date(this.createdTimestamp); } /** * The time the message was last edited at (if applicable) */ get editedAt() { return this.editedTimestamp ? new Date(this.editedTimestamp) : void 0; } /** * Whether this message has a thread associated with it */ get hasThread() { return this.flags.has(import_v104.MessageFlags.HasThread); } /** * Delete this Message */ async delete() { await this.handler.api.delete( import_v104.Routes.channelMessage(this.channelId, this.id) ); return this; } async addReaction(emoji) { const e = typeof emoji === "string" ? emoji : `${emoji.name}:${emoji.id}`; await this.handler.api.put( import_v104.Routes.channelMessageOwnReaction(this.channelId, this.id, e) ); } /** * Pins this message */ async pin() { await this.handler.api.put( import_v104.Routes.channelPin(this.channelId, this.id) ); } /** * Unpin this message */ async unpin() { await this.handler.api.delete( import_v104.Routes.channelPin(this.channelId, this.id) ); } /** * Creates a new thread from an existing message. * @param threadOptions Options for this thread * @returns */ async startThread(threadOptions) { if (this.hasThread) throw new Error(`This message already contains a thread`); if (!this.channel) throw new Error(`Channel for message could not be resolved`); const data = await this.handler.api.post( import_v104.Routes.threads(this.channel.id, this.id), { body: { name: threadOptions.name, auto_archive_duration: threadOptions.autoArchiveDuration, rate_limit_per_user: threadOptions.rateLimitPerUser } } ); return new ThreadChannel(this.handler, data); } /** * Internal method to resolve data for message Create * @private */ static resolveMessageParams(params) { const msg = {}; const files = []; if (params.content) { if (typeof params.content !== "string") throw new TypeError(`Expected a string for message content`); msg.content = params.content; } if (params.embeds) { if (!Array.isArray(params.embeds)) throw new TypeError(`Expected an array for embeds`); msg.embeds = params.embeds; } if (params.components) { if (!Array.isArray(params.components)) throw new TypeError(`Expected an array for Component Action rows`); msg.components = params.components; } if (params.files) { if (!Array.isArray(params.files)) throw new TypeError(`Expected an array for Files`); files.push(...params.files); } if (params.flags) { const bitfield = MessageFlagsBitField.resolve(params.flags); msg.flags = bitfield.toString(); } return { body: msg, files }; } }; // src/structures/primitives/Webhook.ts var WebhookPartial = class { /** * The handler than initiated this class */ handler; /** * The id of the webhook */ id; /** * The secure token of the webhook */ token; constructor(handler, data) { this.handler = handler; this.id = data.id; this.token = data.token; } /** * Fetch the webhook this id belongs to */ async fetch() { const webhook = await this.handler.api.get( // * takes token of undefined as optional parameter import_v105.Routes.webhook(this.id, this.token) ); return new Webhook(this.handler, webhook); } }; var Webhook = class extends WebhookPartial { /** *The type of the webhook * *See https://discord.com/developers/docs/resources/webhook#webhook-object-webhook-types */ type; /** * Owner of this webhook */ owner; /** * The application that created this werbhook */ applicationId; constructor(handler, data) { super(handler, { id: data.id, token: data.token }); this.type = data.type; this.owner = data.user ? new User(this.handler, data.user) : null; this.applicationId = data.application_id; } /** * Gets a message that was sent by this webhook. */ async fetchMessage(messageId, { threadId } = {}) { if (!this.token) throw new TypeError(`This webhook does not contain a Token`); const query = {}; if (threadId) query.threadId = threadId; const message = await this.handler.api.get( import_v105.Routes.webhookMessage(this.id, this.token, messageId), { query } ); return new Message(this.handler, message); } /** * * @param messageId id of the message to edit * @param newMessage new data to edit */ async editReply(messageId, newMessage) { if (!this.token) throw new Error(`This webhook does not contain a Token`); const resolvedParams = Message.resolveMessageParams(newMessage); const edited = await this.handler.api.patch( import_v105.Routes.webhookMessage(this.id, this.token, messageId), { body: resolvedParams.body, files: resolvedParams.files } ); return new Message(this.handler, edited); } /** * Sends a message with this webhook. */ async send(messageData, { threadId } = {}) { if (!this.token) throw new Error(`This webhook does not contain a Token`); const resolvedParams = Message.resolveMessageParams(messageData); const createdMessage = await this.handler.api.post( import_v105.Routes.webhookMessage(this.id, this.token), { body: resolvedParams.body, files: resolvedParams.files, query: { wait: true, thread_id: threadId } } ); return new Message(this.handler, createdMessage); } }; // src/structures/primitives/Guild.ts var import_v106 = require("discord-api-types/v10"); var PartialGuild = class { handler; /** * Id of this guild */ id; constructor(handler, { id }) { Object.defineProperty(this, "handler", { value: handler }); this.id = id; } /** * Fetch the guild this partial belongs to * @param opts.withCounts when true, will return approximate member and presence counts for the guild */ async fetch({ withCounts } = {}) { const guild = await this.handler.api.get(import_v106.Routes.guild(this.id), { query: withCounts ? { with_counts: "true" } : {} }); return new Guild(this.handler, guild); } }; var Guild = class extends PartialGuild { /** * Owner of this Guild as a partial */ owner; /** * Name of this guild */ name; /** * Approximate Member count not always present (use Guild.fetch() with "withCounts" enabled) */ approximateMemberCount; /** * Approximate Presence count not always present (use Guild.fetch() with "withCounts" enabled) */ approximatePresenceCount; /** * The description for the guild */ description; /** * Enabled guild features (animated banner, news, auto moderation, etc). * @link https://discord.com/developers/docs/resources/guild#guild-object-guild-features */ features; /** * Icon hash for this guild's Icon * @link https://discord.com/developers/docs/reference#image-formatting */ iconHash; constructor(handler, apiData) { super(handler, { id: apiData.id }); this.owner = new PartialUser(handler, { id: apiData.owner_id }); this.name = apiData.name; this.description = apiData.description; this.features = apiData.features; this.iconHash = apiData.icon; if ("approximate_member_count" in apiData) { this.approximateMemberCount = apiData.approximate_member_count; } if ("approximate_presence_count" in apiData) { this.approximatePresenceCount = apiData.approximate_presence_count; } } /** * boolean to indicate if this guild is a verified guild or not */ get verified() { return this.features.includes(import_v106.GuildFeature.Verified); } /** * boolean to indicate if this guild is a partnered guild or not */ get partnered() { return this.features.includes(import_v106.GuildFeature.Partnered); } /** * TimeStamp of when this guild was created */ get createdTimestamp() { return convertSnowflakeToTimeStamp(this.id); } /** * The time this guild was created as a date */ get createdAt() { return new Date(this.createdTimestamp); } /** * iconURL gets the current guild icon. * @link https://discord.com/developers/docs/reference#image-formatting */ iconURL(opts = { size: 128 }) { return this.iconHash && `${"https://cdn.discordapp.com" /* DiscordCdn */}/${import_v106.CDNRoutes.guildIcon( this.id, this.iconHash, opts.format ?? (this.iconHash.startsWith("a_") ? import_v106.ImageFormat.GIF : import_v106.ImageFormat.JPEG) )}`; } }; // src/structures/BaseInteraction.ts var BaseInteraction = class { /** * Create a new received interaction * @param handler * @param RawInteractionData */ constructor(handler, RawInteractionData) { this.RawInteractionData = RawInteractionData; Reflect.defineProperty(this, "handler", { value: handler }); Reflect.defineProperty(this, "rawData", { value: RawInteractionData }); this.id = RawInteractionData.id; this.applicationId = RawInteractionData.application_id; this.token = RawInteractionData.token; this.type = RawInteractionData.type; this.version = RawInteractionData.version; this.guildLocale = RawInteractionData.guild_locale ?? null; if (RawInteractionData.guild_id && RawInteractionData.member) { this.guildId = RawInteractionData.guild_id; this.guild = new PartialGuild(this.handler, { id: this.guildId }); this.member = new Member(this.handler, RawInteractionData.member); Reflect.defineProperty(this, "user", { get: () => { return this.member?.user; } }); } else if (RawInteractionData.user) { this.user = new User(this.handler, RawInteractionData.user); } if (RawInteractionData.channel_id) { this.channelId = RawInteractionData.channel_id; this.channel = new GenericPartialChannel(this.handler, { id: this.channelId }); } const permissions = RawInteractionData.app_permissions; if (permissions) { this.appPermissions = new PermissionsBitField(BigInt(permissions)); } this.responded = false; } /** * ID of the interaction */ id; /** * ID of the application this interaction is for */ applicationId; /** * Token of this interaction */ token; /** * Type of this interaction */ type; /** * Id of the Guild that the interaction was sent from */ guildId; /** * Guild the interaction was sent from as a partial guild */ guild; /** * Id of the Channel that the interaction was sent from */ channelId; /** * Channel the interactions was send from */ channel; /** * Readonly Property, as per the Discord docs always 1 * https://discord.com/developers/docs/interactions/receiving-and-responding */ version; appPermissions; callback; /** * If this interaction has Already been responded to */ responded; /** * The user who invoked this interaction */ user; /** * Guild member who invoked this interaction (if any) */ member; /** * Handler that initiated this class */ handler; /** * The preferred locale from the guild this interaction was sent in */ guildLocale; /** * Internal function. define the function used to respond the interaction * @param fn * @private * @ignore */ useCallback(fn) { Reflect.defineProperty(this, "callback", { value: fn, enumerable: false }); return this; } /** * Timestamp of this interaction */ get createdTimestamp() { return convertSnowflakeToTimeStamp(this.id); } /** * Created time as a date */ get createdAt() { return new Date(this.createdTimestamp); } /** * Indicates whether this interaction is a {@link ApplicationCommand} */ isCommand() { return this.type === import_v107.InteractionType.ApplicationCommand; } /** * Indicates whether this interaction is a {@link AutoCompleteInteraction} */ isAutoComplete() { return this.type === import_v107.InteractionType.ApplicationCommandAutocomplete; } /** * Indicates whether this interaction is a {@link ComponentInteraction} */ isComponent() { return this.type === import_v107.InteractionType.MessageComponent; } /** * Indicates whether this interaction can be replied to (i.e {@link BaseReplyInteraction}). */ isRepliable() { return !this.responded && this.type != import_v107.InteractionType.ApplicationCommandAutocomplete; } /** * Respond to this interaction, Raw method * @returns * @private * @ignore */ _respond(response) { if (this.responded) throw new Error(`This interaction has already been responded to.`); this.callback(response); this.responded = true; return this; } }; var BaseReplyInteraction = class extends BaseInteraction { deferReply({ fetchReply = false, ephemeral = false } = {}) { if (this.responded) throw new Error( `This Interaction already timed out or has been replied to` ); this._respond({ type: import_v107.InteractionResponseType.DeferredChannelMessageWithSource, data: { flags: ephemeral ? import_v107.MessageFlags.Ephemeral : void 0 } }); if (fetchReply) { return this.fetchReply(); } return Promise.resolve(this); } /** * Fetch the reply that was sent for this interaction * @returns Message */ fetchReply() { if (!this.responded) throw new Error(`Please Respond to this interaction before fetching it.`); return Webhook.prototype.fetchMessage.call( { id: this.applicationId, token: this.token, handler: this.handler }, "@original" ); } reply(opts) { if (this.responded) throw new Error( `This interaction either timed out or already been responded to` ); const APIResponse = { type: import_v107.InteractionResponseType.ChannelMessageWithSource, data: {} }; if (opts) { APIResponse.data = Message.resolveMessageParams(opts).body; } else throw new TypeError(`CreateMessage Options are required`); this._respond(APIResponse); if (opts.fetchReply === true) return this.fetchReply(); return Promise.resolve(this); } /** * Edit previously sent responses */ editReply(message) { if (!this.responded) throw new Error(`Interaction was not responded to`); return Webhook.prototype.editReply.call( { id: this.applicationId, token: this.token, handler: this.handler }, "@original", message ); } }; var InteractionOptions = class { _options; subCommand; group; constructor(options) { this._options = options ?? []; if (options[0]?.type === import_v107.ApplicationCommandOptionType.Subcommand) { this.subCommand = options[0].name; this._options = options[0].options ?? []; } if (options[0]?.type === import_v107.ApplicationCommandOptionType.SubcommandGroup) { this.group = options[0].name; this._options = options[0].options ?? []; } } getSubCommand(required = false) { if (required && !this.subCommand) throw new TypeError(`Subcommand Not found`); return this.subCommand ?? null; } getSubCommandGroup(required = false) { if (required && !this.group) throw new TypeError(`Subcommand Not found`); return this.group ?? null; } get(name, required = false) { const opt = this._options.find( (o) => o.name === name ); if (required && !opt) throw new TypeError(`Missing interaction option: ${name}`); return opt ?? null; } _getType(name, expectedTypes, properties, required) { const option = this.get(name, required); if (!option) return null; if (!expectedTypes.includes(option.type)) throw new TypeError( `Expected Type of option to be ${expectedTypes.join(" ")} Received ${option.type}` ); if (required && properties.every( (prop) => option[prop] == null || typeof option[prop] == "undefined" )) throw new TypeError(`Expected Value to be available`); return option; } getString(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.String], ["value"], required )?.value ?? null; } getBoolean(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.Boolean], ["value"], required )?.value ?? null; } getNumber(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.Number], ["value"], required )?.value ?? null; } getInteger(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.Integer], ["value"], required )?.value ?? null; } getUserId(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.User], ["value"], required )?.value ?? null; } getChannelId(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.Channel], ["value"], required )?.value ?? null; } getRolelId(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.Role], ["value"], required )?.value ?? null; } getMentionable(name, required = false) { return this._getType( name, [import_v107.ApplicationCommandOptionType.Mentionable], ["value"], required )?.value ?? null; } getFocused(full) { const focusedOption = this._options.find( (option) => option.focused ); if (!focusedOption) throw new Error(`No Focused option found`); return full ? focusedOption : focusedOption.value; } }; // src/structures/ApplicationCommand.ts var ApplicationCommand = class extends BaseReplyInteraction { type = import_v108.InteractionType.ApplicationCommand; /** * Type of this command * https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types */ commandType; /** * Name of this command */ commandName; /** * Id Of **The Application command** (not interaction) */ commandId; /** * Resolved Data of this interaction */ resolved; constructor(handler, rawData) { super(handler, rawData); const data = rawData.data; this.commandType = data.type; this.commandName = data.name; this.commandId = data.id; this.resolved = { users: /* @__PURE__ */ new Map(), members: /* @__PURE__ */ new Map(), roles: /* @__PURE__ */ new Map(), messages: /* @__PURE__ */ new Map() }; } /** * If this is a Message Context menu */ isMessageMenu() { return this.commandType === import_v108.ApplicationCommandType.Message; } /** * If this is a User Context menu */ isUserMenu() { return this.commandType === import_v108.ApplicationCommandType.User; } /** * If this is a Slash Command */ isSlashCommand() { return this.commandType === import_v108.ApplicationCommandType.ChatInput; } /** * Alias to isSlashCommand */ isChatInputInteraction() { return this.isSlashCommand(); } }; var ChatInputInteraction = class extends ApplicationCommand { options; commandType = import_v108.ApplicationCommandType.ChatInput; constructor(handler, rawData) { super(handler, rawData); this.options = new InteractionOptions(rawData.data.options ?? []); } }; var MessageCommandInteraction = class extends ApplicationCommand { commandType = import_v108.ApplicationCommandType.Message; constructor(handler, rawData) { super(handler, rawData); } }; var UserCommandInteraction = class extends ApplicationCommand { commandType = import_v108.ApplicationCommandType.User; /** * Id of the Target user */ targetId; /** * Target User */ targetUser; constructor(handler, rawData) { super(handler, rawData); const data = rawData.data; this.targetId = data.target_id; if (data.resolved.users) { for (const [id, user] of Object.entries(data.resolved.users)) { this.resolved.users.set(id, new User(this.handler, user)); } } if (data.resolved.members) { for (const [id, member] of Object.entries(data.resolved.members)) { this.resolved.members.set(id, member); } } this.targetUser = this.resolved.users.get(this.targetId); } }; // src/structures/AutoCompleteInteraction.ts var import_v109 = require("discord-api-types/v10"); var AutoCompleteInteraction = class extends BaseInteraction { type = import_v109.InteractionType.ApplicationCommandAutocomplete; /** * Id of the command this autocomplete was sent for */ commandId; /** * Name of the command this autocomplete was sent for */ commandName; /** * Type of the command this autocomplete was sent for */ commandType; /** * Guild id of the command this autocomplete was sent for */ commandGuildId; options; constructor(handler, rawData) { super(handler, rawData); const data = rawData.data; this.commandId = data.id; this.commandName = data.name; this.commandType = data.type; this.commandGuildId = data.guild_id; this.options = new InteractionOptions(data.options ?? []); } /** * Send autocomplete results * * @example * ```ts * interaction.respond([ * "regular Choice", * { name: 'choice', value: 'choice value' } * ]) * ``` * * @example * ```ts * // for no choices screen * interaction.respond([]) * ``` */ respond(choices) { if (!Array.isArray(choices)) throw new TypeError(`Expected autocomplete choices to be a array`); const _choices = this.getChoices(choices); this._respond({ type: import_v109.InteractionResponseType.ApplicationCommandAutocompleteResult, data: { choices: _choices } }); return this; } getChoices(choices) { return choices.map( (choice) => typeof choice !== "string" ? choice : { name: choice, value: choice } ); } }; // src/structures/ComponentInteraction.ts var import_v1010 = require("discord-api-types/v10"); var ComponentInteraction = class extends BaseReplyInteraction { type = import_v1010.InteractionType.MessageComponent; customId; constructor(handler, apiData) { super(handler, apiData); const data = apiData.data; this.customId = data.custom_id; } }; // src/utils/Factories.ts var InteractionFactory = class { static from(handler, apiInteraction) { switch (apiInteraction.type) { case import_v1011.InteractionType.ApplicationCommand: return ApplicationCommandFactory.from(handler, apiInteraction); case import_v1011.InteractionType.ApplicationCommandAutocomplete: return new AutoCompleteInteraction(handler, apiInteraction); case import_v1011.InteractionType.MessageComponent: return ComponentInteractionFactory.from(handler, apiInteraction); default: return null; } } }; var ComponentInteractionFactory = class { static from(handler, apiComponent) { return new ComponentInteraction(handler, apiComponent); } }; var ChannelFactory = class { static from(handler, apiChannel) { switch (apiChannel.type) { case import_v1011.ChannelType.GuildText: return new GuildTextChannel(handler, apiChannel); case import_v1011.ChannelType.DM: return new DMTextChannel(handler, apiChannel); case import_v1011.ChannelType.PrivateThread: case import_v1011.ChannelType.AnnouncementThread: case import_v1011.ChannelType.PublicThread: return new ThreadChannel(handler, apiChannel); default: return null; } } }; var ApplicationCommandFactory = class { static from(handler, apiAppCommand) { switch (apiAppCommand.data.type) { case import_v1011.ApplicationCommandType.ChatInput: return new ChatInputInteraction( handler, apiAppCommand ); case import_v1011.ApplicationCommandType.Message: return new MessageCommandInteraction( handler, apiAppCommand ); case import_v1011.ApplicationCommandType.User: return new UserCommandInteraction( handler, apiAppCommand ); default: return null; } } }; // src/utils/REST.ts var UserAgent = `DiscordBot (https://github.com/typicalninja/disci, 0.0.1)`.trim(); var Rest = class { authPrefix; authToken; rootUrl; /** * for support of serverless and other platforms */ constructor(_opts) { this.authPrefix = _opts.authPrefix || "Bot"; this.authToken = _opts.token || null; this.rootUrl = _opts.rootUrl ? _opts.rootUrl.endsWith("/") ? _opts.rootUrl.slice(0, _opts.rootUrl.length - 1) : _opts.rootUrl : "https://discord.com/api" /* DiscordApi */; } /** * Set the current active token, request may fail if this is not set * @param token * @returns */ setToken(token) { if (typeof token !== "string" || token === "") throw new TypeError( `Token must be a valid string, received ${typeof token}` ); this.authToken = token; return this; } async makeRequest(method, path, opts) { const request = this.getRequest(path, method, opts); const req = await fetch(this.getUrl(path, opts?.query), request.init); if (!req.ok) { const errors = await tryAndValue(() => req.json()); throw new Error( `Request to [${method}:${path}] returned ${req.status} [${req.statusText}]`, { cause: errors } ); } const contentType = req.headers.get("content-type"); if (contentType && contentType.includes("application/json")) return await req.json(); else return await req.arrayBuffer(); } get(path, opts) { return this.makeRequest("GET", path, opts); } post(path, opts) { return this.makeRequest("POST", path, opts); } put(path, opts) { return this.makeRequest("PUT", path, opts); } patch(path, opts) { return this.makeRequest("PATCH", path, opts); } delete(path, opts) { return this.makeRequest("DELETE", path, opts); } getUrl(path, queryParams) { let url; if (path.startsWith("/")) { url = `${this.rootUrl}${path}`; } else if (path.startsWith("https://") || path.startsWith("http://")) { url = path; } else { url = `${this.rootUrl}/${path}`; } if (queryParams) { url = `${url}?${new URLSearchParams( queryParams ).toString()}`; } return url; } get authHeader() { return `${this.authPrefix} ${this.authToken}`; } getRequest(path, method, options) { const baseHeaders = { "User-Agent": UserAgent }; if (options?.auth !== false) { if (!this.authToken) throw new Error(`Auth token was expected for request but was not set`); baseHeaders.Authorization = this.authHeader; } if (options?.reason && options.reason.length) { baseHeaders["X-Audit-Log-Reason"] = encodeURIComponent(options.reason); } if (options?.headers) { Object.assign(baseHeaders, options.headers); } let fBody; if (options?.files?.length) { const formData = new FormData(); for (const [index, file] of options.files.entries()) { const fileKey = file.key ?? `files[${index}]`; if (isBufferLike(file.data)) { const contentType = file.contentType; if (!contentType) throw new Error( `Expected content type for file (${fileKey}) to be a string` ); formData.append( fileKey, new Blob([file.data], { type: contentType }), file.name ); } else formData.append( fileKey, new Blob([`${file.data}`], { type: file.contentType }), file.name ); } if (options.body) { options.body = serializeObject(options.body); if (options.appendBodyToForm) { for (const [key, value] of Object.entries( options.body )) { formData.append(key, value); } } else formData.append("payload_json", JSON.stringify(options.body)); } fBody = formData; } else if (options?.body) { fBody = JSON.stringify(serializeObject(options.body)); baseHeaders["Content-Type"] = "application/json"; } const request = { method: method.toUpperCase(), headers: baseHeaders, body: fBody }; return { init: request, url: this.getUrl(path, options?.query) }; } }; // src/InteractionHandler.ts var InteractionHandler = class extends import_eventemitter3.EventEmitter { options; /** * Handler Rest Manager */ api; constructor(options = {}) { super(); this.options = Object.assign(defaultOptions, options); this.api = new Rest(this.options.rest); } /** * Process a request and return a response according to the request. * This does not verify the validity of the request * * @param body body of the received request * @param signal Abort controller signal allow you to control when the handler ends (timeouts etc) * @returns A json object containing data to be responded with * * * @example * * ```ts * // get the request here * * // verify it here * if(!(await isVerified(request))) return new Response("Invalid Headers, Unauthorized", { status: 401 }) * * const timeOutAbort = new AbortController(); * const timeout = setTimeout(() => { * timeOutAbort.abort("Time out"); * }, 3000); * * try { * const handled = await processRequest(body, timeOutAbort.signal) * // if it resolved that means handler successfully resolved * // remember to remove the timeout * clearTimeout(timeout) * // it safe to return the response as a json response * return new Response(handled, { status: 200 }) * } * catch { * return new Response("Server Error", { status: 500 }) * } * ``` */ processRequest(body, signal) { return new Promise((resolve, reject) => { const rawInteraction = tryAndValue( () => typeof body === "string" ? JSON.parse(body) : body ); if (!rawInteraction) return reject( new TypeError( `Failed to parse received interaction to a valid interaction` ) ); const interaction = InteractionFactory.from(this, rawInteraction); if (interaction) { interaction.useCallback((response) => resolve(response)); if (signal) { signal.addEventListener("abort", () => { interaction.useCallback(() => { throw new Error(`Interaction timed out (via abort)`); }); reject(signal.reason); }); } return this.emit("interactionCreate", interaction); } else if (rawInteraction.type === import_v1012.InteractionType.Ping) { return resolve({ type: import_v1012.InteractionResponseType.Pong }); } else { reject( new TypeError( `Unsupported Interaction of type ${rawInteraction.type} received` ) ); } }); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ApplicationCommand, AutoCompleteInteraction, BaseInteraction, BaseReplyInteraction, ChatInputInteraction, ComponentInteraction, InteractionHandler, InteractionOptions, MessageCommandInteraction, PartialGuild, PartialUser, Rest, UserCommandInteraction, Webhook, WebhookPartial }); //! impl: discord.js (https://github.com/discordjs/discord.js/blob/main/packages/rest/src/lib/utils/utils.ts#L140) //! impl: from d.js