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
JavaScript
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