Downgrade to v13

[vi] cảm giác đau khổ
This commit is contained in:
March 7th
2022-03-24 17:55:32 +07:00
parent 9596b1a210
commit 7dfdef46a5
218 changed files with 8584 additions and 9108 deletions

44
src/util/ActivityFlags.js Normal file
View File

@@ -0,0 +1,44 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with an {@link Activity#flags} bitfield.
* @extends {BitField}
*/
class ActivityFlags extends BitField {}
/**
* @name ActivityFlags
* @kind constructor
* @memberof ActivityFlags
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Numeric activity flags. All available properties:
* * `INSTANCE`
* * `JOIN`
* * `SPECTATE`
* * `JOIN_REQUEST`
* * `SYNC`
* * `PLAY`
* * `PARTY_PRIVACY_FRIENDS`
* * `PARTY_PRIVACY_VOICE_CHANNEL`
* * `EMBEDDED`
* @type {Object}
* @see {@link https://discord.com/developers/docs/topics/gateway#activity-object-activity-flags}
*/
ActivityFlags.FLAGS = {
INSTANCE: 1 << 0,
JOIN: 1 << 1,
SPECTATE: 1 << 2,
JOIN_REQUEST: 1 << 3,
SYNC: 1 << 4,
PLAY: 1 << 5,
PARTY_PRIVACY_FRIENDS: 1 << 6,
PARTY_PRIVACY_VOICE_CHANNEL: 1 << 7,
EMBEDDED: 1 << 8,
};
module.exports = ActivityFlags;

View File

@@ -1,25 +0,0 @@
'use strict';
const { ActivityFlags } = require('discord-api-types/v9');
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with an {@link Activity#flags} bitfield.
* @extends {BitField}
*/
class ActivityFlagsBitField extends BitField {}
/**
* @name ActivityFlagsBitField
* @kind constructor
* @memberof ActivityFlagsBitField
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Numeric activity flags.
* @type {ActivityFlags}
*/
ActivityFlagsBitField.Flags = ActivityFlags;
module.exports = ActivityFlagsBitField;

View File

@@ -0,0 +1,48 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link ClientApplication#flags} bitfield.
* @extends {BitField}
*/
class ApplicationFlags extends BitField {}
/**
* @name ApplicationFlags
* @kind constructor
* @memberof ApplicationFlags
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name ApplicationFlags#bitfield
*/
/**
* Numeric application flags. All available properties:
* * `GATEWAY_PRESENCE`
* * `GATEWAY_PRESENCE_LIMITED`
* * `GATEWAY_GUILD_MEMBERS`
* * `GATEWAY_GUILD_MEMBERS_LIMITED`
* * `VERIFICATION_PENDING_GUILD_LIMIT`
* * `EMBEDDED`
* * `GATEWAY_MESSAGE_CONTENT`
* * `GATEWAY_MESSAGE_CONTENT_LIMITED`
* @type {Object}
* @see {@link https://discord.com/developers/docs/resources/application#application-object-application-flags}
*/
ApplicationFlags.FLAGS = {
GATEWAY_PRESENCE: 1 << 12,
GATEWAY_PRESENCE_LIMITED: 1 << 13,
GATEWAY_GUILD_MEMBERS: 1 << 14,
GATEWAY_GUILD_MEMBERS_LIMITED: 1 << 15,
VERIFICATION_PENDING_GUILD_LIMIT: 1 << 16,
EMBEDDED: 1 << 17,
GATEWAY_MESSAGE_CONTENT: 1 << 18,
GATEWAY_MESSAGE_CONTENT_LIMITED: 1 << 19,
};
module.exports = ApplicationFlags;

View File

@@ -1,31 +0,0 @@
'use strict';
const { ApplicationFlags } = require('discord-api-types/v9');
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link ClientApplication#flags} bitfield.
* @extends {BitField}
*/
class ApplicationFlagsBitField extends BitField {}
/**
* @name ApplicationFlagsBitField
* @kind constructor
* @memberof ApplicationFlagsBitField
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name ApplicationFlagsBitField#bitfield
*/
/**
* Numeric application flags. All available properties:
* @type {ApplicationFlags}
*/
ApplicationFlagsBitField.Flags = ApplicationFlags;
module.exports = ApplicationFlagsBitField;

View File

@@ -101,7 +101,7 @@ class BitField {
*/
serialize(...hasParams) {
const serialized = {};
for (const [flag, bit] of Object.entries(this.constructor.Flags)) serialized[flag] = this.has(bit, ...hasParams);
for (const [flag, bit] of Object.entries(this.constructor.FLAGS)) serialized[flag] = this.has(bit, ...hasParams);
return serialized;
}
@@ -111,7 +111,7 @@ class BitField {
* @returns {string[]}
*/
toArray(...hasParams) {
return Object.keys(this.constructor.Flags).filter(bit => this.has(bit, ...hasParams));
return Object.keys(this.constructor.FLAGS).filter(bit => this.has(bit, ...hasParams));
}
toJSON() {
@@ -128,7 +128,7 @@ class BitField {
/**
* Data that can be resolved to give a bitfield. This can be:
* * A bit number (this can be a number literal or a value taken from {@link BitField.Flags})
* * A bit number (this can be a number literal or a value taken from {@link BitField.FLAGS})
* * A string bit number
* * An instance of BitField
* * An Array of BitFieldResolvable
@@ -146,7 +146,7 @@ class BitField {
if (bit instanceof BitField) return bit.bitfield;
if (Array.isArray(bit)) return bit.map(p => this.resolve(p)).reduce((prev, p) => prev | p, defaultBit);
if (typeof bit === 'string') {
if (typeof this.Flags[bit] !== 'undefined') return this.Flags[bit];
if (typeof this.FLAGS[bit] !== 'undefined') return this.FLAGS[bit];
if (!isNaN(bit)) return typeof defaultBit === 'bigint' ? BigInt(bit) : Number(bit);
}
throw new RangeError('BITFIELD_INVALID', bit);
@@ -159,7 +159,7 @@ class BitField {
* @type {Object}
* @abstract
*/
BitField.Flags = {};
BitField.FLAGS = {};
/**
* @type {number|bigint}

View File

@@ -1,34 +0,0 @@
'use strict';
module.exports = {
Default: 0x000000,
White: 0xffffff,
Aqua: 0x1abc9c,
Green: 0x57f287,
Blue: 0x3498db,
Yellow: 0xfee75c,
Purple: 0x9b59b6,
LuminousVividPink: 0xe91e63,
Fuchsia: 0xeb459e,
Gold: 0xf1c40f,
Orange: 0xe67e22,
Red: 0xed4245,
Grey: 0x95a5a6,
Navy: 0x34495e,
DarkAqua: 0x11806a,
DarkGreen: 0x1f8b4c,
DarkBlue: 0x206694,
DarkPurple: 0x71368a,
DarkVividPink: 0xad1457,
DarkGold: 0xc27c0e,
DarkOrange: 0xa84300,
DarkRed: 0x992d22,
DarkGrey: 0x979c9f,
DarkerGrey: 0x7f8c8d,
LightGrey: 0xbcc0c0,
DarkNavy: 0x2c3e50,
Blurple: 0x5865f2,
Greyple: 0x99aab5,
DarkButNotBlack: 0x2c2f33,
NotQuiteBlack: 0x23272a,
};

View File

@@ -1,44 +0,0 @@
'use strict';
/**
* @typedef {Object} BaseComponentData
* @property {ComponentType} type
*/
/**
* @typedef {BaseComponentData} ActionRowData
* @property {ComponentData[]} components
*/
/**
* @typedef {BaseComponentData} ButtonComponentData
* @property {ButtonStyle} style
* @property {?boolean} disabled
* @property {string} label
* @property {?APIComponentEmoji} emoji
* @property {?string} customId
* @property {?string} url
*/
/**
* @typedef {object} SelectMenuComponentOptionData
* @property {string} label
* @property {string} value
* @property {?string} description
* @property {?APIComponentEmoji} emoji
* @property {?boolean} default
*/
/**
* @typedef {BaseComponentData} SelectMenuComponentData
* @property {string} customId
* @property {?boolean} disabled
* @property {?number} maxValues
* @property {?number} minValues
* @property {?SelectMenuComponentOptionData[]} options
* @property {?string} placeholder
*/
/**
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} ComponentData
*/

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
'use strict';
const { Buffer } = require('node:buffer');
const fs = require('node:fs/promises');
const fs = require('node:fs');
const path = require('node:path');
const stream = require('node:stream');
const { fetch } = require('undici');
const fetch = require('node-fetch');
const { Error: DiscordError, TypeError } = require('../errors');
const Invite = require('../structures/Invite');
@@ -66,8 +66,8 @@ class DataResolver extends null {
if (typeof image === 'string' && image.startsWith('data:')) {
return image;
}
const file = await this.resolveFile(image);
return this.resolveBase64(file);
const file = await this.resolveFileAsBuffer(image);
return DataResolver.resolveBase64(file);
}
/**
@@ -102,34 +102,44 @@ class DataResolver extends null {
*/
/**
* Resolves a BufferResolvable to a Buffer.
* Resolves a BufferResolvable to a Buffer or a Stream.
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
* @returns {Promise<Buffer>}
* @returns {Promise<Buffer|Stream>}
*/
static async resolveFile(resource) {
if (Buffer.isBuffer(resource)) return resource;
if (resource instanceof stream.Readable) {
const buffers = [];
for await (const data of resource) buffers.push(data);
return Buffer.concat(buffers);
}
if (Buffer.isBuffer(resource) || resource instanceof stream.Readable) return resource;
if (typeof resource === 'string') {
if (/^https?:\/\//.test(resource)) {
const res = await fetch(resource);
return Buffer.from(await res.arrayBuffer());
return res.body;
}
const file = path.resolve(resource);
const stats = await fs.stat(file);
if (!stats.isFile()) throw new DiscordError('FILE_NOT_FOUND', file);
return fs.readFile(file);
return new Promise((resolve, reject) => {
const file = path.resolve(resource);
fs.stat(file, (err, stats) => {
if (err) return reject(err);
if (!stats.isFile()) return reject(new DiscordError('FILE_NOT_FOUND', file));
return resolve(fs.createReadStream(file));
});
});
}
throw new TypeError('REQ_RESOURCE_TYPE');
}
/**
* Resolves a BufferResolvable to a Buffer.
* @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve
* @returns {Promise<Buffer>}
*/
static async resolveFileAsBuffer(resource) {
const file = await this.resolveFile(resource);
if (Buffer.isBuffer(file)) return file;
const buffers = [];
for await (const data of file) buffers.push(data);
return Buffer.concat(buffers);
}
}
module.exports = DataResolver;

View File

@@ -1,48 +0,0 @@
'use strict';
/**
* @typedef {Object} EmbedData
* @property {?string} title
* @property {?EmbedType} type
* @property {?string} description
* @property {?string} url
* @property {?string} timestamp
* @property {?number} color
* @property {?EmbedFooterData} footer
* @property {?EmbedImageData} image
* @property {?EmbedImageData} thumbnail
* @property {?EmbedProviderData} provider
* @property {?EmbedAuthorData} author
* @property {?EmbedFieldData[]} fields
*/
/**
* @typedef {Object} EmbedFooterData
* @property {string} text
* @property {?string} iconURL
*/
/**
* @typedef {Object} EmbedImageData
* @property {?string} url
*/
/**
* @typedef {Object} EmbedProviderData
* @property {?string} name
* @property {?string} url
*/
/**
* @typedef {Object} EmbedAuthorData
* @property {string} name
* @property {?string} url
* @property {?string} iconURL
*/
/**
* @typedef {Object} EmbedFieldData
* @property {string} name
* @property {string} value
* @property {?boolean} inline
*/

View File

@@ -1,819 +0,0 @@
'use strict';
const {
ApplicationCommandType,
InteractionType,
ComponentType,
ButtonStyle,
ApplicationCommandOptionType,
ChannelType,
ApplicationCommandPermissionType,
MessageType,
GuildNSFWLevel,
GuildVerificationLevel,
GuildDefaultMessageNotifications,
GuildExplicitContentFilter,
GuildPremiumTier,
GuildScheduledEventStatus,
StageInstancePrivacyLevel,
GuildMFALevel,
TeamMemberMembershipState,
GuildScheduledEventEntityType,
IntegrationExpireBehavior,
AuditLogEvent,
} = require('discord-api-types/v9');
function unknownKeyStrategy(val) {
throw new Error(`Could not resolve enum value for ${val}`);
}
/**
* Holds a bunch of methods to resolve enum values to readable strings.
*/
class EnumResolvers extends null {
/**
* A string that can be resolved to a {@link ChannelType} enum value. Here are the available types:
* * GUILD_TEXT
* * DM
* * GUILD_VOICE
* * GROUP_DM
* * GUILD_CATEGORY
* * GUILD_NEWS
* * GUILD_NEWS_THREAD
* * GUILD_PUBLIC_THREAD
* * GUILD_PRIVATE_THREAD
* * GUILD_STAGE_VOICE
* @typedef {string} ChannelTypeEnumResolvable
*/
/**
* Resolves enum key to {@link ChannelType} enum value
* @param {ChannelTypeEnumResolvable|ChannelType} key The key to resolve
* @returns {ChannelType}
*/
static resolveChannelType(key) {
switch (key) {
case 'GUILD_TEXT':
return ChannelType.GuildText;
case 'DM':
return ChannelType.DM;
case 'GUILD_VOICE':
return ChannelType.GuildVoice;
case 'GROUP_DM':
return ChannelType.GroupDM;
case 'GUILD_CATEGORY':
return ChannelType.GuildCategory;
case 'GUILD_NEWS':
return ChannelType.GuildNews;
case 'GUILD_NEWS_THREAD':
return ChannelType.GuildNewsThread;
case 'GUILD_PUBLIC_THREAD':
return ChannelType.GuildPublicThread;
case 'GUILD_PRIVATE_THREAD':
return ChannelType.GuildPrivateThread;
case 'GUILD_STAGE_VOICE':
return ChannelType.GuildStageVoice;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to an {@link InteractionType} enum value. Here are the available types:
* * PING
* * APPLICATION_COMMAND
* * MESSAGE_COMPONENT
* * APPLICATION_COMMAND_AUTOCOMPLETE
* @typedef {string} InteractionTypeEnumResolvable
*/
/**
* Resolves enum key to {@link InteractionType} enum value
* @param {InteractionTypeEnumResolvable|InteractionType} key The key to resolve
* @returns {InteractionType}
*/
static resolveInteractionType(key) {
switch (key) {
case 'PING':
return InteractionType.Ping;
case 'APPLICATION_COMMAND':
return InteractionType.ApplicationCommand;
case 'MESSAGE_COMPONENT':
return InteractionType.MessageComponent;
case 'APPLICATION_COMMAND_AUTOCOMPLETE':
return InteractionType.ApplicationCommandAutocomplete;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to an {@link ApplicationCommandType} enum value. Here are the available types:
* * CHAT_INPUT
* * USER
* * MESSAGE
* @typedef {string} ApplicationCommandTypeEnumResolvable
*/
/**
* Resolves enum key to {@link ApplicationCommandType} enum value
* @param {ApplicationCommandTypeEnumResolvable|ApplicationCommandType} key The key to resolve
* @returns {ApplicationCommandType}
*/
static resolveApplicationCommandType(key) {
switch (key) {
case 'CHAT_INPUT':
return ApplicationCommandType.ChatInput;
case 'USER':
return ApplicationCommandType.User;
case 'MESSAGE':
return ApplicationCommandType.Message;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to an {@link ApplicationCommandOptionType} enum value. Here are the available types:
* * SUB_COMMAND
* * SUB_COMMAND_GROUP
* * STRING
* * INTEGER
* * BOOLEAN
* * USER
* * CHANNEL
* * ROLE
* * NUMBER
* * MENTIONABLE
* @typedef {string} ApplicationCommandOptionTypeEnumResolvable
*/
/**
* Resolves enum key to {@link ApplicationCommandOptionType} enum value
* @param {ApplicationCommandOptionTypeEnumResolvable|ApplicationCommandOptionType} key The key to resolve
* @returns {ApplicationCommandOptionType}
*/
static resolveApplicationCommandOptionType(key) {
switch (key) {
case 'SUB_COMMAND':
return ApplicationCommandOptionType.Subcommand;
case 'SUB_COMMAND_GROUP':
return ApplicationCommandOptionType.SubcommandGroup;
case 'STRING':
return ApplicationCommandOptionType.String;
case 'INTEGER':
return ApplicationCommandOptionType.Integer;
case 'BOOLEAN':
return ApplicationCommandOptionType.Boolean;
case 'USER':
return ApplicationCommandOptionType.User;
case 'CHANNEL':
return ApplicationCommandOptionType.Channel;
case 'ROLE':
return ApplicationCommandOptionType.Role;
case 'NUMBER':
return ApplicationCommandOptionType.Number;
case 'MENTIONABLE':
return ApplicationCommandOptionType.Mentionable;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to an {@link ApplicationCommandPermissionType} enum value.
* Here are the available types:
* * ROLE
* * USER
* @typedef {string} ApplicationCommandPermissionTypeEnumResolvable
*/
/**
* Resolves enum key to {@link ApplicationCommandPermissionType} enum value
* @param {ApplicationCommandPermissionTypeEnumResolvable|ApplicationCommandPermissionType} key The key to resolve
* @returns {ApplicationCommandPermissionType}
*/
static resolveApplicationCommandPermissionType(key) {
switch (key) {
case 'ROLE':
return ApplicationCommandPermissionType.Role;
case 'USER':
return ApplicationCommandPermissionType.User;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link ComponentType} enum value. Here are the available types:
* * ACTION_ROW
* * BUTTON
* * SELECT_MENU
* @typedef {string} ComponentTypeEnumResolvable
*/
/**
* Resolves enum key to {@link ComponentType} enum value
* @param {ComponentTypeEnumResolvable|ComponentType} key The key to resolve
* @returns {ComponentType}
*/
static resolveComponentType(key) {
switch (key) {
case 'ACTION_ROW':
return ComponentType.ActionRow;
case 'BUTTON':
return ComponentType.Button;
case 'SELECT_MENU':
return ComponentType.SelectMenu;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link ButtonStyle} enum value. Here are the available types:
* * PRIMARY
* * SECONDARY
* * SUCCESS
* * DANGER
* * LINK
* @typedef {string} ButtonStyleEnumResolvable
*/
/**
* Resolves enum key to {@link ButtonStyle} enum value
* @param {ButtonStyleEnumResolvable|ButtonStyle} key The key to resolve
* @returns {ButtonStyle}
*/
static resolveButtonStyle(key) {
switch (key) {
case 'PRIMARY':
return ButtonStyle.Primary;
case 'SECONDARY':
return ButtonStyle.Secondary;
case 'SUCCESS':
return ButtonStyle.Success;
case 'DANGER':
return ButtonStyle.Danger;
case 'LINK':
return ButtonStyle.Link;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link MessageType} enum value. Here are the available types:
* * DEFAULT
* * RECIPIENT_ADD
* * RECIPIENT_REMOVE
* * CALL
* * CHANNEL_NAME_CHANGE
* * CHANNEL_ICON_CHANGE
* * CHANNEL_PINNED_MESSAGE
* * GUILD_MEMBER_JOIN
* * USER_PREMIUM_GUILD_SUBSCRIPTION
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3
* * CHANNEL_FOLLOW_ADD
* * GUILD_DISCOVERY_DISQUALIFIED
* * GUILD_DISCOVERY_REQUALIFIED
* * GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING
* * GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING
* * THREAD_CREATED
* * REPLY
* * CHAT_INPUT_COMMAND
* * THREAD_STARTER_MESSAGE
* * GUILD_INVITE_REMINDER
* * CONTEXT_MENU_COMMAND
* @typedef {string} MessageTypeEnumResolvable
*/
/**
* Resolves enum key to {@link MessageType} enum value
* @param {MessageTypeEnumResolvable|MessageType} key The key to lookup
* @returns {MessageType}
*/
static resolveMessageType(key) {
switch (key) {
case 'DEFAULT':
return MessageType.Default;
case 'RECIPIENT_ADD':
return MessageType.RecipientAdd;
case 'RECIPIENT_REMOVE':
return MessageType.RecipientRemove;
case 'CALL':
return MessageType.Call;
case 'CHANNEL_NAME_CHANGE':
return MessageType.ChannelNameChange;
case 'CHANNEL_ICON_CHANGE':
return MessageType.ChannelIconChange;
case 'CHANNEL_PINNED_MESSAGE':
return MessageType.ChannelPinnedMessage;
case 'GUILD_MEMBER_JOIN':
return MessageType.GuildMemberJoin;
case 'USER_PREMIUM_GUILD_SUBSCRIPTION':
return MessageType.UserPremiumGuildSubscription;
case 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1':
return MessageType.UserPremiumGuildSubscriptionTier1;
case 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2':
return MessageType.UserPremiumGuildSubscriptionTier2;
case 'USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3':
return MessageType.UserPremiumGuildSubscriptionTier3;
case 'CHANNEL_FOLLOW_ADD':
return MessageType.ChannelFollowAdd;
case 'GUILD_DISCOVERY_DISQUALIFIED':
return MessageType.GuildDiscoveryDisqualified;
case 'GUILD_DISCOVERY_REQUALIFIED':
return MessageType.GuildDiscoveryRequalified;
case 'GUILD_DISCOVERY_GRACE_PERIOD_INITIAL_WARNING':
return MessageType.GuildDiscoveryGracePeriodInitialWarning;
case 'GUILD_DISCOVERY_GRACE_PERIOD_FINAL_WARNING':
return MessageType.GuildDiscoveryGracePeriodFinalWarning;
case 'THREAD_CREATED':
return MessageType.ThreadCreated;
case 'REPLY':
return MessageType.Reply;
case 'CHAT_INPUT_COMMAND':
return MessageType.ChatInputCommand;
case 'THREAD_STARTER_MESSAGE':
return MessageType.ThreadStarterMessage;
case 'GUILD_INVITE_REMINDER':
return MessageType.GuildInviteReminder;
case 'CONTEXT_MENU_COMMAND':
return MessageType.ContextMenuCommand;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildNSFWLevel} enum value. Here are the available types:
* * DEFAULT
* * EXPLICIT
* * SAFE
* * AGE_RESTRICTED
* @typedef {string} GuildNSFWLevelEnumResolvable
*/
/**
* Resolves enum key to {@link GuildNSFWLevel} enum value
* @param {GuildNSFWLevelEnumResolvable|GuildNSFWLevel} key The key to lookup
* @returns {GuildNSFWLevel}
*/
static resolveGuildNSFWLevel(key) {
switch (key) {
case 'DEFAULT':
return GuildNSFWLevel.Default;
case 'EXPLICIT':
return GuildNSFWLevel.Explicit;
case 'SAFE':
return GuildNSFWLevel.Safe;
case 'AGE_RESTRICTED':
return GuildNSFWLevel.AgeRestricted;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildVerificationLevel} enum value. Here are the available types:
* * NONE
* * LOW
* * MEDIUM
* * HIGH
* * VERY_HIGH
* @typedef {string} GuildVerificationLevelEnumResolvable
*/
/**
* Resolves enum key to {@link GuildVerificationLevel} enum value
* @param {GuildVerificationLevelEnumResolvable|GuildVerificationLevel} key The key to lookup
* @returns {GuildVerificationLevel}
*/
static resolveGuildVerificationLevel(key) {
switch (key) {
case 'NONE':
return GuildVerificationLevel.None;
case 'LOW':
return GuildVerificationLevel.Low;
case 'MEDIUM':
return GuildVerificationLevel.Medium;
case 'HIGH':
return GuildVerificationLevel.High;
case 'VERY_HIGH':
return GuildVerificationLevel.VeryHigh;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildDefaultMessageNotifications} enum value.
* Here are the available types:
* * ALL_MESSAGES
* * ONLY_MENTIONS
* @typedef {string} GuildDefaultMessageNotificationsEnumResolvable
*/
/**
* Resolves enum key to {@link GuildDefaultMessageNotifications} enum value
* @param {GuildDefaultMessageNotificationsEnumResolvable|GuildDefaultMessageNotifications} key The key to lookup
* @returns {GuildDefaultMessageNotifications}
*/
static resolveGuildDefaultMessageNotifications(key) {
switch (key) {
case 'ALL_MESSAGES':
return GuildDefaultMessageNotifications.AllMessages;
case 'ONLY_MENTIONS':
return GuildDefaultMessageNotifications.OnlyMentions;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildExplicitContentFilter} enum value. Here are the available types:
* * DISABLED
* * MEMBERS_WITHOUT_ROLES
* * ALL_MEMBERS
* @typedef {string} GuildExplicitContentFilterEnumResolvable
*/
/**
* Resolves enum key to {@link GuildExplicitContentFilter} enum value
* @param {GuildExplicitContentFilterEnumResolvable|GuildExplicitContentFilter} key The key to lookup
* @returns {GuildExplicitContentFilter}
*/
static resolveGuildExplicitContentFilter(key) {
switch (key) {
case 'DISABLED':
return GuildExplicitContentFilter.Disabled;
case 'MEMBERS_WITHOUT_ROLES':
return GuildExplicitContentFilter.MembersWithoutRoles;
case 'ALL_MEMBERS':
return GuildExplicitContentFilter.AllMembers;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildPremiumTier} enum value. Here are the available types:
* * NONE
* * TIER_1
* * TIER_2
* * TIER_3
* @typedef {string} GuildPremiumTierEnumResolvable
*/
/**
* Resolves enum key to {@link GuildPremiumTier} enum value
* @param {GuildPremiumTierEnumResolvable|GuildPremiumTier} key The key to lookup
* @returns {GuildPremiumTier}
*/
static resolveGuildPremiumTier(key) {
switch (key) {
case 'NONE':
return GuildPremiumTier.None;
case 'TIER_1':
return GuildPremiumTier.Tier1;
case 'TIER_2':
return GuildPremiumTier.Tier2;
case 'TIER_3':
return GuildPremiumTier.Tier3;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildScheduledEventStatus} enum value. Here are the available types:
* * SCHEDULED
* * ACTIVE
* * COMPLETED
* * CANCELED
* @typedef {string} GuildScheduledEventStatusEnumResolvable
*/
/**
* Resolves enum key to {@link GuildScheduledEventStatus} enum value
* @param {GuildScheduledEventStatusEnumResolvable|GuildScheduledEventStatus} key The key to lookup
* @returns {GuildScheduledEventStatus}
*/
static resolveGuildScheduledEventStatus(key) {
switch (key) {
case 'SCHEDULED':
return GuildScheduledEventStatus.Scheduled;
case 'ACTIVE':
return GuildScheduledEventStatus.Active;
case 'COMPLETED':
return GuildScheduledEventStatus.Completed;
case 'CANCELED':
return GuildScheduledEventStatus.Canceled;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link StageInstancePrivacyLevel} enum value. Here are the available types:
* * PUBLIC
* * GUILD_ONLY
* @typedef {string} StageInstancePrivacyLevelEnumResolvable
*/
/**
* Resolves enum key to {@link StageInstancePrivacyLevel} enum value
* @param {StageInstancePrivacyLevelEnumResolvable|StageInstancePrivacyLevel} key The key to lookup
* @returns {StageInstancePrivacyLevel}
*/
static resolveStageInstancePrivacyLevel(key) {
switch (key) {
case 'PUBLIC':
return StageInstancePrivacyLevel.Public;
case 'GUILD_ONLY':
return StageInstancePrivacyLevel.GuildOnly;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildMFALevel} enum value. Here are the available types:
* * NONE
* * ELEVATED
* @typedef {string} GuildMFALevelEnumResolvable
*/
/**
* Resolves enum key to {@link GuildMFALevel} enum value
* @param {GuildMFALevelEnumResolvable|GuildMFALevel} key The key to lookup
* @returns {GuildMFALevel}
*/
static resolveGuildMFALevel(key) {
switch (key) {
case 'NONE':
return GuildMFALevel.None;
case 'ELEVATED':
return GuildMFALevel.Elevated;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link TeamMemberMembershipState} enum value. Here are the available types:
* * INVITED
* * ACCEPTED
* @typedef {string} TeamMemberMembershipStateEnumResolvable
*/
/**
* Resolves enum key to {@link TeamMemberMembershipState} enum value
* @param {TeamMemberMembershipStateEnumResolvable|TeamMemberMembershipState} key The key to lookup
* @returns {TeamMemberMembershipState}
*/
static resolveTeamMemberMembershipState(key) {
switch (key) {
case 'INVITED':
return TeamMemberMembershipState.Invited;
case 'ACCEPTED':
return TeamMemberMembershipState.Accepted;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link GuildScheduledEventEntityType} enum value. Here are the available types:
* * STAGE_INSTANCE
* * VOICE
* * EXTERNAL
* @typedef {string} GuildScheduledEventEntityTypeEnumResolvable
*/
/**
* Resolves enum key to {@link GuildScheduledEventEntityType} enum value
* @param {GuildScheduledEventEntityTypeEnumResolvable|GuildScheduledEventEntityType} key The key to lookup
* @returns {GuildScheduledEventEntityType}
*/
static resolveGuildScheduledEventEntityType(key) {
switch (key) {
case 'STAGE_INSTANCE':
return GuildScheduledEventEntityType.StageInstance;
case 'VOICE':
return GuildScheduledEventEntityType.Voice;
case 'EXTERNAL':
return GuildScheduledEventEntityType.External;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link IntegrationExpireBehavior} enum value. Here are the available types:
* * REMOVE_ROLE
* * KICK
* @typedef {string} IntegrationExpireBehaviorEnumResolvable
*/
/**
* Resolves enum key to {@link IntegrationExpireBehavior} enum value
* @param {IntegrationExpireBehaviorEnumResolvable|IntegrationExpireBehavior} key The key to lookup
* @returns {IntegrationExpireBehavior}
*/
static resolveIntegrationExpireBehavior(key) {
switch (key) {
case 'REMOVE_ROLE':
return IntegrationExpireBehavior.RemoveRole;
case 'KICK':
return IntegrationExpireBehavior.Kick;
default:
return unknownKeyStrategy(key);
}
}
/**
* A string that can be resolved to a {@link AuditLogEvent} enum value. Here are the available types:
* * GUILD_UPDATE
* * CHANNEL_CREATE
* * CHANNEL_UPDATE
* * CHANNEL_DELETE
* * CHANNEL_OVERWRITE_CREATE
* * CHANNEL_OVERWRITE_UPDATE
* * CHANNEL_OVERWRITE_DELETE
* * MEMBER_KICK
* * MEMBER_PRUNE
* * MEMBER_BAN_ADD
* * MEMBER_BAN_REMOVE
* * MEMBER_UPDATE
* * MEMBER_ROLE_UPDATE
* * MEMBER_MOVE
* * MEMBER_DISCONNECT
* * BOT_ADD
* * ROLE_CREATE
* * ROLE_UPDATE
* * ROLE_DELETE
* * INVITE_CREATE
* * INVITE_UPDATE
* * INVITE_DELETE
* * WEBHOOK_CREATE
* * WEBHOOK_UPDATE
* * WEBHOOK_DELETE
* * INTEGRATION_CREATE
* * INTEGRATION_UPDATE
* * INTEGRATION_DELETE
* * STAGE_INSTANCE_CREATE
* * STAGE_INSTANCE_UPDATE
* * STAGE_INSTANCE_DELETE
* * STICKER_CREATE
* * STICKER_UPDATE
* * STICKER_DELETE
* * GUILD_SCHEDULED_EVENT_CREATE
* * GUILD_SCHEDULED_EVENT_UPDATE
* * GUILD_SCHEDULED_EVENT_DELETE
* * THREAD_CREATE
* * THREAD_UPDATE
* * THREAD_DELETE
* @typedef {string} AuditLogEventEnumResolvable
*/
/**
* Resolves enum key to {@link AuditLogEvent} enum value
* @param {AuditLogEventEnumResolvable|AuditLogEvent} key The key to lookup
* @returns {AuditLogEvent}
*/
static resolveAuditLogEvent(key) {
switch (key) {
case 'GUILD_UPDATE':
return AuditLogEvent.GuildUpdate;
case 'CHANNEL_CREATE':
return AuditLogEvent.ChannelCreate;
case 'CHANNEL_UPDATE':
return AuditLogEvent.ChannelUpdate;
case 'CHANNEL_DELETE':
return AuditLogEvent.ChannelDelete;
case 'CHANNEL_OVERWRITE_CREATE':
return AuditLogEvent.ChannelOverwriteCreate;
case 'CHANNEL_OVERWRITE_UPDATE':
return AuditLogEvent.ChannelOverwriteUpdate;
case 'CHANNEL_OVERWRITE_DELETE':
return AuditLogEvent.ChannelOverwriteDelete;
case 'MEMBER_KICK':
return AuditLogEvent.MemberKick;
case 'MEMBER_PRUNE':
return AuditLogEvent.MemberPrune;
case 'MEMBER_BAN_ADD':
return AuditLogEvent.MemberBanAdd;
case 'MEMBER_BAN_REMOVE':
return AuditLogEvent.MemberBanRemove;
case 'MEMBER_UPDATE':
return AuditLogEvent.MemberUpdate;
case 'MEMBER_ROLE_UPDATE':
return AuditLogEvent.MemberRoleUpdate;
case 'MEMBER_MOVE':
return AuditLogEvent.MemberMove;
case 'MEMBER_DISCONNECT':
return AuditLogEvent.MemberDisconnect;
case 'BOT_ADD':
return AuditLogEvent.BotAdd;
case 'ROLE_CREATE':
return AuditLogEvent.RoleCreate;
case 'ROLE_UPDATE':
return AuditLogEvent.RoleUpdate;
case 'ROLE_DELETE':
return AuditLogEvent.RoleDelete;
case 'INVITE_CREATE':
return AuditLogEvent.InviteCreate;
case 'INVITE_UPDATE':
return AuditLogEvent.InviteUpdate;
case 'INVITE_DELETE':
return AuditLogEvent.InviteDelete;
case 'WEBHOOK_CREATE':
return AuditLogEvent.WebhookCreate;
case 'WEBHOOK_UPDATE':
return AuditLogEvent.WebhookUpdate;
case 'WEBHOOK_DELETE':
return AuditLogEvent.WebhookDelete;
case 'EMOJI_CREATE':
return AuditLogEvent.EmojiCreate;
case 'EMOJI_UPDATE':
return AuditLogEvent.EmojiUpdate;
case 'EMOJI_DELETE':
return AuditLogEvent.EmojiDelete;
case 'MESSAGE_DELETE':
return AuditLogEvent.MessageDelete;
case 'MESSAGE_BULK_DELETE':
return AuditLogEvent.MessageBulkDelete;
case 'MESSAGE_PIN':
return AuditLogEvent.MessagePin;
case 'MESSAGE_UNPIN':
return AuditLogEvent.MessageUnpin;
case 'INTEGRATION_CREATE':
return AuditLogEvent.IntegrationCreate;
case 'INTEGRATION_UPDATE':
return AuditLogEvent.IntegrationUpdate;
case 'INTEGRATION_DELETE':
return AuditLogEvent.IntegrationDelete;
case 'STAGE_INSTANCE_CREATE':
return AuditLogEvent.StageInstanceCreate;
case 'STAGE_INSTANCE_UPDATE':
return AuditLogEvent.StageInstanceUpdate;
case 'STAGE_INSTANCE_DELETE':
return AuditLogEvent.StageInstanceDelete;
case 'STICKER_CREATE':
return AuditLogEvent.StickerCreate;
case 'STICKER_UPDATE':
return AuditLogEvent.StickerUpdate;
case 'STICKER_DELETE':
return AuditLogEvent.StickerDelete;
case 'GUILD_SCHEDULED_EVENT_CREATE':
return AuditLogEvent.GuildScheduledEventCreate;
case 'GUILD_SCHEDULED_EVENT_UPDATE':
return AuditLogEvent.GuildScheduledEventUpdate;
case 'GUILD_SCHEDULED_EVENT_DELETE':
return AuditLogEvent.GuildScheduledEventDelete;
case 'THREAD_CREATE':
return AuditLogEvent.ThreadCreate;
case 'THREAD_UPDATE':
return AuditLogEvent.ThreadUpdate;
case 'THREAD_DELETE':
return AuditLogEvent.ThreadDelete;
default:
return unknownKeyStrategy(key);
}
}
}
// Precondition logic wrapper
function preconditioner(func) {
return key => {
if (typeof key !== 'string' && typeof key !== 'number') {
throw new Error('Enum value must be string or number');
}
if (typeof key === 'number') {
return key;
}
return func(key);
};
}
// Injects wrapper into class static methods.
function applyPreconditioner(obj) {
for (const name in Object.getOwnPropertyNames(obj)) {
if (typeof obj[name] !== 'function') {
return;
}
obj[name] = preconditioner(obj[name]);
}
}
// Apply precondition logic
applyPreconditioner(EnumResolvers);
module.exports = EnumResolvers;

View File

@@ -1,13 +0,0 @@
'use strict';
function createEnum(keys) {
const obj = {};
for (const [index, key] of keys.entries()) {
if (key === null) continue;
obj[key] = index;
obj[index] = key;
}
return obj;
}
module.exports = { createEnum };

View File

@@ -1,72 +0,0 @@
'use strict';
module.exports = {
ClientReady: 'ready',
GuildCreate: 'guildCreate',
GuildDelete: 'guildDelete',
GuildUpdate: 'guildUpdate',
GuildUnavailable: 'guildUnavailable',
GuildMemberAdd: 'guildMemberAdd',
GuildMemberRemove: 'guildMemberRemove',
GuildMemberUpdate: 'guildMemberUpdate',
GuildMemberAvailable: 'guildMemberAvailable',
GuildMembersChunk: 'guildMembersChunk',
GuildIntegrationsUpdate: 'guildIntegrationsUpdate',
GuildRoleCreate: 'roleCreate',
GuildRoleDelete: 'roleDelete',
InviteCreate: 'inviteCreate',
InviteDelete: 'inviteDelete',
GuildRoleUpdate: 'roleUpdate',
GuildEmojiCreate: 'emojiCreate',
GuildEmojiDelete: 'emojiDelete',
GuildEmojiUpdate: 'emojiUpdate',
GuildBanAdd: 'guildBanAdd',
GuildBanRemove: 'guildBanRemove',
ChannelCreate: 'channelCreate',
ChannelDelete: 'channelDelete',
ChannelUpdate: 'channelUpdate',
ChannelPinsUpdate: 'channelPinsUpdate',
MessageCreate: 'messageCreate',
MessageDelete: 'messageDelete',
MessageUpdate: 'messageUpdate',
MessageBulkDelete: 'messageDeleteBulk',
MessageReactionAdd: 'messageReactionAdd',
MessageReactionRemove: 'messageReactionRemove',
MessageReactionRemoveAll: 'messageReactionRemoveAll',
MessageReactionRemoveEmoji: 'messageReactionRemoveEmoji',
ThreadCreate: 'threadCreate',
ThreadDelete: 'threadDelete',
ThreadUpdate: 'threadUpdate',
ThreadListSync: 'threadListSync',
ThreadMemberUpdate: 'threadMemberUpdate',
ThreadMembersUpdate: 'threadMembersUpdate',
UserUpdate: 'userUpdate',
PresenceUpdate: 'presenceUpdate',
VoiceServerUpdate: 'voiceServerUpdate',
VoiceStateUpdate: 'voiceStateUpdate',
TypingStart: 'typingStart',
WebhooksUpdate: 'webhookUpdate',
InteractionCreate: 'interactionCreate',
Error: 'error',
Warn: 'warn',
Debug: 'debug',
CacheSweep: 'cacheSweep',
ShardDisconnect: 'shardDisconnect',
ShardError: 'shardError',
ShardReconnecting: 'shardReconnecting',
ShardReady: 'shardReady',
ShardResume: 'shardResume',
Invalidated: 'invalidated',
Raw: 'raw',
StageInstanceCreate: 'stageInstanceCreate',
StageInstanceUpdate: 'stageInstanceUpdate',
StageInstanceDelete: 'stageInstanceDelete',
GuildStickerCreate: 'stickerCreate',
GuildStickerDelete: 'stickerDelete',
GuildStickerUpdate: 'stickerUpdate',
GuildScheduledEventCreate: 'guildScheduledEventCreate',
GuildScheduledEventUpdate: 'guildScheduledEventUpdate',
GuildScheduledEventDelete: 'guildScheduledEventDelete',
GuildScheduledEventUserAdd: 'guildScheduledEventUserAdd',
GuildScheduledEventUserRemove: 'guildScheduledEventUserRemove',
};

66
src/util/Intents.js Normal file
View File

@@ -0,0 +1,66 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to calculate intents.
* @extends {BitField}
*/
class Intents extends BitField {}
/**
* @name Intents
* @kind constructor
* @memberof Intents
* @param {IntentsResolvable} [bits=0] Bit(s) to read from
*/
/**
* Data that can be resolved to give a permission number. This can be:
* * A string (see {@link Intents.FLAGS})
* * An intents flag
* * An instance of Intents
* * An array of IntentsResolvable
* @typedef {string|number|Intents|IntentsResolvable[]} IntentsResolvable
*/
/**
* Numeric WebSocket intents. All available properties:
* * `GUILDS`
* * `GUILD_MEMBERS`
* * `GUILD_BANS`
* * `GUILD_EMOJIS_AND_STICKERS`
* * `GUILD_INTEGRATIONS`
* * `GUILD_WEBHOOKS`
* * `GUILD_INVITES`
* * `GUILD_VOICE_STATES`
* * `GUILD_PRESENCES`
* * `GUILD_MESSAGES`
* * `GUILD_MESSAGE_REACTIONS`
* * `GUILD_MESSAGE_TYPING`
* * `DIRECT_MESSAGES`
* * `DIRECT_MESSAGE_REACTIONS`
* * `DIRECT_MESSAGE_TYPING`
* * `GUILD_SCHEDULED_EVENTS`
* @type {Object}
* @see {@link https://discord.com/developers/docs/topics/gateway#list-of-intents}
*/
Intents.FLAGS = {
GUILDS: 1 << 0,
GUILD_MEMBERS: 1 << 1,
GUILD_BANS: 1 << 2,
GUILD_EMOJIS_AND_STICKERS: 1 << 3,
GUILD_INTEGRATIONS: 1 << 4,
GUILD_WEBHOOKS: 1 << 5,
GUILD_INVITES: 1 << 6,
GUILD_VOICE_STATES: 1 << 7,
GUILD_PRESENCES: 1 << 8,
GUILD_MESSAGES: 1 << 9,
GUILD_MESSAGE_REACTIONS: 1 << 10,
GUILD_MESSAGE_TYPING: 1 << 11,
DIRECT_MESSAGES: 1 << 12,
DIRECT_MESSAGE_REACTIONS: 1 << 13,
DIRECT_MESSAGE_TYPING: 1 << 14,
GUILD_SCHEDULED_EVENTS: 1 << 16,
};
module.exports = Intents;

View File

@@ -1,33 +0,0 @@
'use strict';
const { GatewayIntentBits } = require('discord-api-types/v9');
const BitField = require('./BitField');
/**
* Data structure that makes it easy to calculate intents.
* @extends {BitField}
*/
class IntentsBitField extends BitField {}
/**
* @name IntentsBitField
* @kind constructor
* @memberof IntentsBitField
* @param {IntentsResolvable} [bits=0] Bit(s) to read from
*/
/**
* Data that can be resolved to give a permission number. This can be:
* * A string (see {@link IntentsBitField.Flags})
* * An intents flag
* * An instance of {@link IntentsBitField}
* * An array of IntentsResolvable
* @typedef {string|number|IntentsBitField|IntentsResolvable[]} IntentsResolvable
*/
/**
* Numeric WebSocket intents
* @type {GatewayIntentBits}
*/
IntentsBitField.Flags = GatewayIntentBits;
module.exports = IntentsBitField;

View File

@@ -1,18 +1,35 @@
'use strict';
const { setInterval } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { _cleanupSymbol } = require('./Constants.js');
const Sweepers = require('./Sweepers.js');
const { TypeError } = require('../errors/DJSError.js');
/**
* @typedef {Function} SweepFilter
* @param {LimitedCollection} collection The collection being swept
* @returns {Function|null} Return `null` to skip sweeping, otherwise a function passed to `sweep()`,
* See {@link [Collection#sweep](https://discord.js.org/#/docs/collection/main/class/Collection?scrollTo=sweep)}
* for the definition of this function.
*/
/**
* Options for defining the behavior of a LimitedCollection
* @typedef {Object} LimitedCollectionOptions
* @property {?number} [maxSize=Infinity] The maximum size of the Collection
* @property {?Function} [keepOverLimit=null] A function, which is passed the value and key of an entry, ran to decide
* to keep an entry past the maximum size
* @property {?SweepFilter} [sweepFilter=null] DEPRECATED: There is no direct alternative to this,
* however most of its purpose is fulfilled by {@link Client#sweepers}
* A function ran every `sweepInterval` to determine how to sweep
* @property {?number} [sweepInterval=0] DEPRECATED: There is no direct alternative to this,
* however most of its purpose is fulfilled by {@link Client#sweepers}
* How frequently, in seconds, to sweep the collection.
*/
/**
* A Collection which holds a max amount of entries.
* A Collection which holds a max amount of entries and sweeps periodically.
* @extends {Collection}
* @param {LimitedCollectionOptions} [options={}] Options for constructing the Collection.
* @param {Iterable} [iterable=null] Optional entries passed to the Map constructor.
@@ -22,7 +39,7 @@ class LimitedCollection extends Collection {
if (typeof options !== 'object' || options === null) {
throw new TypeError('INVALID_TYPE', 'options', 'object', true);
}
const { maxSize = Infinity, keepOverLimit = null } = options;
const { maxSize = Infinity, keepOverLimit = null, sweepInterval = 0, sweepFilter = null } = options;
if (typeof maxSize !== 'number') {
throw new TypeError('INVALID_TYPE', 'maxSize', 'number');
@@ -30,6 +47,12 @@ class LimitedCollection extends Collection {
if (keepOverLimit !== null && typeof keepOverLimit !== 'function') {
throw new TypeError('INVALID_TYPE', 'keepOverLimit', 'function');
}
if (typeof sweepInterval !== 'number') {
throw new TypeError('INVALID_TYPE', 'sweepInterval', 'number');
}
if (sweepFilter !== null && typeof sweepFilter !== 'function') {
throw new TypeError('INVALID_TYPE', 'sweepFilter', 'function');
}
super(iterable);
@@ -44,6 +67,28 @@ class LimitedCollection extends Collection {
* @type {?Function}
*/
this.keepOverLimit = keepOverLimit;
/**
* A function called every sweep interval that returns a function passed to `sweep`.
* @deprecated in favor of {@link Client#sweepers}
* @type {?SweepFilter}
*/
this.sweepFilter = sweepFilter;
/**
* The id of the interval being used to sweep.
* @deprecated in favor of {@link Client#sweepers}
* @type {?Timeout}
*/
this.interval =
sweepInterval > 0 && sweepInterval !== Infinity && sweepFilter
? setInterval(() => {
const sweepFn = this.sweepFilter(this);
if (sweepFn === null) return;
if (typeof sweepFn !== 'function') throw new TypeError('SWEEP_FILTER_RETURN');
this.sweep(sweepFn);
}, sweepInterval * 1_000).unref()
: null;
}
set(key, value) {
@@ -60,6 +105,24 @@ class LimitedCollection extends Collection {
return super.set(key, value);
}
/**
* Create a sweepFilter function that uses a lifetime to determine sweepability.
* @param {LifetimeFilterOptions} [options={}] The options used to generate the filter function
* @deprecated Use {@link Sweepers.filterByLifetime} instead
* @returns {SweepFilter}
*/
static filterByLifetime({
lifetime = 14400,
getComparisonTimestamp = e => e?.createdTimestamp,
excludeFromSweep = () => false,
} = {}) {
return Sweepers.filterByLifetime({ lifetime, getComparisonTimestamp, excludeFromSweep });
}
[_cleanupSymbol]() {
return this.interval ? () => clearInterval(this.interval) : null;
}
static get [Symbol.species]() {
return Collection;
}

48
src/util/MessageFlags.js Normal file
View File

@@ -0,0 +1,48 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link Message#flags} bitfield.
* @extends {BitField}
*/
class MessageFlags extends BitField {}
/**
* @name MessageFlags
* @kind constructor
* @memberof MessageFlags
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name MessageFlags#bitfield
*/
/**
* Numeric message flags. All available properties:
* * `CROSSPOSTED`
* * `IS_CROSSPOST`
* * `SUPPRESS_EMBEDS`
* * `SOURCE_MESSAGE_DELETED`
* * `URGENT`
* * `HAS_THREAD`
* * `EPHEMERAL`
* * `LOADING`
* @type {Object}
* @see {@link https://discord.com/developers/docs/resources/channel#message-object-message-flags}
*/
MessageFlags.FLAGS = {
CROSSPOSTED: 1 << 0,
IS_CROSSPOST: 1 << 1,
SUPPRESS_EMBEDS: 1 << 2,
SOURCE_MESSAGE_DELETED: 1 << 3,
URGENT: 1 << 4,
HAS_THREAD: 1 << 5,
EPHEMERAL: 1 << 6,
LOADING: 1 << 7,
};
module.exports = MessageFlags;

View File

@@ -1,31 +0,0 @@
'use strict';
const { MessageFlags } = require('discord-api-types/v9');
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link Message#flags} bitfield.
* @extends {BitField}
*/
class MessageFlagsBitField extends BitField {}
/**
* @name MessageFlagsBitField
* @kind constructor
* @memberof MessageFlagsBitField
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name MessageFlagsBitField#bitfield
*/
/**
* Numeric message flags.
* @type {MessageFlags}
*/
MessageFlagsBitField.Flags = MessageFlags;
module.exports = MessageFlagsBitField;

View File

@@ -1,8 +1,24 @@
'use strict';
const process = require('node:process');
const Transformers = require('./Transformers');
const JSONBig = require('json-bigint');
/**
* Rate limit data
* @typedef {Object} RateLimitData
* @property {number} timeout Time until this rate limit ends, in ms
* @property {number} limit The maximum amount of requests of this endpoint
* @property {string} method The HTTP method of this request
* @property {string} path The path of the request relative to the HTTP endpoint
* @property {string} route The route of the request relative to the HTTP endpoint
* @property {boolean} global Whether this is a global rate limit
*/
/**
* Whether this rate limit should throw an Error
* @typedef {Function} RateLimitQueueFilter
* @param {RateLimitData} rateLimitData The data of this rate limit
* @returns {boolean|Promise<boolean>}
*/
/**
* @typedef {Function} CacheFactory
@@ -23,20 +39,44 @@ const JSONBig = require('json-bigint');
* You can use your own function, or the {@link Options} class to customize the Collection used for the cache.
* <warn>Overriding the cache used in `GuildManager`, `ChannelManager`, `GuildChannelManager`, `RoleManager`,
* and `PermissionOverwriteManager` is unsupported and **will** break functionality</warn>
* @property {number} [messageCacheLifetime=0] DEPRECATED: Pass `lifetime` to `sweepers.messages` instead.
* How long a message should stay in the cache until it is considered sweepable (in seconds, 0 for forever)
* @property {number} [messageSweepInterval=0] DEPRECATED: Pass `interval` to `sweepers.messages` instead.
* How frequently to remove messages from the cache that are older than the message cache lifetime
* (in seconds, 0 for never)
* @property {MessageMentionOptions} [allowedMentions] Default value for {@link MessageOptions#allowedMentions}
* @property {Partials[]} [partials] Structures allowed to be partial. This means events can be emitted even when
* @property {number} [invalidRequestWarningInterval=0] The number of invalid REST requests (those that return
* 401, 403, or 429) in a 10 minute window between emitted warnings (0 for no warnings). That is, if set to 500,
* warnings will be emitted at invalid request number 500, 1000, 1500, and so on.
* @property {PartialType[]} [partials] Structures allowed to be partial. This means events can be emitted even when
* they're missing all the data for a particular structure. See the "Partial Structures" topic on the
* [guide](https://discordjs.guide/popular-topics/partials.html) for some
* important usage information, as partials require you to put checks in place when handling data.
* @property {number} [restWsBridgeTimeout=5000] Maximum time permitted between REST responses and their
* corresponding WebSocket events
* @property {number} [restTimeOffset=500] Extra time in milliseconds to wait before continuing to make REST
* requests (higher values will reduce rate-limiting errors on bad connections)
* @property {number} [restRequestTimeout=15000] Time to wait before cancelling a REST request, in milliseconds
* @property {number} [restSweepInterval=60] How frequently to delete inactive request buckets, in seconds
* (or 0 for never)
* @property {number} [restGlobalRateLimit=0] How many requests to allow sending per second (0 for unlimited, 50 for
* the standard global limit used by Discord)
* @property {string[]|RateLimitQueueFilter} [rejectOnRateLimit] Decides how rate limits and pre-emptive throttles
* should be handled. If this option is an array containing the prefix of the request route (e.g. /channels to match any
* route starting with /channels, such as /channels/222197033908436994/messages) or a function returning true, a
* {@link RateLimitError} will be thrown. Otherwise the request will be queued for later
* @property {number} [retryLimit=1] How many times to retry on 5XX errors
* (Infinity for an indefinite amount of retries)
* @property {boolean} [failIfNotExists=true] Default value for {@link ReplyMessageOptions#failIfNotExists}
* @property {string[]} [userAgentSuffix] An array of additional bot info to be appended to the end of the required
* [User Agent](https://discord.com/developers/docs/reference#user-agent) header
* @property {PresenceData} [presence={}] Presence data to use upon login
* @property {IntentsResolvable} intents Intents to enable for this connection
* @property {number} [waitGuildTimeout=15_000] Time in milliseconds that Clients with the GUILDS intent should wait for
* missing guilds to be received before starting the bot. If not specified, the default is 15 seconds.
* missing guilds to be recieved before starting the bot. If not specified, the default is 15 seconds.
* @property {SweeperOptions} [sweepers={}] Options for cache sweeping
* @property {WebsocketOptions} [ws] Options for the WebSocket
* @property {RESTOptions} [rest] Options for the REST manager
* @property {Function} [jsonTransformer] A function used to transform outgoing json data
* @property {HTTPOptions} [http] HTTP options
*/
/**
@@ -62,6 +102,26 @@ const JSONBig = require('json-bigint');
* sent in the initial guild member list, must be between 50 and 250
*/
/**
* HTTPS Agent options.
* @typedef {Object} AgentOptions
* @see {@link https://nodejs.org/api/https.html#https_class_https_agent}
* @see {@link https://nodejs.org/api/http.html#http_new_agent_options}
*/
/**
* HTTP options
* @typedef {Object} HTTPOptions
* @property {number} [version=9] API version to use
* @property {AgentOptions} [agent={}] HTTPS Agent options
* @property {string} [api='https://discord.com/api'] Base URL of the API
* @property {string} [cdn='https://cdn.discordapp.com'] Base URL of the CDN
* @property {string} [invite='https://discord.gg'] Base URL of invites
* @property {string} [template='https://discord.new'] Base URL of templates
* @property {Object} [headers] Additional headers to send for all API requests
* @property {string} [scheduledEvent='https://discord.com/events'] Base URL of guild scheduled events
*/
/**
* Contains various utilities for client options.
*/
@@ -72,25 +132,28 @@ class Options extends null {
*/
static createDefault() {
return {
waitGuildTimeout: 15_000,
shardCount: 1,
makeCache: this.cacheWithLimits(this.defaultMakeCacheSettings),
messageCacheLifetime: 0,
messageSweepInterval: 0,
invalidRequestWarningInterval: 0,
intents: 32767,
partials: [],
restWsBridgeTimeout: 5_000,
restRequestTimeout: 15_000,
restGlobalRateLimit: 0,
retryLimit: 1,
restTimeOffset: 500,
restSweepInterval: 60,
failIfNotExists: true,
userAgentSuffix: [],
presence: {},
sweepers: {},
ws: {
jsonTransformer: (object) => JSONBig.stringify(object),
checkUpdate: true,
readyStatus: false,
waitGuildTimeout: 15_000,
shardCount: 1,
makeCache: this.cacheWithLimits(this.defaultMakeCacheSettings),
messageCacheLifetime: 0,
messageSweepInterval: 0,
invalidRequestWarningInterval: 0,
intents: 65535,
partials: [],
restWsBridgeTimeout: 5_000,
restRequestTimeout: 15_000,
restGlobalRateLimit: 0,
retryLimit: 1,
restTimeOffset: 500,
restSweepInterval: 60,
failIfNotExists: true,
userAgentSuffix: [],
presence: {},
sweepers: {},
ws: {
large_threshold: 50,
compress: false,
properties: {
@@ -121,6 +184,7 @@ class Options extends null {
'X-Debug-Options': 'bugReporterEnabled',
'X-Discord-Locale': 'en-US',
Origin: 'https://discord.com',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36',
},
agent: {},
version: 10,
@@ -130,10 +194,7 @@ class Options extends null {
template: 'https://discord.new',
scheduledEvent: 'https://discord.com/events',
},
jsonTransformer: (object) => JSONBig.stringify(object),
checkUpdate: true,
readyStatus: false,
};
};
}
/**
@@ -144,14 +205,32 @@ class Options extends null {
* If LimitedCollectionOptions are provided for a manager, it uses those settings to form a LimitedCollection.
* @returns {CacheFactory}
* @example
* // Store up to 200 messages per channel and 200 members per guild, always keeping the client member.
* // Store up to 200 messages per channel and discard archived threads if they were archived more than 4 hours ago.
* // Note archived threads will remain in the guild and client caches with these settings
* Options.cacheWithLimits({
* MessageManager: 200,
* GuildMemberManager: {
* maxSize: 200,
* keepOverLimit: (member) => member.id === client.user.id,
* ThreadManager: {
* sweepInterval: 3600,
* sweepFilter: LimitedCollection.filterByLifetime({
* getComparisonTimestamp: e => e.archiveTimestamp,
* excludeFromSweep: e => !e.archived,
* }),
* },
* });
* @example
* // Sweep messages every 5 minutes, removing messages that have not been edited or created in the last 30 minutes
* Options.cacheWithLimits({
* // Keep default thread sweeping behavior
* ...Options.defaultMakeCacheSettings,
* // Override MessageManager
* MessageManager: {
* sweepInterval: 300,
* sweepFilter: LimitedCollection.filterByLifetime({
* lifetime: 1800,
* getComparisonTimestamp: e => e.editedTimestamp ?? e.createdTimestamp,
* })
* }
* });
*/
static cacheWithLimits(settings = {}) {
const { Collection } = require('@discordjs/collection');
@@ -169,9 +248,15 @@ class Options extends null {
}
return new LimitedCollection({ maxSize: setting });
}
/* eslint-disable-next-line eqeqeq */
/* eslint-disable eqeqeq */
const noSweeping =
setting.sweepFilter == null ||
setting.sweepInterval == null ||
setting.sweepInterval <= 0 ||
setting.sweepInterval === Infinity;
const noLimit = setting.maxSize == null || setting.maxSize === Infinity;
if (noLimit) {
/* eslint-enable eqeqeq */
if (noSweeping && noLimit) {
return new Collection();
}
return new LimitedCollection(setting);
@@ -201,6 +286,20 @@ class Options extends null {
static get defaultMakeCacheSettings() {
return {
MessageManager: 200,
/*
ChannelManager: {
sweepInterval: 3600,
sweepFilter: require('./Util').archivedThreadSweepFilter(),
},
GuildChannelManager: {
sweepInterval: 3600,
sweepFilter: require('./Util').archivedThreadSweepFilter(),
},
ThreadManager: {
sweepInterval: 3600,
sweepFilter: require('./Util').archivedThreadSweepFilter(),
},
*/
};
}
}
@@ -221,8 +320,3 @@ Options.defaultSweeperSettings = {
};
module.exports = Options;
/**
* @external RESTOptions
* @see {@link https://discord.js.org/#/docs/rest/main/typedef/RESTOptions}
*/

View File

@@ -1,5 +0,0 @@
'use strict';
const { createEnum } = require('./Enums');
module.exports = createEnum(['User', 'Channel', 'GuildMember', 'Message', 'Reaction', 'GuildScheduledEvent']);

182
src/util/Permissions.js Normal file
View File

@@ -0,0 +1,182 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of
* permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrites} for the member
* that override their default permissions.
* @extends {BitField}
*/
class Permissions extends BitField {
/**
* Bitfield of the packed bits
* @type {bigint}
* @name Permissions#bitfield
*/
/**
* Data that can be resolved to give a permission number. This can be:
* * A string (see {@link Permissions.FLAGS})
* * A permission number
* * An instance of Permissions
* * An Array of PermissionResolvable
* @typedef {string|bigint|Permissions|PermissionResolvable[]} PermissionResolvable
*/
/**
* Gets all given bits that are missing from the bitfield.
* @param {BitFieldResolvable} bits Bit(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {string[]}
*/
missing(bits, checkAdmin = true) {
return checkAdmin && this.has(this.constructor.FLAGS.ADMINISTRATOR) ? [] : super.missing(bits);
}
/**
* Checks whether the bitfield has a permission, or any of multiple permissions.
* @param {PermissionResolvable} permission Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
any(permission, checkAdmin = true) {
return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.any(permission);
}
/**
* Checks whether the bitfield has a permission, or multiple permissions.
* @param {PermissionResolvable} permission Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
has(permission, checkAdmin = true) {
return (checkAdmin && super.has(this.constructor.FLAGS.ADMINISTRATOR)) || super.has(permission);
}
/**
* Gets an {@link Array} of bitfield names based on the permissions available.
* @returns {string[]}
*/
toArray() {
return super.toArray(false);
}
}
/**
* Numeric permission flags. All available properties:
* * `CREATE_INSTANT_INVITE` (create invitations to the guild)
* * `KICK_MEMBERS`
* * `BAN_MEMBERS`
* * `ADMINISTRATOR` (implicitly has *all* permissions, and bypasses all channel overwrites)
* * `MANAGE_CHANNELS` (edit and reorder channels)
* * `MANAGE_GUILD` (edit the guild information, region, etc.)
* * `ADD_REACTIONS` (add new reactions to messages)
* * `VIEW_AUDIT_LOG`
* * `PRIORITY_SPEAKER`
* * `STREAM`
* * `VIEW_CHANNEL`
* * `SEND_MESSAGES`
* * `SEND_TTS_MESSAGES`
* * `MANAGE_MESSAGES` (delete messages and reactions)
* * `EMBED_LINKS` (links posted will have a preview embedded)
* * `ATTACH_FILES`
* * `READ_MESSAGE_HISTORY` (view messages that were posted prior to opening Discord)
* * `MENTION_EVERYONE`
* * `USE_EXTERNAL_EMOJIS` (use emojis from different guilds)
* * `VIEW_GUILD_INSIGHTS`
* * `CONNECT` (connect to a voice channel)
* * `SPEAK` (speak in a voice channel)
* * `MUTE_MEMBERS` (mute members across all voice channels)
* * `DEAFEN_MEMBERS` (deafen members across all voice channels)
* * `MOVE_MEMBERS` (move members between voice channels)
* * `USE_VAD` (use voice activity detection)
* * `CHANGE_NICKNAME`
* * `MANAGE_NICKNAMES` (change other members' nicknames)
* * `MANAGE_ROLES`
* * `MANAGE_WEBHOOKS`
* * `MANAGE_EMOJIS_AND_STICKERS`
* * `USE_APPLICATION_COMMANDS`
* * `REQUEST_TO_SPEAK`
* * `MANAGE_EVENTS`
* * `MANAGE_THREADS`
* * `USE_PUBLIC_THREADS` (deprecated)
* * `CREATE_PUBLIC_THREADS`
* * `USE_PRIVATE_THREADS` (deprecated)
* * `CREATE_PRIVATE_THREADS`
* * `USE_EXTERNAL_STICKERS` (use stickers from different guilds)
* * `SEND_MESSAGES_IN_THREADS`
* * `START_EMBEDDED_ACTIVITIES`
* * `MODERATE_MEMBERS`
* @type {Object<string, bigint>}
* @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
*/
Permissions.FLAGS = {
CREATE_INSTANT_INVITE: 1n << 0n,
KICK_MEMBERS: 1n << 1n,
BAN_MEMBERS: 1n << 2n,
ADMINISTRATOR: 1n << 3n,
MANAGE_CHANNELS: 1n << 4n,
MANAGE_GUILD: 1n << 5n,
ADD_REACTIONS: 1n << 6n,
VIEW_AUDIT_LOG: 1n << 7n,
PRIORITY_SPEAKER: 1n << 8n,
STREAM: 1n << 9n,
VIEW_CHANNEL: 1n << 10n,
SEND_MESSAGES: 1n << 11n,
SEND_TTS_MESSAGES: 1n << 12n,
MANAGE_MESSAGES: 1n << 13n,
EMBED_LINKS: 1n << 14n,
ATTACH_FILES: 1n << 15n,
READ_MESSAGE_HISTORY: 1n << 16n,
MENTION_EVERYONE: 1n << 17n,
USE_EXTERNAL_EMOJIS: 1n << 18n,
VIEW_GUILD_INSIGHTS: 1n << 19n,
CONNECT: 1n << 20n,
SPEAK: 1n << 21n,
MUTE_MEMBERS: 1n << 22n,
DEAFEN_MEMBERS: 1n << 23n,
MOVE_MEMBERS: 1n << 24n,
USE_VAD: 1n << 25n,
CHANGE_NICKNAME: 1n << 26n,
MANAGE_NICKNAMES: 1n << 27n,
MANAGE_ROLES: 1n << 28n,
MANAGE_WEBHOOKS: 1n << 29n,
MANAGE_EMOJIS_AND_STICKERS: 1n << 30n,
USE_APPLICATION_COMMANDS: 1n << 31n,
REQUEST_TO_SPEAK: 1n << 32n,
MANAGE_EVENTS: 1n << 33n,
MANAGE_THREADS: 1n << 34n,
// TODO: Remove deprecated USE_*_THREADS flags in v14
USE_PUBLIC_THREADS: 1n << 35n,
CREATE_PUBLIC_THREADS: 1n << 35n,
USE_PRIVATE_THREADS: 1n << 36n,
CREATE_PRIVATE_THREADS: 1n << 36n,
USE_EXTERNAL_STICKERS: 1n << 37n,
SEND_MESSAGES_IN_THREADS: 1n << 38n,
START_EMBEDDED_ACTIVITIES: 1n << 39n,
MODERATE_MEMBERS: 1n << 40n,
};
/**
* Bitfield representing every permission combined
* @type {bigint}
*/
Permissions.ALL = Object.values(Permissions.FLAGS).reduce((all, p) => all | p, 0n);
/**
* Bitfield representing the default permissions for users
* @type {bigint}
*/
Permissions.DEFAULT = BigInt(104324673);
/**
* Bitfield representing the permissions required for moderators of stage channels
* @type {bigint}
*/
Permissions.STAGE_MODERATOR =
Permissions.FLAGS.MANAGE_CHANNELS | Permissions.FLAGS.MUTE_MEMBERS | Permissions.FLAGS.MOVE_MEMBERS;
Permissions.defaultBit = BigInt(0);
module.exports = Permissions;

View File

@@ -1,95 +0,0 @@
'use strict';
const { PermissionFlagsBits } = require('discord-api-types/v9');
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a permission bitfield. All {@link GuildMember}s have a set of
* permissions in their guild, and each channel in the guild may also have {@link PermissionOverwrites} for the member
* that override their default permissions.
* @extends {BitField}
*/
class PermissionsBitField extends BitField {
/**
* Bitfield of the packed bits
* @type {bigint}
* @name Permissions#bitfield
*/
/**
* Data that can be resolved to give a permission number. This can be:
* * A string (see {@link PermissionsBitField.Flags})
* * A permission number
* * An instance of {@link PermissionsBitField}
* * An Array of PermissionResolvable
* @typedef {string|bigint|PermissionsBitField|PermissionResolvable[]} PermissionResolvable
*/
/**
* Gets all given bits that are missing from the bitfield.
* @param {BitFieldResolvable} bits Bit(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {string[]}
*/
missing(bits, checkAdmin = true) {
return checkAdmin && this.has(PermissionFlagsBits.Administrator) ? [] : super.missing(bits);
}
/**
* Checks whether the bitfield has a permission, or any of multiple permissions.
* @param {PermissionResolvable} permission Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
any(permission, checkAdmin = true) {
return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.any(permission);
}
/**
* Checks whether the bitfield has a permission, or multiple permissions.
* @param {PermissionResolvable} permission Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
has(permission, checkAdmin = true) {
return (checkAdmin && super.has(PermissionFlagsBits.Administrator)) || super.has(permission);
}
/**
* Gets an {@link Array} of bitfield names based on the permissions available.
* @returns {string[]}
*/
toArray() {
return super.toArray(false);
}
}
/**
* Numeric permission flags.
* @type {PermissionFlagsBits}
* @see {@link https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags}
*/
PermissionsBitField.Flags = PermissionFlagsBits;
/**
* Bitfield representing every permission combined
* @type {bigint}
*/
PermissionsBitField.All = Object.values(PermissionFlagsBits).reduce((all, p) => all | p, 0n);
/**
* Bitfield representing the default permissions for users
* @type {bigint}
*/
PermissionsBitField.Default = BigInt(104324673);
/**
* Bitfield representing the permissions required for moderators of stage channels
* @type {bigint}
*/
PermissionsBitField.StageModerator =
PermissionFlagsBits.ManageChannels | PermissionFlagsBits.MuteMembers | PermissionFlagsBits.MoveMembers;
PermissionsBitField.defaultBit = BigInt(0);
module.exports = PermissionsBitField;

View File

@@ -1,10 +0,0 @@
'use strict';
module.exports = {
Close: 'close',
Destroyed: 'destroyed',
InvalidSession: 'invalidSession',
Ready: 'ready',
Resumed: 'resumed',
AllReady: 'allReady',
};

92
src/util/SnowflakeUtil.js Normal file
View File

@@ -0,0 +1,92 @@
'use strict';
// Discord epoch (2015-01-01T00:00:00.000Z)
const EPOCH = 1_420_070_400_000;
let INCREMENT = BigInt(0);
/**
* A container for useful snowflake-related methods.
*/
class SnowflakeUtil extends null {
/**
* A {@link https://developer.twitter.com/en/docs/twitter-ids Twitter snowflake},
* except the epoch is 2015-01-01T00:00:00.000Z.
*
* If we have a snowflake '266241948824764416' we can represent it as binary:
* ```
* 64 22 17 12 0
* 000000111011000111100001101001000101000000 00001 00000 000000000000
* number of ms since Discord epoch worker pid increment
* ```
* @typedef {string} Snowflake
*/
/**
* Generates a Discord snowflake.
* <info>This hardcodes the worker's id as 1 and the process's id as 0.</info>
* @param {number|Date} [timestamp=Date.now()] Timestamp or date of the snowflake to generate
* @returns {Snowflake} The generated snowflake
*/
static generate(timestamp = Date.now()) {
if (timestamp instanceof Date) timestamp = timestamp.getTime();
if (typeof timestamp !== 'number' || isNaN(timestamp)) {
throw new TypeError(
`"timestamp" argument must be a number (received ${isNaN(timestamp) ? 'NaN' : typeof timestamp})`,
);
}
if (INCREMENT >= 4095n) INCREMENT = BigInt(0);
// Assign WorkerId as 1 and ProcessId as 0:
return ((BigInt(timestamp - EPOCH) << 22n) | (1n << 17n) | INCREMENT++).toString();
}
/**
* A deconstructed snowflake.
* @typedef {Object} DeconstructedSnowflake
* @property {number} timestamp Timestamp the snowflake was created
* @property {Date} date Date the snowflake was created
* @property {number} workerId The worker's id in the snowflake
* @property {number} processId The process's id in the snowflake
* @property {number} increment Increment in the snowflake
* @property {string} binary Binary representation of the snowflake
*/
/**
* Deconstructs a Discord snowflake.
* @param {Snowflake} snowflake Snowflake to deconstruct
* @returns {DeconstructedSnowflake}
*/
static deconstruct(snowflake) {
const bigIntSnowflake = BigInt(snowflake);
return {
timestamp: Number(bigIntSnowflake >> 22n) + EPOCH,
get date() {
return new Date(this.timestamp);
},
workerId: Number((bigIntSnowflake >> 17n) & 0b11111n),
processId: Number((bigIntSnowflake >> 12n) & 0b11111n),
increment: Number(bigIntSnowflake & 0b111111111111n),
binary: bigIntSnowflake.toString(2).padStart(64, '0'),
};
}
/**
* Retrieves the timestamp field's value from a Discord snowflake.
* @param {Snowflake} snowflake Snowflake to get the timestamp value from
* @returns {number}
*/
static timestampFrom(snowflake) {
return Number(BigInt(snowflake) >> 22n) + EPOCH;
}
/**
* Discord's epoch value (2015-01-01T00:00:00.000Z).
* @type {number}
* @readonly
*/
static get EPOCH() {
return EPOCH;
}
}
module.exports = SnowflakeUtil;

View File

@@ -1,15 +0,0 @@
'use strict';
const { createEnum } = require('./Enums');
module.exports = createEnum([
'Ready',
'Connecting',
'Reconnecting',
'Idle',
'Nearly',
'Disconnected',
'WaitingForGuilds',
'Identifying',
'Resuming',
]);

View File

@@ -1,8 +1,7 @@
'use strict';
const { setInterval, clearInterval } = require('node:timers');
const { ThreadChannelTypes, SweeperKeys } = require('./Constants');
const Events = require('./Events');
const { setInterval } = require('node:timers');
const { Events, ThreadChannelTypes, SweeperKeys } = require('./Constants');
const { TypeError } = require('../errors/DJSError.js');
/**
@@ -72,7 +71,7 @@ class Sweepers {
const globalCommands = this.client.application?.commands.cache.sweep(filter) ?? 0;
this.client.emit(
Events.CacheSweep,
Events.CACHE_SWEEP,
`Swept ${globalCommands} global application commands and ${guildCommands} guild commands in ${guilds} guilds.`,
);
return guildCommands + globalCommands;
@@ -137,12 +136,12 @@ class Sweepers {
let messages = 0;
for (const channel of this.client.channels.cache.values()) {
if (!channel.isTextBased()) continue;
if (!channel.isText()) continue;
channels++;
messages += channel.messages.cache.sweep(filter);
}
this.client.emit(Events.CacheSweep, `Swept ${messages} messages in ${channels} text-based channels.`);
this.client.emit(Events.CACHE_SWEEP, `Swept ${messages} messages in ${channels} text-based channels.`);
return messages;
}
@@ -169,7 +168,7 @@ class Sweepers {
let reactions = 0;
for (const channel of this.client.channels.cache.values()) {
if (!channel.isTextBased()) continue;
if (!channel.isText()) continue;
channels++;
for (const message of channel.messages.cache.values()) {
@@ -178,7 +177,7 @@ class Sweepers {
}
}
this.client.emit(
Events.CacheSweep,
Events.CACHE_SWEEP,
`Swept ${reactions} reactions on ${messages} messages in ${channels} text-based channels.`,
);
return reactions;
@@ -193,15 +192,6 @@ class Sweepers {
return this._sweepGuildDirectProp('stageInstances', filter, { outputName: 'stage instances' }).items;
}
/**
* Sweeps all guild stickers and removes the ones which are indicated by the filter.
* @param {Function} filter The function used to determine which stickers will be removed from the caches.
* @returns {number} Amount of stickers that were removed from the caches
*/
sweepStickers(filter) {
return this._sweepGuildDirectProp('stickers', filter).items;
}
/**
* Sweeps all thread members and removes the ones which are indicated by the filter.
* <info>It is highly recommended to keep the client thread member cached</info>
@@ -220,7 +210,7 @@ class Sweepers {
threads++;
members += channel.members.cache.sweep(filter);
}
this.client.emit(Events.CacheSweep, `Swept ${members} thread members in ${threads} threads.`);
this.client.emit(Events.CACHE_SWEEP, `Swept ${members} thread members in ${threads} threads.`);
return members;
}
@@ -251,7 +241,7 @@ class Sweepers {
this.client.channels._remove(key);
}
}
this.client.emit(Events.CacheSweep, `Swept ${threads} threads.`);
this.client.emit(Events.CACHE_SWEEP, `Swept ${threads} threads.`);
return threads;
}
@@ -267,7 +257,7 @@ class Sweepers {
const users = this.client.users.cache.sweep(filter);
this.client.emit(Events.CacheSweep, `Swept ${users} users.`);
this.client.emit(Events.CACHE_SWEEP, `Swept ${users} users.`);
return users;
}
@@ -405,7 +395,7 @@ class Sweepers {
}
if (emit) {
this.client.emit(Events.CacheSweep, `Swept ${items} ${outputName ?? key} in ${guilds} guilds.`);
this.client.emit(Events.CACHE_SWEEP, `Swept ${items} ${outputName ?? key} in ${guilds} guilds.`);
}
return { guilds, items };

View File

@@ -0,0 +1,51 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link Guild#systemChannelFlags} bitfield.
* <info>Note that all event message types are enabled by default,
* and by setting their corresponding flags you are disabling them</info>
* @extends {BitField}
*/
class SystemChannelFlags extends BitField {}
/**
* @name SystemChannelFlags
* @kind constructor
* @memberof SystemChannelFlags
* @param {SystemChannelFlagsResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name SystemChannelFlags#bitfield
*/
/**
* Data that can be resolved to give a system channel flag bitfield. This can be:
* * A string (see {@link SystemChannelFlags.FLAGS})
* * A system channel flag
* * An instance of SystemChannelFlags
* * An Array of SystemChannelFlagsResolvable
* @typedef {string|number|SystemChannelFlags|SystemChannelFlagsResolvable[]} SystemChannelFlagsResolvable
*/
/**
* Numeric system channel flags. All available properties:
* * `SUPPRESS_JOIN_NOTIFICATIONS` (Suppress member join notifications)
* * `SUPPRESS_PREMIUM_SUBSCRIPTIONS` (Suppress server boost notifications)
* * `SUPPRESS_GUILD_REMINDER_NOTIFICATIONS` (Suppress server setup tips)
* * `SUPPRESS_JOIN_NOTIFICATION_REPLIES` (Hide member join sticker reply buttons)
* @type {Object}
* @see {@link https://discord.com/developers/docs/resources/guild#guild-object-system-channel-flags}
*/
SystemChannelFlags.FLAGS = {
SUPPRESS_JOIN_NOTIFICATIONS: 1 << 0,
SUPPRESS_PREMIUM_SUBSCRIPTIONS: 1 << 1,
SUPPRESS_GUILD_REMINDER_NOTIFICATIONS: 1 << 2,
SUPPRESS_JOIN_NOTIFICATION_REPLIES: 1 << 3,
};
module.exports = SystemChannelFlags;

View File

@@ -1,42 +0,0 @@
'use strict';
const { GuildSystemChannelFlags } = require('discord-api-types/v9');
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link Guild#systemChannelFlags} bitfield.
* <info>Note that all event message types are enabled by default,
* and by setting their corresponding flags you are disabling them</info>
* @extends {BitField}
*/
class SystemChannelFlagsBitField extends BitField {}
/**
* @name SystemChannelFlagsBitField
* @kind constructor
* @memberof SystemChannelFlagsBitField
* @param {SystemChannelFlagsResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name SystemChannelFlagsBitField#bitfield
*/
/**
* Data that can be resolved to give a system channel flag bitfield. This can be:
* * A string (see {@link SystemChannelFlagsBitField.Flags})
* * A system channel flag
* * An instance of SystemChannelFlagsBitField
* * An Array of SystemChannelFlagsResolvable
* @typedef {string|number|SystemChannelFlagsBitField|SystemChannelFlagsResolvable[]} SystemChannelFlagsResolvable
*/
/**
* Numeric system channel flags.
* @type {GuildSystemChannelFlags}
*/
SystemChannelFlagsBitField.Flags = GuildSystemChannelFlags;
module.exports = SystemChannelFlagsBitField;

View File

@@ -6,25 +6,25 @@ const BitField = require('./BitField');
* Data structure that makes it easy to interact with a {@link ThreadMember#flags} bitfield.
* @extends {BitField}
*/
class ThreadMemberFlagsBitField extends BitField {}
class ThreadMemberFlags extends BitField {}
/**
* @name ThreadMemberFlagsBitField
* @name ThreadMemberFlags
* @kind constructor
* @memberof ThreadMemberFlagsBitField
* @memberof ThreadMemberFlags
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name ThreadMemberFlagsBitField#bitfield
* @name ThreadMemberFlags#bitfield
*/
/**
* Numeric thread member flags. There are currently no bitflags relevant to bots for this.
* @type {Object<string, number>}
*/
ThreadMemberFlagsBitField.Flags = {};
ThreadMemberFlags.FLAGS = {};
module.exports = ThreadMemberFlagsBitField;
module.exports = ThreadMemberFlags;

View File

@@ -1,20 +0,0 @@
'use strict';
const snakeCase = require('lodash.snakecase');
class Transformers extends null {
/**
* Transforms camel-cased keys into snake cased keys
* @param {*} obj The object to transform
* @returns {*}
*/
static toSnakeCase(obj) {
if (typeof obj !== 'object' || !obj) return obj;
if (Array.isArray(obj)) return obj.map(Transformers.toSnakeCase);
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => [snakeCase(key), Transformers.toSnakeCase(value)]),
);
}
}
module.exports = Transformers;

59
src/util/UserFlags.js Normal file
View File

@@ -0,0 +1,59 @@
'use strict';
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link User#flags} bitfield.
* @extends {BitField}
*/
class UserFlags extends BitField {}
/**
* @name UserFlags
* @kind constructor
* @memberof UserFlags
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name UserFlags#bitfield
*/
/**
* Numeric user flags. All available properties:
* * `DISCORD_EMPLOYEE`
* * `PARTNERED_SERVER_OWNER`
* * `HYPESQUAD_EVENTS`
* * `BUGHUNTER_LEVEL_1`
* * `HOUSE_BRAVERY`
* * `HOUSE_BRILLIANCE`
* * `HOUSE_BALANCE`
* * `EARLY_SUPPORTER`
* * `TEAM_USER`
* * `BUGHUNTER_LEVEL_2`
* * `VERIFIED_BOT`
* * `EARLY_VERIFIED_BOT_DEVELOPER`
* * `DISCORD_CERTIFIED_MODERATOR`
* * `BOT_HTTP_INTERACTIONS`
* @type {Object}
* @see {@link https://discord.com/developers/docs/resources/user#user-object-user-flags}
*/
UserFlags.FLAGS = {
DISCORD_EMPLOYEE: 1 << 0,
PARTNERED_SERVER_OWNER: 1 << 1,
HYPESQUAD_EVENTS: 1 << 2,
BUGHUNTER_LEVEL_1: 1 << 3,
HOUSE_BRAVERY: 1 << 6,
HOUSE_BRILLIANCE: 1 << 7,
HOUSE_BALANCE: 1 << 8,
EARLY_SUPPORTER: 1 << 9,
TEAM_USER: 1 << 10,
BUGHUNTER_LEVEL_2: 1 << 14,
VERIFIED_BOT: 1 << 16,
EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17,
DISCORD_CERTIFIED_MODERATOR: 1 << 18,
BOT_HTTP_INTERACTIONS: 1 << 19,
};
module.exports = UserFlags;

View File

@@ -1,31 +0,0 @@
'use strict';
const { UserFlags } = require('discord-api-types/v9');
const BitField = require('./BitField');
/**
* Data structure that makes it easy to interact with a {@link User#flags} bitfield.
* @extends {BitField}
*/
class UserFlagsBitField extends BitField {}
/**
* @name UserFlagsBitField
* @kind constructor
* @memberof UserFlagsBitField
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
*/
/**
* Bitfield of the packed bits
* @type {number}
* @name UserFlagsBitField#bitfield
*/
/**
* Numeric user flags.
* @type {UserFlags}
*/
UserFlagsBitField.Flags = UserFlags;
module.exports = UserFlagsBitField;

View File

@@ -1,13 +1,17 @@
'use strict';
const { parse } = require('node:path');
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { ChannelType, RouteBases, Routes } = require('discord-api-types/v9');
const { fetch } = require('undici');
const Colors = require('./Colors');
const fetch = require('node-fetch');
const { Colors, Endpoints } = require('./Constants');
const Options = require('./Options');
const { Error: DiscordError, RangeError, TypeError } = require('../errors');
const has = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
const isObject = d => typeof d === 'object' && d !== null;
let deprecationEmittedForRemoveMentions = false;
/**
* Contains various general-purpose utility methods.
*/
@@ -268,7 +272,8 @@ class Util extends null {
*/
static async fetchRecommendedShards(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) {
if (!token) throw new DiscordError('TOKEN_MISSING');
const response = await fetch(RouteBases.api + Routes.gatewayBot(), {
const defaults = Options.createDefault();
const response = await fetch(`${defaults.http.api}/v${defaults.http.version}${Endpoints.botGateway}`, {
method: 'GET',
headers: { Authorization: `Bot ${token.replace(/^Bot\s*/i, '')}` },
});
@@ -330,7 +335,7 @@ class Util extends null {
static mergeDefault(def, given) {
if (!given) return def;
for (const key in def) {
if (!Object.hasOwn(given, key) || given[key] === undefined) {
if (!has(given, key) || given[key] === undefined) {
given[key] = def[key];
} else if (given[key] === Object(given[key])) {
given[key] = Util.mergeDefault(def[key], given[key]);
@@ -419,37 +424,37 @@ class Util extends null {
* [255, 0, 255] // purple
* ```
* or one of the following strings:
* - `Default`
* - `White`
* - `Aqua`
* - `Green`
* - `Blue`
* - `Yellow`
* - `Purple`
* - `LuminousVividPink`
* - `Fuchsia`
* - `Gold`
* - `Orange`
* - `Red`
* - `Grey`
* - `Navy`
* - `DarkAqua`
* - `DarkGreen`
* - `DarkBlue`
* - `DarkPurple`
* - `DarkVividPink`
* - `DarkGold`
* - `DarkOrange`
* - `DarkRed`
* - `DarkGrey`
* - `DarkerGrey`
* - `LightGrey`
* - `DarkNavy`
* - `Blurple`
* - `Greyple`
* - `DarkButNotBlack`
* - `NotQuiteBlack`
* - `Random`
* - `DEFAULT`
* - `WHITE`
* - `AQUA`
* - `GREEN`
* - `BLUE`
* - `YELLOW`
* - `PURPLE`
* - `LUMINOUS_VIVID_PINK`
* - `FUCHSIA`
* - `GOLD`
* - `ORANGE`
* - `RED`
* - `GREY`
* - `NAVY`
* - `DARK_AQUA`
* - `DARK_GREEN`
* - `DARK_BLUE`
* - `DARK_PURPLE`
* - `DARK_VIVID_PINK`
* - `DARK_GOLD`
* - `DARK_ORANGE`
* - `DARK_RED`
* - `DARK_GREY`
* - `DARKER_GREY`
* - `LIGHT_GREY`
* - `DARK_NAVY`
* - `BLURPLE`
* - `GREYPLE`
* - `DARK_BUT_NOT_BLACK`
* - `NOT_QUITE_BLACK`
* - `RANDOM`
* @typedef {string|number|number[]} ColorResolvable
*/
@@ -460,8 +465,8 @@ class Util extends null {
*/
static resolveColor(color) {
if (typeof color === 'string') {
if (color === 'Random') return Math.floor(Math.random() * (0xffffff + 1));
if (color === 'Default') return 0;
if (color === 'RANDOM') return Math.floor(Math.random() * (0xffffff + 1));
if (color === 'DEFAULT') return 0;
color = Colors[color] ?? parseInt(color.replace('#', ''), 16);
} else if (Array.isArray(color)) {
color = (color[0] << 16) + (color[1] << 8) + color[2];
@@ -493,17 +498,16 @@ class Util extends null {
* @param {number} position New position for the object
* @param {boolean} relative Whether `position` is relative to its current position
* @param {Collection<string, Channel|Role>} sorted A collection of the objects sorted properly
* @param {Client} client The client to use to patch the data
* @param {string} route Route to call PATCH on
* @param {APIRouter} route Route to call PATCH on
* @param {string} [reason] Reason for the change
* @returns {Promise<Channel[]|Role[]>} Updated item list, with `id` and `position` properties
* @private
*/
static async setPosition(item, position, relative, sorted, client, route, reason) {
static async setPosition(item, position, relative, sorted, route, reason) {
let updatedItems = [...sorted.values()];
Util.moveElementInArray(updatedItems, item, position, relative);
updatedItems = updatedItems.map((r, i) => ({ id: r.id, position: i }));
await client.rest.patch(route, { body: updatedItems, reason });
await route.patch({ data: updatedItems, reason });
return updatedItems;
}
@@ -518,8 +522,34 @@ class Util extends null {
const res = parse(path);
return ext && res.ext.startsWith(ext) ? res.name : res.base.split('?')[0];
}
/**
* Breaks user, role and everyone/here mentions by adding a zero width space after every @ character
* @param {string} str The string to sanitize
* @returns {string}
* @deprecated Use {@link BaseMessageOptions#allowedMentions} instead.
*/
static removeMentions(str) {
if (!deprecationEmittedForRemoveMentions) {
process.emitWarning(
'The Util.removeMentions method is deprecated. Use MessageOptions#allowedMentions instead.',
'DeprecationWarning',
);
deprecationEmittedForRemoveMentions = true;
}
return Util._removeMentions(str);
}
static _removeMentions(str) {
return str.replaceAll('@', '@\u200b');
}
/**
* The content to have all mentions replaced by the equivalent text.
* <warn>When {@link Util.removeMentions} is removed, this method will no longer sanitize mentions.
* Use {@link BaseMessageOptions#allowedMentions} instead to prevent mentions when sending a message.</warn>
* @param {string} str The string to be converted
* @param {TextBasedChannels} channel The channel the string was sent in
* @returns {string}
@@ -528,17 +558,17 @@ class Util extends null {
str = str
.replace(/<@!?[0-9]+>/g, input => {
const id = input.replace(/<|!|>|@/g, '');
if (channel.type === ChannelType.DM) {
if (channel.type === 'DM') {
const user = channel.client.users.cache.get(id);
return user ? `@${user.username}` : input;
return user ? Util._removeMentions(`@${user.username}`) : input;
}
const member = channel.guild.members.cache.get(id);
if (member) {
return `@${member.displayName}`;
return Util._removeMentions(`@${member.displayName}`);
} else {
const user = channel.client.users.cache.get(id);
return user ? `@${user.username}` : input;
return user ? Util._removeMentions(`@${user.username}`) : input;
}
})
.replace(/<#[0-9]+>/g, input => {
@@ -546,7 +576,7 @@ class Util extends null {
return mentionedChannel ? `#${mentionedChannel.name}` : input;
})
.replace(/<@&[0-9]+>/g, input => {
if (channel.type === ChannelType.DM) return input;
if (channel.type === 'DM') return input;
const role = channel.guild.roles.cache.get(input.replace(/<|@|>|&/g, ''));
return role ? `@${role.name}` : input;
});
@@ -561,6 +591,18 @@ class Util extends null {
static cleanCodeBlockContent(text) {
return text.replaceAll('```', '`\u200b``');
}
/**
* Creates a sweep filter that sweeps archived threads
* @param {number} [lifetime=14400] How long a thread has to be archived to be valid for sweeping
* @deprecated When not using with `makeCache` use `Sweepers.archivedThreadSweepFilter` instead
* @returns {SweepFilter}
*/
static archivedThreadSweepFilter(lifetime = 14400) {
const filter = require('./Sweepers').archivedThreadSweepFilter(lifetime);
filter.isDefault = true;
return filter;
}
}
module.exports = Util;