UNPKG

nyxo.js

Version:

Complete Next-Generation Discord Bot Framework with Type-Safe API, Auto-Caching, and Real-Time Gateway

1,249 lines (1,236 loc) 434 kB
import "reflect-metadata"; import { ApplicationCommandOptionType, ApplicationCommandType, ApplicationFlags, AutoModerationActionType, AutoModerationRuleTriggerType, BitField, ChannelFlags, ChannelType, ComponentType, EntitlementType, GuildFeature, GuildScheduledEventStatus, GuildScheduledEventType, ISO3166_ALPHA2_REGEX, InteractionCallbackType, InteractionContextType, InteractionType, InviteTargetType, InviteType, MessageFlags, MessageReferenceType, MessageType, RoleFlags, SnowflakeUtil, StickerFormatType, StickerType, SubscriptionStatus, WebhookType, formatChannel, formatCustomEmoji, formatRole, formatUser, isValidUsername, isValidWebhookName, link } from "@nyxojs/core"; import { Gateway, GatewayOptions, VoiceChannelEffectSendAnimationType } from "@nyxojs/gateway"; import { Cdn, EntitlementOwnerType, Rest, RestOptions } from "@nyxojs/rest"; import { Store, StoreOptions } from "@nyxojs/store"; import { EventEmitter } from "eventemitter3"; import { z } from "zod/v4"; export * from "@nyxojs/builders" export * from "@nyxojs/core" export * from "@nyxojs/gateway" export * from "@nyxojs/rest" export * from "@nyxojs/store" //#region rolldown:runtime var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __commonJS = (cb, mod) => function() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion //#region src/bases/class.base.ts /** * Metadata keys used by the caching system */ const METADATA_KEYS = { CACHE_STORE_KEY: "nyxojs:cache:storeKey", CACHE_KEY_EXTRACTOR: "nyxojs:cache:keyExtractor" }; /** * Decorator that marks a class as cacheable and configures its caching behavior. * * This decorator enables automatic caching of entity instances in the specified store. * It optionally accepts a custom key extractor function for complex identifiers. * * @param storeKey - The name of the cache store where instances should be stored * @param keyExtractor - Optional function to extract the cache key from entity data */ function Cacheable(storeKey, keyExtractor) { return (target) => { Reflect.defineMetadata(METADATA_KEYS.CACHE_STORE_KEY, storeKey, target); if (keyExtractor) Reflect.defineMetadata(METADATA_KEYS.CACHE_KEY_EXTRACTOR, keyExtractor, target); }; } /** * Base class for all data models in the Nyxo.js framework. * * The BaseClass provides a foundation for working with Discord API data entities * in a structured and type-safe manner. It handles automatic caching through the * @Cacheable decorator. * * @template T The type of data this model contains (e.g., UserEntity, ChannelEntity) */ var BaseClass = class { /** * Reference to the client instance. * Provides access to API methods and other parts of the framework. */ client; /** * The raw data object received from the Discord API. * This should be accessed through getter methods rather than directly. */ rawData; /** * Creates a new instance of a model. * * @param client - The client instance that will be used for API requests * @param data - The raw data object from the Discord API * @throws {Error} Error if client or data is not provided */ constructor(client, data) { this.client = client; this.rawData = data; this.#initializeCache(); } /** * Converts this modal to a plain object with all properties. * The returned object is frozen to prevent accidental modification. * * @returns An immutable copy of the raw data object */ toJson() { return Object.freeze({ ...this.rawData }); } /** * Updates the internal data of this modal with new data. * This is useful when you need to update the model with fresh data from the API. * Also updates the entity in cache if it was previously cached. * * @param data - New data to merge with the existing data * @returns This instance for method chaining * @throws Error if data is not provided */ patch(data) { this.rawData = { ...this.rawData, ...data }; const cacheInfo = this.getCacheInfo(); if (cacheInfo) { const { storeKey, id } = cacheInfo; if (id && storeKey) { const cacheStore = this.client.cache[storeKey]; if (cacheStore.has(id)) cacheStore.add(id, this); } } return this; } /** * Removes this entity from the cache if it's currently stored. * This method is useful when you want to explicitly remove an entity * from the cache without waiting for automatic eviction. * * @returns true if the entity was removed from the cache, false otherwise */ uncache() { const cacheInfo = this.getCacheInfo(); if (!cacheInfo) return false; const { storeKey, id } = cacheInfo; if (!(id && storeKey)) return false; const cacheStore = this.client.cache[storeKey]; if (cacheStore.has(id)) return cacheStore.delete(id); return false; } /** * Checks if this modal is equal to another modal. * The default implementation compares IDs if available, otherwise compares data. * * @param other - The other modal to compare with * @returns Whether the classes represent the same entity */ equals(other) { if ("id" in this.rawData && "id" in other.rawData) return this.rawData.id === other.rawData.id; const thisKeys = Object.keys(this.rawData).sort(); const otherKeys = Object.keys(other.rawData).sort(); if (thisKeys.length !== otherKeys.length) return false; return thisKeys.every((key) => { const thisValue = this.rawData[key]; const otherValue = other.rawData[key]; return thisValue === otherValue; }); } /** * Checks if the data object is empty. * * @returns Whether the data object has no properties */ isEmpty() { return Object.keys(this.rawData).length === 0; } /** * Gets caching information for this entity by reading metadata from the class. * This information is set by the @Cacheable decorator. * * @returns Cache information containing the store key and ID, or null if the entity cannot be cached */ getCacheInfo() { const entityConstructor = this.constructor; const storeKey = Reflect.getMetadata(METADATA_KEYS.CACHE_STORE_KEY, entityConstructor); if (!storeKey) return null; const keyExtractor = Reflect.getMetadata(METADATA_KEYS.CACHE_KEY_EXTRACTOR, entityConstructor); let id = null; if (keyExtractor) id = keyExtractor(this.rawData); else if ("id" in this.rawData) id = typeof this.rawData.id === "string" ? this.rawData.id : String(this.rawData.id); return id ? { storeKey, id } : null; } /** * Initializes the cache for this entity. * * This method is automatically called when the entity is created. * It checks if the entity can be cached based on metadata and adds it to the appropriate cache store if it doesn't already exist. * * This is a private method and should not be called directly. * It is intended to be used internally by the framework to ensure that entities are cached correctly when they are instantiated. * * @private */ #initializeCache() { const cacheInfo = this.getCacheInfo(); if (cacheInfo) { const { storeKey, id } = cacheInfo; if (id && storeKey) { const cacheStore = this.client.cache[storeKey]; if (!cacheStore) return; const existingEntity = cacheStore.get(id); if (existingEntity) existingEntity.patch(this.rawData); else cacheStore.set(id, this); } } } }; //#endregion //#region src/utils/channel.util.ts /** * Creates and returns the appropriate channel instance based on the provided channel data. * * This function acts as a factory for channel objects, analyzing the type property * of the provided data and instantiating the corresponding channel class. * * @param client - The client instance used to create the channel * @param data - The channel entity data received from Discord API * @returns An instance of the appropriate channel class that corresponds to the channel type * * @throws {Error} If the channel type is not recognized or supported */ function channelFactory(client, data) { switch (data.type) { case ChannelType.Dm: return new DmChannel(client, data); case ChannelType.GroupDm: return new GroupDmChannel(client, data); case ChannelType.GuildText: return new GuildTextChannel(client, data); case ChannelType.GuildVoice: return new GuildVoiceChannel(client, data); case ChannelType.GuildCategory: return new GuildCategoryChannel(client, data); case ChannelType.GuildAnnouncement: return new GuildAnnouncementChannel(client, data); case ChannelType.PublicThread: return new PublicThreadChannel(client, data); case ChannelType.PrivateThread: return new PrivateThreadChannel(client, data); case ChannelType.AnnouncementThread: return new AnnouncementThreadChannel(client, data); case ChannelType.GuildStageVoice: return new GuildStageVoiceChannel(client, data); case ChannelType.GuildForum: return new GuildForumChannel(client, data); case ChannelType.GuildMedia: return new GuildMediaChannel(client, data); case ChannelType.GuildDirectory: return new GuildDirectoryChannel(client, data); default: throw new Error("Unknown channel. Please check the channel type and try again."); } } //#endregion //#region src/utils/interaction.util.ts /** * Creates and returns the appropriate interaction instance based on the provided interaction data. * * This function acts as a factory for interaction objects, analyzing the type property * and data of the provided interaction and instantiating the corresponding interaction class. * * @param client - The client instance used to create the interaction * @param data - The interaction entity data received from Discord API * @returns An instance of the appropriate interaction class that corresponds to the interaction type * * @throws {Error} If the interaction type is not recognized or supported */ function interactionFactory(client, data) { switch (data.type) { case InteractionType.Ping: return new PingInteraction(client, data); case InteractionType.ApplicationCommand: { if (data.data) { const commandData = data.data; switch (commandData.type) { case ApplicationCommandType.ChatInput: return new SlashCommandInteraction(client, data); case ApplicationCommandType.User: return new UserCommandInteraction(client, data); case ApplicationCommandType.Message: return new MessageCommandInteraction(client, data); default: return new CommandInteraction(client, data); } } return new CommandInteraction(client, data); } case InteractionType.MessageComponent: { if (data.data) { const componentData = data.data; switch (componentData.component_type) { case ComponentType.Button: return new ButtonInteraction(client, data); case ComponentType.StringSelect: case ComponentType.UserSelect: case ComponentType.RoleSelect: case ComponentType.MentionableSelect: case ComponentType.ChannelSelect: return new SelectMenuInteraction(client, data); default: return new ComponentInteraction(client, data); } } return new ComponentInteraction(client, data); } case InteractionType.ApplicationCommandAutocomplete: return new AutocompleteInteraction(client, data); case InteractionType.ModalSubmit: return new ModalSubmitInteraction(client, data); default: return new Interaction(client, data); } } //#endregion //#region src/utils/event.util.ts /** * Creates a strongly-typed mapping between Gateway and Client events */ function defineEvent(clientEvent, transform) { return { clientEvent, transform }; } /** * Generic handler for DELETE operations * Gets entity from cache and removes it */ function handleDeleteEvent(client, cacheKey, entityId) { const store = client.cache[cacheKey]; const cachedEntity = store.get(entityId) ?? null; if (cachedEntity) store.delete(entityId); return [cachedEntity]; } /** * Checks if the provided creator is a class constructor */ function isClassConstructor(creator) { return typeof creator === "function" && creator.prototype?.constructor === creator && (creator.prototype instanceof BaseClass || Object.getPrototypeOf(creator.prototype) === BaseClass.prototype); } /** * Generic handler for UPDATE operations * Creates new entity, retrieves old entity from cache, and updates cache */ function handleUpdateEvent(client, data, cacheKey, EntityFactory) { const newEntity = isClassConstructor(EntityFactory) ? new EntityFactory(client, data) : EntityFactory(client, data); const cacheInfo = newEntity.getCacheInfo(); if (!cacheInfo?.id) return [null, newEntity]; const store = client.cache[cacheKey]; const oldEntity = store.get(cacheInfo.id) ?? null; return [oldEntity, newEntity]; } /** * Efficiently handles bulk updates of entities (emojis, stickers) * Compares new elements with cached elements and processes the changes */ function handleBulkUpdate(client, data, entityField, cacheKey, EntityClass, eventNames) { const guildId = data.guild_id; const newEntities = data[entityField]; const cachedEntities = Array.from(client.cache[cacheKey].values()).filter((entity) => entity.guildId === guildId); const newMap = /* @__PURE__ */ new Map(); const cachedMap = /* @__PURE__ */ new Map(); for (const entity of newEntities) if (entity.id) newMap.set(entity.id, { ...entity, guild_id: guildId }); for (const entity of cachedEntities) if (entity.id) cachedMap.set(entity.id, entity); for (const [id, entityData] of newMap.entries()) if (!cachedMap.has(id)) { const newEntity = new EntityClass(client, entityData); client.emit(eventNames.create, newEntity); } for (const [id, entityData] of newMap.entries()) if (cachedMap.has(id)) { const [oldEntity, newEntity] = handleUpdateEvent(client, entityData, cacheKey, EntityClass); client.emit(eventNames.update, oldEntity, newEntity); } for (const [id] of cachedMap.entries()) if (!newMap.has(id)) { const [deletedEntity] = handleDeleteEvent(client, cacheKey, id); client.emit(eventNames.delete, deletedEntity); } return [data]; } /** * Maps of Gateway events to client events. * Each mapping defines how raw Gateway events are transformed into client events. */ const GatewayDispatchEventMap = new Map([ ["READY", defineEvent("ready", (client, data) => [new Ready(client, data)])], ["APPLICATION_COMMAND_PERMISSIONS_UPDATE", defineEvent("applicationCommandPermissionsUpdate", (_, data) => [data])], ["AUTO_MODERATION_RULE_CREATE", defineEvent("autoModerationRuleCreate", (client, data) => [new AutoModeration(client, data)])], ["AUTO_MODERATION_RULE_UPDATE", defineEvent("autoModerationRuleUpdate", (client, data) => handleUpdateEvent(client, data, "autoModerationRules", AutoModeration))], ["AUTO_MODERATION_RULE_DELETE", defineEvent("autoModerationRuleDelete", (client, data) => handleDeleteEvent(client, "autoModerationRules", data.id))], ["AUTO_MODERATION_ACTION_EXECUTION", defineEvent("autoModerationActionExecution", (client, data) => [new AutoModerationActionExecution(client, data)])], ["CHANNEL_CREATE", defineEvent("channelCreate", (client, data) => [channelFactory(client, data)])], ["CHANNEL_UPDATE", defineEvent("channelUpdate", (client, data) => handleUpdateEvent(client, data, "channels", channelFactory))], ["CHANNEL_DELETE", defineEvent("channelDelete", (client, data) => handleDeleteEvent(client, "channels", data.id))], ["CHANNEL_PINS_UPDATE", defineEvent("channelPinsUpdate", (_, data) => [data])], ["THREAD_CREATE", defineEvent("threadCreate", (client, data) => [channelFactory(client, data)])], ["THREAD_UPDATE", defineEvent("threadUpdate", (client, data) => handleUpdateEvent(client, data, "channels", channelFactory))], ["THREAD_DELETE", defineEvent("threadDelete", (client, data) => handleDeleteEvent(client, "channels", data.id))], ["THREAD_LIST_SYNC", defineEvent("threadListSync", (_, data) => [data])], ["THREAD_MEMBER_UPDATE", defineEvent("threadMemberUpdate", (client, data) => handleUpdateEvent(client, data, "threadMembers", ThreadMember))], ["THREAD_MEMBERS_UPDATE", defineEvent("threadMembersUpdate", (_, data) => [data])], ["ENTITLEMENT_CREATE", defineEvent("entitlementCreate", (client, data) => [new Entitlement(client, data)])], ["ENTITLEMENT_UPDATE", defineEvent("entitlementUpdate", (client, data) => handleUpdateEvent(client, data, "entitlements", Entitlement))], ["ENTITLEMENT_DELETE", defineEvent("entitlementDelete", (client, data) => handleDeleteEvent(client, "entitlements", data.id))], ["GUILD_CREATE", defineEvent("guildCreate", (client, data) => [new Guild(client, data)])], ["GUILD_UPDATE", defineEvent("guildUpdate", (client, data) => handleUpdateEvent(client, data, "guilds", Guild))], ["GUILD_DELETE", defineEvent("guildDelete", (client, data) => handleDeleteEvent(client, "guilds", data.id))], ["GUILD_AUDIT_LOG_ENTRY_CREATE", defineEvent("guildAuditLogEntryCreate", (client, data) => [new GuildAuditLogEntry(client, data)])], ["GUILD_BAN_ADD", defineEvent("guildBanAdd", (client, data) => [new Ban(client, { guild_id: data.guild_id, user: data.user, reason: null })])], ["GUILD_BAN_REMOVE", defineEvent("guildBanRemove", (client, data) => [new Ban(client, { guild_id: data.guild_id, user: data.user, reason: null })])], ["GUILD_EMOJIS_UPDATE", defineEvent("guildEmojisUpdate", (client, data) => handleBulkUpdate(client, data, "emojis", "emojis", Emoji, { create: "emojiCreate", update: "emojiUpdate", delete: "emojiDelete" }))], ["GUILD_STICKERS_UPDATE", defineEvent("guildStickersUpdate", (client, data) => handleBulkUpdate(client, data, "stickers", "stickers", Sticker, { create: "stickerCreate", update: "stickerUpdate", delete: "stickerDelete" }))], ["GUILD_MEMBER_ADD", defineEvent("guildMemberAdd", (client, data) => [new GuildMember(client, data)])], ["GUILD_MEMBER_UPDATE", defineEvent("guildMemberUpdate", (client, data) => handleUpdateEvent(client, data, "members", GuildMember))], ["GUILD_MEMBER_REMOVE", defineEvent("guildMemberRemove", (client, data) => handleDeleteEvent(client, "members", `${data.guild_id}:${data.user.id}`))], ["GUILD_MEMBERS_CHUNK", defineEvent("guildMembersChunk", (_, data) => [data])], ["GUILD_ROLE_CREATE", defineEvent("guildRoleCreate", (client, data) => [new Role(client, { guild_id: data.guild_id, ...data.role })])], ["GUILD_ROLE_UPDATE", defineEvent("guildRoleUpdate", (client, data) => handleUpdateEvent(client, { guild_id: data.guild_id, ...data.role }, "roles", Role))], ["GUILD_ROLE_DELETE", defineEvent("guildRoleDelete", (client, data) => handleDeleteEvent(client, "roles", data.role_id))], ["GUILD_SCHEDULED_EVENT_CREATE", defineEvent("guildScheduledEventCreate", (client, data) => [new ScheduledEvent(client, data)])], ["GUILD_SCHEDULED_EVENT_UPDATE", defineEvent("guildScheduledEventUpdate", (client, data) => handleUpdateEvent(client, data, "scheduledEvents", ScheduledEvent))], ["GUILD_SCHEDULED_EVENT_DELETE", defineEvent("guildScheduledEventDelete", (client, data) => handleDeleteEvent(client, "scheduledEvents", data.id))], ["GUILD_SCHEDULED_EVENT_USER_ADD", defineEvent("guildScheduledEventUserAdd", (_, data) => [data])], ["GUILD_SCHEDULED_EVENT_USER_REMOVE", defineEvent("guildScheduledEventUserRemove", (_, data) => [data])], ["GUILD_SOUNDBOARD_SOUND_CREATE", defineEvent("guildSoundboardSoundCreate", (client, data) => [new SoundboardSound(client, data)])], ["GUILD_SOUNDBOARD_SOUND_UPDATE", defineEvent("guildSoundboardSoundUpdate", (client, data) => handleUpdateEvent(client, data, "soundboards", SoundboardSound))], ["GUILD_SOUNDBOARD_SOUND_DELETE", defineEvent("guildSoundboardSoundDelete", (client, data) => handleDeleteEvent(client, "soundboards", data.sound_id))], ["GUILD_SOUNDBOARD_SOUNDS_UPDATE", defineEvent("guildSoundboardSoundsUpdate", (client, data) => { const sounds = data.soundboard_sounds.map((soundData) => new SoundboardSound(client, { ...soundData, guild_id: data.guild_id })); return [sounds]; })], ["SOUNDBOARD_SOUNDS", defineEvent("soundboardSounds", (client, data) => { const soundboardSounds = data.soundboard_sounds.map((sound) => new SoundboardSound(client, { ...sound, guild_id: data.guild_id })); return [soundboardSounds]; })], ["INTEGRATION_CREATE", defineEvent("integrationCreate", (client, data) => [new Integration(client, data)])], ["INTEGRATION_UPDATE", defineEvent("integrationUpdate", (client, data) => handleUpdateEvent(client, data, "integrations", Integration))], ["INTEGRATION_DELETE", defineEvent("integrationDelete", (client, data) => handleDeleteEvent(client, "integrations", data.id))], ["INVITE_CREATE", defineEvent("inviteCreate", (client, data) => [new Invite(client, data)])], ["INVITE_DELETE", defineEvent("inviteDelete", (client, data) => handleDeleteEvent(client, "invites", data.code))], ["MESSAGE_CREATE", defineEvent("messageCreate", (client, data) => [new Message(client, data)])], ["MESSAGE_UPDATE", defineEvent("messageUpdate", (client, data) => handleUpdateEvent(client, data, "messages", Message))], ["MESSAGE_DELETE", defineEvent("messageDelete", (client, data) => handleDeleteEvent(client, "messages", data.id))], ["MESSAGE_DELETE_BULK", defineEvent("messageDeleteBulk", (client, data) => [data.ids.map((id) => { const [message] = handleDeleteEvent(client, "messages", id); return message; })])], ["MESSAGE_REACTION_ADD", defineEvent("messageReactionAdd", (client, data) => [new MessageReaction(client, data)])], ["MESSAGE_REACTION_REMOVE", defineEvent("messageReactionRemove", (client, data) => [new MessageReaction(client, data)])], ["MESSAGE_REACTION_REMOVE_ALL", defineEvent("messageReactionRemoveAll", (client, data) => [new MessageReactionRemoveAll(client, data)])], ["MESSAGE_REACTION_REMOVE_EMOJI", defineEvent("messageReactionRemoveEmoji", (client, data) => [new MessageReactionRemoveEmoji(client, data)])], ["MESSAGE_POLL_VOTE_ADD", defineEvent("messagePollVoteAdd", (client, data) => [new MessagePollVote(client, data)])], ["MESSAGE_POLL_VOTE_REMOVE", defineEvent("messagePollVoteRemove", (client, data) => [new MessagePollVote(client, data)])], ["TYPING_START", defineEvent("typingStart", (_, data) => [data])], ["USER_UPDATE", defineEvent("userUpdate", (client, data) => handleUpdateEvent(client, data, "users", User))], ["VOICE_CHANNEL_EFFECT_SEND", defineEvent("voiceChannelEffectSend", (client, data) => [new VoiceChannelEffect(client, data)])], ["VOICE_STATE_UPDATE", defineEvent("voiceStateUpdate", (client, data) => handleUpdateEvent(client, data, "voiceStates", VoiceState))], ["VOICE_SERVER_UPDATE", defineEvent("voiceServerUpdate", (_, data) => [data])], ["WEBHOOKS_UPDATE", defineEvent("webhooksUpdate", (client, data) => handleUpdateEvent(client, data, "webhooks", Webhook))], ["INTERACTION_CREATE", defineEvent("interactionCreate", (client, data) => [interactionFactory(client, data)])], ["STAGE_INSTANCE_CREATE", defineEvent("stageInstanceCreate", (client, data) => [new StageInstance(client, data)])], ["STAGE_INSTANCE_UPDATE", defineEvent("stageInstanceUpdate", (client, data) => handleUpdateEvent(client, data, "stageInstances", StageInstance))], ["STAGE_INSTANCE_DELETE", defineEvent("stageInstanceDelete", (client, data) => handleDeleteEvent(client, "stageInstances", data.id))], ["SUBSCRIPTION_CREATE", defineEvent("subscriptionCreate", (client, data) => [new Subscription(client, data)])], ["SUBSCRIPTION_UPDATE", defineEvent("subscriptionUpdate", (client, data) => handleUpdateEvent(client, data, "subscriptions", Subscription))], ["SUBSCRIPTION_DELETE", defineEvent("subscriptionDelete", (client, data) => handleDeleteEvent(client, "subscriptions", data.id))] ]); /** * Events to forward directly from REST client to main client */ const RestKeyofEventMappings = [ "request", "rateLimitHit", "rateLimitUpdate", "rateLimitExpire", "retry" ]; /** * Events to forward directly from Gateway client to main client */ const GatewayKeyofEventMappings = [ "heartbeatSent", "heartbeatAcknowledge", "heartbeatTimeout", "sessionStart", "sessionResume", "sessionInvalidate", "shardResume", "shardReconnect", "shardReady", "shardDisconnect", "wsClose", "wsError", "dispatch", "sequenceUpdate", "wsOpen", "wsMessage" ]; //#endregion //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/decorate.js var require_decorate = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/decorate.js"(exports, module) { function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } module.exports = __decorate, module.exports.__esModule = true, module.exports["default"] = module.exports; } }); //#endregion //#region src/classes/sticker.class.ts var import_decorate$16 = __toESM(require_decorate(), 1); let Sticker = class Sticker$1 extends BaseClass { /** * Gets the unique identifier (Snowflake) of this sticker. * * This ID is used for API operations and remains constant for the lifetime of the sticker. * * @returns The sticker's ID as a Snowflake string */ id = this.rawData.id; /** * Gets the ID of the sticker pack this sticker is from, if applicable. * * Only present for standard stickers that are part of an official Discord sticker pack. * * @returns The pack's ID as a Snowflake string, or undefined if not from a pack */ packId = this.rawData.pack_id; /** * Gets the name of this sticker. * * This is the display name shown in the Discord client (2-30 characters). * * @returns The sticker name as a string */ name = this.rawData.name; /** * Gets the description of this sticker. * * This is a short description of what the sticker depicts (0-100 characters). * * @returns The sticker description as a string, or null if not set */ description = this.rawData.description; /** * Gets the autocomplete/suggestion tags for this sticker. * * These are comma-separated keywords used for search and suggestions. * * @returns The tags as a string */ tags = this.rawData.tags; /** * Gets the sticker asset hash. * * Previously this was a hashed value, now it's an empty string. * Included for backward compatibility. * * @returns The asset value as a string, or undefined if not available * @deprecated This property is no longer used by Discord */ asset = this.rawData.asset; /** * Gets the type of this sticker. * * Indicates whether this is a standard sticker from a pack or a guild-specific sticker. * * @returns The sticker type enum value * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-types} */ type = this.rawData.type; /** * Gets the format type of this sticker. * * Determines the file format of the sticker (PNG, APNG, Lottie, or GIF). * * @returns The format type enum value * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types} */ formatType = this.rawData.format_type; /** * Indicates whether this guild sticker can be used by Nitro users in other guilds. * * Only applicable to guild stickers. * * @returns True if the sticker is available for use by Nitro users across guilds, undefined if not applicable */ available = this.rawData.available; /** * Gets the ID of the guild that owns this sticker. * * Only present for guild stickers. * * @returns The guild's ID as a Snowflake string, or undefined if not a guild sticker */ guildId = this.rawData.guild_id; /** * Gets the user that uploaded the guild sticker. * * Only present for guild stickers and when the current user has the * MANAGE_GUILD_EXPRESSIONS permission. * * @returns The User object, or undefined if not available */ user = this.rawData.user ? new User(this.client, this.rawData.user) : void 0; /** * Gets the sort order within the sticker pack. * * Only applicable to standard stickers in packs. * * @returns The sort value as a number, or undefined if not applicable */ sortValue = this.rawData.sort_value; /** * Checks if this is a standard sticker from an official pack. * * @returns True if this is a standard sticker, false otherwise */ get isStandard() { return this.type === StickerType.Standard; } /** * Checks if this is a guild-specific sticker. * * @returns True if this is a guild sticker, false otherwise */ get isGuildSticker() { return this.type === StickerType.Guild; } /** * Gets the URL to this sticker's image. * * @returns The URL to the sticker image */ url(options) { return Cdn.sticker(this.id, options); } /** * Updates this sticker with new information. * * Only works for guild stickers. Requires the MANAGE_GUILD_EXPRESSIONS permission. * * @param options - Options for updating the sticker * @param reason - Optional audit log reason for the update * @returns A promise resolving to the updated Sticker * @throws Error if the sticker couldn't be updated or is not a guild sticker * @see {@link https://discord.com/developers/docs/resources/sticker#modify-guild-sticker} */ async update(options, reason) { if (!this.guildId) throw new Error("Cannot update standard stickers"); const updatedSticker = await this.client.rest.stickers.updateGuildSticker(this.guildId, this.id, options, reason); this.patch(updatedSticker); return this; } /** * Deletes this sticker. * * Only works for guild stickers. Requires the MANAGE_GUILD_EXPRESSIONS permission. * * @param reason - Optional audit log reason for the deletion * @returns A promise that resolves when the sticker is deleted * @throws Error if the sticker couldn't be deleted or is not a guild sticker * @see {@link https://discord.com/developers/docs/resources/sticker#delete-guild-sticker} */ async delete(reason) { if (!this.guildId) throw new Error("Cannot delete standard stickers"); await this.client.rest.stickers.deleteGuildSticker(this.guildId, this.id, reason); this.uncache(); } /** * Refreshes this sticker's data from the API. * * @returns A promise resolving to the updated Sticker * @throws Error if the sticker couldn't be fetched */ async refresh() { let stickerData; if (this.guildId) stickerData = await this.client.rest.stickers.fetchGuildSticker(this.guildId, this.id); else stickerData = await this.client.rest.stickers.fetchSticker(this.id); this.patch(stickerData); return this; } /** * Sets a new name for this sticker. * * Only works for guild stickers. * * @param name - The new name for the sticker (2-30 characters) * @param reason - Optional audit log reason for the change * @returns A promise resolving to the updated Sticker * @throws Error if the name couldn't be updated or sticker is not a guild sticker */ setName(name, reason) { return this.update({ name }, reason); } /** * Sets a new description for this sticker. * * Only works for guild stickers. * * @param description - The new description for the sticker (0-100 characters) * @param reason - Optional audit log reason for the change * @returns A promise resolving to the updated Sticker * @throws Error if the description couldn't be updated or sticker is not a guild sticker */ setDescription(description, reason) { return this.update({ description }, reason); } /** * Sets new tags for this sticker. * * Only works for guild stickers. * * @param tags - The new comma-separated tags for the sticker (max 200 characters) * @param reason - Optional audit log reason for the change * @returns A promise resolving to the updated Sticker * @throws Error if the tags couldn't be updated or sticker is not a guild sticker */ setTags(tags, reason) { return this.update({ tags }, reason); } }; Sticker = (0, import_decorate$16.default)([Cacheable("stickers")], Sticker); /** * Represents a lightweight Discord Sticker Item, providing basic information for stickers in messages. * * The StickerItem class represents the smallest amount of data required to render a sticker. * It is used in contexts where the full sticker data isn't needed, such as in messages. * This lightweight representation includes only essential display information like ID, name, and format. * * This class transforms snake_case API responses into camelCase properties for * a more JavaScript-friendly interface while maintaining type safety. * * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-item-object} */ var StickerItem = class extends BaseClass { /** * Gets the unique identifier (Snowflake) of this sticker. * * This ID can be used to retrieve the full sticker information if needed. * * @returns The sticker's ID as a Snowflake string */ id = this.rawData.id; /** * Gets the name of this sticker. * * This is the display name shown in the Discord client. * * @returns The sticker name as a string */ name = this.rawData.name; /** * Gets the format type of this sticker. * * Determines the file format of the sticker (PNG, APNG, Lottie, or GIF). * * @returns The format type enum value * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-object-sticker-format-types} */ formatType = this.rawData.format_type; /** * Fetches the complete sticker information. * * This method retrieves the full Sticker object with additional details * that aren't included in the StickerItem. * * @returns A promise resolving to the complete Sticker object * @throws Error if the sticker couldn't be fetched */ async fetchSticker() { const stickerData = await this.client.rest.stickers.fetchSticker(this.id); return new Sticker(this.client, stickerData); } /** * Checks if this sticker is an animated sticker. * * @returns True if the sticker is animated (APNG, Lottie, or GIF), false otherwise */ isAnimated() { return this.formatType === StickerFormatType.Apng || this.formatType === StickerFormatType.Lottie || this.formatType === StickerFormatType.Gif; } /** * Checks if this sticker is a Lottie sticker. * * Lottie stickers are vector-based animation format that enables smaller file sizes. * They can only be uploaded to guilds with VERIFIED and/or PARTNERED features. * * @returns True if this is a Lottie sticker, false otherwise */ isLottie() { return this.formatType === StickerFormatType.Lottie; } /** * Checks if this sticker is a PNG or APNG sticker. * * @returns True if this is a PNG or APNG sticker, false otherwise */ isPng() { return this.formatType === StickerFormatType.Png || this.formatType === StickerFormatType.Apng; } /** * Checks if this sticker is a GIF sticker. * * @returns True if this is a GIF sticker, false otherwise */ isGif() { return this.formatType === StickerFormatType.Gif; } }; /** * Represents a Discord Sticker Pack, providing access to collections of official stickers. * * The StickerPack class encapsulates a collection of standard stickers provided by Discord. * Sticker packs are collections of related stickers that may be available to users based on * various factors like Nitro subscription status. * * This class transforms snake_case API responses into camelCase properties for * a more JavaScript-friendly interface while maintaining type safety. * * @see {@link https://discord.com/developers/docs/resources/sticker#sticker-pack-object} */ var StickerPack = class extends BaseClass { /** * Gets the unique identifier (Snowflake) of this sticker pack. * * This ID is used for API operations and remains constant for the lifetime of the pack. * * @returns The sticker pack's ID as a Snowflake string */ id = this.rawData.id; /** * Gets the stickers contained in this pack. * * This is an array of complete sticker objects available in this pack. * * @returns An array of Sticker objects */ stickers = this.rawData.stickers.map((sticker) => new Sticker(this.client, sticker)); /** * Gets the name of this sticker pack. * * This is the display name shown in the Discord client. * * @returns The sticker pack name as a string */ name = this.rawData.name; /** * Gets the ID of this pack's SKU. * * This links the sticker pack to its purchasable SKU. * * @returns The SKU ID as a Snowflake string */ skuId = this.rawData.sku_id; /** * Gets the ID of a sticker in the pack which is shown as the pack's icon. * * This sticker is used as the visual representation of the pack in the Discord client. * * @returns The cover sticker's ID as a Snowflake string, or undefined if not set */ coverStickerId = this.rawData.cover_sticker_id; /** * Gets the description of this sticker pack. * * This is a brief explanation of the sticker pack's theme or contents. * * @returns The description as a string */ description = this.rawData.description; /** * Gets the ID of this pack's banner image. * * This is used as the background when viewing the pack in the Discord client. * * @returns The banner asset ID as a Snowflake string, or undefined if not set */ bannerAssetId = this.rawData.banner_asset_id; /** * Gets the number of stickers in this pack. * * @returns The count of stickers in the pack */ stickerCount = this.stickers.length; /** * Gets the cover sticker for this pack. * * This is the sticker that's used as the visual representation of the pack. * * @returns The cover Sticker object, or undefined if not set */ coverSticker = this.stickers.find((sticker) => sticker.id === this.coverStickerId); /** * Gets the URL to this sticker pack's banner image, if available. * * @returns The URL to the banner image, or null if no banner asset is set */ bannerUrl(options) { if (!this.bannerAssetId) return null; return Cdn.stickerPackBanner(this.bannerAssetId, options); } /** * Refreshes this sticker pack's data from the API. * * @returns A promise resolving to the updated StickerPack * @throws Error if the sticker pack couldn't be fetched */ async refresh() { const packData = await this.client.rest.stickers.fetchStickerPack(this.id); this.patch(packData); return this; } /** * Gets a specific sticker from this pack by its ID. * * @param stickerId - The ID of the sticker to retrieve * @returns The Sticker object if found, or undefined if not in this pack */ getSticker(stickerId) { return this.stickers.find((sticker) => sticker.id === stickerId); } /** * Gets a specific sticker from this pack by its name. * * @param name - The name of the sticker to retrieve * @returns The first matching Sticker object if found, or undefined if not in this pack */ getStickerByName(name) { return this.stickers.find((sticker) => sticker.name.toLowerCase() === name.toLowerCase()); } /** * Searches for stickers in this pack that match a query string. * * This searches through sticker names and tags to find relevant matches. * * @param query - The search query to match against sticker names and tags * @returns An array of matching Sticker objects, or an empty array if none match */ searchStickers(query) { const lowercaseQuery = query.toLowerCase(); return this.stickers.filter((sticker) => { const nameMatch = sticker.name.toLowerCase().includes(lowercaseQuery); const tagsMatch = sticker.tags.toLowerCase().includes(lowercaseQuery); return nameMatch || tagsMatch; }); } }; //#endregion //#region src/classes/message.class.ts var import_decorate$15 = __toESM(require_decorate(), 1); var _Message; /** * Represents a Discord Message Reaction Add/Remove event, providing methods to interact with reaction events. * * The MessageReaction class serves as a wrapper around Discord's Reaction Gateway events, * which track when users add or remove reactions from messages. It provides: * - Access to reaction information (user, emoji, message, etc.) * - Methods to manage reactions (remove, get reactors) * - Utilities for emoji formatting and reaction analysis * * This is used for both message_reaction_add and message_reaction_remove Gateway events. * * @see {@link https://discord.com/developers/docs/events/gateway-events#message-reaction-add} */ var MessageReaction = class extends BaseClass { /** * Gets the ID of the user who added or removed the reaction. * * This identifies which user performed the reaction action. * * @returns The user's ID as a Snowflake string */ userId = this.rawData.user_id; /** * Gets the ID of the channel containing the message. * * This identifies which channel contains the reacted message. * * @returns The channel's ID as a Snowflake string */ channelId = this.rawData.channel_id; /** * Gets the ID of the message that received the reaction. * * This identifies which message was reacted to. * * @returns The message's ID as a Snowflake string */ messageId = this.rawData.message_id; /** * Gets the ID of the guild containing the message. * * This identifies which guild the message belongs to, if applicable. * May be undefined for reactions in DM channels. * * @returns The guild's ID as a Snowflake string, or undefined for DMs */ guildId = this.rawData.guild_id; /** * Gets the emoji information for the reaction. * * Contains the ID, name, and animated status of the emoji. * * @returns The emoji object */ emoji = new Emoji(this.client, { ...this.rawData.emoji, guild_id: this.guildId }); /** * Indicates whether this is a super-reaction (Nitro burst reaction). * * @returns True if this is a super-reaction, false otherwise */ burst = this.rawData.burst; /** * Gets the type of the reaction. * * Identifies the reaction's category (standard, super, etc.). * * @returns The reaction type */ type = this.rawData.type; /** * Gets the array of hexadecimal color codes used for super-reaction animation. * * Each color is in "#rrggbb" format. Only present for super-reactions. * * @returns Array of color strings, or undefined if not a super-reaction */ burstColors = "burst_colors" in this.rawData ? this.rawData.burst_colors : void 0; /** * Gets the ID of the user who authored the message which was reacted to. * * Useful for tracking reactions to specific users' messages. * * @returns The message author's ID, or undefined if not available */ messageAuthorId = "message_author_id" in this.rawData ? this.rawData.message_author_id : void 0; /** * Gets the guild member object for the user who added the reaction. * * Only present for reactions in guild channels. * * @returns The GuildMember object, or undefined if not available */ get member() { if (!("member" in this.rawData && this.rawData.member && this.guildId)) return void 0; const memberWithGuild = { ...this.rawData.member, guild_id: this.guildId }; return new GuildMember(this.client, memberWithGuild); } /** * Fetches the message that was reacted to. * * @returns A promise resolving to the Message object * @throws Error if the message couldn't be fetched */ async fetchMessage() { const messageData = await this.client.rest.messages.fetchMessage(this.channelId, this.messageId); return new Message(this.client, messageData); } /** * Fetches the user who added/removed the reaction. * * @returns A promise resolving to the User object * @throws Error if the user couldn't be fetched */ async fetchUser() { const userData = await this.client.rest.users.fetchUser(this.userId); return new User(this.client, userData); } /** * Fetches all users who have reacted with the same emoji. * * @param params - Query parameters for pagination and filtering * @returns A promise resolving to an array of User objects who reacted with this emoji * @throws Error if the users couldn't be fetched */ async fetchReactionUsers(params) { const emojiString = this.emoji.id ? `${this.emoji.name}:${this.emoji.id}` : encodeURIComponent(this.emoji.name || ""); const users = await this.client.rest.messages.fetchReactionUsers(this.channelId, this.messageId, emojiString, params); return users.map((userData) => new User(this.client, userData)); } /** * Removes this specific reaction from the message. * * If called without parameters, removes the current user's reaction. * If a userId is provided, removes that user's reaction (requires MANAGE_MESSAGES permission). * * @param userId - Optional user ID to remove reaction for * @returns A promise that resolves when the reaction is removed * @throws Error if the reaction couldn't be removed */ removeReaction(userId) { const emojiString = this.emoji.id ? `${this.emoji.name}:${this.emoji.id}` : encodeURIComponent(this.emoji.name || ""); if (userId) return this.client.rest.messages.removeUserReaction(this.channelId, this.messageId, emojiString, userId); return this.client.rest.messages.removeOwnReaction(this.channelId, this.messageId, emojiString); } /** * Removes all reactions of this emoji from the message. * * @returns A promise that resolves when the reactions are removed * @throws Error if the reactions couldn't be removed */ removeAllReactionsForEmoji() { const emojiString = this.emoji.id ? `${this.emoji.name}:${this.emoji.id}` : encodeURIComponent(this.emoji.name || ""); return this.client.rest.messages.removeEmojiReactions(this.channelId, this.messageId, emojiString); } /** * Adds the current user's reaction with the same emoji. * * This allows the bot to add the same reaction that was observed. * * @returns A promise that resolves when the reaction is added * @throws Error if the reaction couldn't be added */ react() { const emojiString = this.emoji.id ? `${this.emoji.name}:${this.emoji.id}` : encodeURIComponent(this.emoji.name || ""); return this.client.rest.messages.addReaction(this.channelId, this.messageId, emojiString); } }; /** * Represents a Discord Message Reaction Remove All event, providing methods for bulk reaction removals. * * This class handles events when all reactions are removed from a message at once. * * @see {@link https://discord.com/developers/docs/events/gateway-events#message-reaction-remove-all} */ var MessageReactionRemoveAll = class extends BaseClass { /** * Gets the ID of the channel containing the message. * * This identifies which channel contains the message. * * @returns The channel's ID as a Snowflake string */ channelId = this.rawData.channel_id; /** * Gets the ID of the message that had all reactions removed. * * This identifies which message had its reactions cleared. * * @returns The message's ID as a Snowflake string */ messageId = this.rawData.message_id; /** * Gets the ID of the guild containing the message. * * This identifies which guild the message belongs to, if applicable. * May be undefined for messages in DM channels. * * @returns The guild's ID as a Snowflake string, or undefined for DMs */ guildId = this.rawData.guild_id; /** * Fetches the message that had all reactions removed. * * @returns A promise resolving to the Message object * @throws Error if the message couldn't be fetched */ async fetchMessage() { const messageData = await this.client.rest.messages.fetchMessage(this.channelId, this.messageId); return new Message(this.client, messageData); } }; /** * Represents a Discord Message Reaction Remove Emoji event, handling removal of a specific emoji type. * * This class handles events when all reactions of a specific emoji are removed from a message. * * @see {@link https://discord.com/developers/docs/events/gateway-events#message-reaction-remove-emoji} */ var MessageReactionRemoveEmoji = class extends BaseClass { /** * Gets the ID of the channel containing the message. * * This identifies which channel contains the message. * * @returns The channel's ID as a Snowflake string */ channelId = this.rawData.channel_id; /** * Gets the ID of the guild containing the message. * * This identifies which guild the message belongs to, if applicable. * May be undefined for messages in DM channels. * * @returns The guild's ID as a Snowflake string, or undefined for DMs */ guildId = this.rawData.guild_id; /** * Gets the ID of the message that had reactions removed. * * This identifies which message had its reactions modified. * * @returns The message's ID as a Snowflake string */ messageId = this.rawData.message_id; /** * Gets the partial emoji object for the removed emoji. * * Contains only the essential information needed to identify the emoji. * * @returns The partial emoji object */ emoji = new Emoji(this.client, { ...this.rawData.emoji, guild_id: this.guildId }); /** * Fetches the message that had reactions removed. * * @returns A promise resolving to the Message object * @throws Error if the message couldn't be fetched */ async fetchMessage() { const messageData = await this.client.rest.messages.fetchMessage(this.channelId, this.messageId); return new Message(this.client, messageData); } }; let Message = _Message = class Message$1 extends BaseClass { /** * Gets the unique iden