Initial commit of Discord bot

This commit is contained in:
VinceC
2024-09-16 04:22:47 -05:00
commit d50c7c5b15
3950 changed files with 365981 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { isJSONEncodable } = require('@discordjs/util');
const { Routes } = require('discord-api-types/v10');
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const ApplicationCommand = require('../structures/ApplicationCommand');
const PermissionsBitField = require('../util/PermissionsBitField');
/**
* Manages API methods for application commands and stores their cache.
* @extends {CachedManager}
*/
class ApplicationCommandManager extends CachedManager {
constructor(client, iterable) {
super(client, ApplicationCommand, iterable);
/**
* The manager for permissions of arbitrary commands on arbitrary guilds
* @type {ApplicationCommandPermissionsManager}
*/
this.permissions = new ApplicationCommandPermissionsManager(this);
}
/**
* The cache of this manager
* @type {Collection<Snowflake, ApplicationCommand>}
* @name ApplicationCommandManager#cache
*/
_add(data, cache, guildId) {
return super._add(data, cache, { extras: [this.guild, guildId] });
}
/**
* The APIRouter path to the commands
* @param {Snowflake} [options.id] The application command's id
* @param {Snowflake} [options.guildId] The guild's id to use in the path,
* ignored when using a {@link GuildApplicationCommandManager}
* @returns {string}
* @private
*/
commandPath({ id, guildId } = {}) {
if (this.guild ?? guildId) {
if (id) {
return Routes.applicationGuildCommand(this.client.application.id, this.guild?.id ?? guildId, id);
}
return Routes.applicationGuildCommands(this.client.application.id, this.guild?.id ?? guildId);
}
if (id) {
return Routes.applicationCommand(this.client.application.id, id);
}
return Routes.applicationCommands(this.client.application.id);
}
/**
* Data that resolves to give an ApplicationCommand object. This can be:
* * An ApplicationCommand object
* * A Snowflake
* @typedef {ApplicationCommand|Snowflake} ApplicationCommandResolvable
*/
/**
* Data that resolves to the data of an ApplicationCommand
* @typedef {ApplicationCommandData|APIApplicationCommand} ApplicationCommandDataResolvable
*/
/**
* Options used to fetch data from Discord
* @typedef {Object} BaseFetchOptions
* @property {boolean} [cache=true] Whether to cache the fetched data if it wasn't already
* @property {boolean} [force=false] Whether to skip the cache check and request the API
*/
/**
* Options used to fetch Application Commands from Discord
* @typedef {BaseFetchOptions} FetchApplicationCommandOptions
* @property {Snowflake} [guildId] The guild's id to fetch commands for, for when the guild is not cached
* @property {LocaleString} [locale] The locale to use when fetching this command
* @property {boolean} [withLocalizations] Whether to fetch all localization data
*/
/**
* Obtains one or multiple application commands from Discord, or the cache if it's already available.
* @param {Snowflake|FetchApplicationCommandOptions} [id] Options for fetching application command(s)
* @param {FetchApplicationCommandOptions} [options] Additional options for this fetch
* @returns {Promise<ApplicationCommand|Collection<Snowflake, ApplicationCommand>>}
* @example
* // Fetch a single command
* client.application.commands.fetch('123456789012345678')
* .then(command => console.log(`Fetched command ${command.name}`))
* .catch(console.error);
* @example
* // Fetch all commands
* guild.commands.fetch()
* .then(commands => console.log(`Fetched ${commands.size} commands`))
* .catch(console.error);
*/
async fetch(id, { guildId, cache = true, force = false, locale, withLocalizations } = {}) {
if (typeof id === 'object') {
({ guildId, cache = true, locale, withLocalizations } = id);
} else if (id) {
if (!force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
const command = await this.client.rest.get(this.commandPath({ id, guildId }));
return this._add(command, cache);
}
const data = await this.client.rest.get(this.commandPath({ guildId }), {
headers: {
'X-Discord-Locale': locale,
},
query: makeURLSearchParams({ with_localizations: withLocalizations }),
});
return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection());
}
/**
* Creates an application command.
* @param {ApplicationCommandDataResolvable} command The command
* @param {Snowflake} [guildId] The guild's id to create this command in,
* ignored when using a {@link GuildApplicationCommandManager}
* @returns {Promise<ApplicationCommand>}
* @example
* // Create a new command
* client.application.commands.create({
* name: 'test',
* description: 'A test command',
* })
* .then(console.log)
* .catch(console.error);
*/
async create(command, guildId) {
const data = await this.client.rest.post(this.commandPath({ guildId }), {
body: this.constructor.transformCommand(command),
});
return this._add(data, true, guildId);
}
/**
* Sets all the commands for this application or guild.
* @param {ApplicationCommandDataResolvable[]} commands The commands
* @param {Snowflake} [guildId] The guild's id to create the commands in,
* ignored when using a {@link GuildApplicationCommandManager}
* @returns {Promise<Collection<Snowflake, ApplicationCommand>>}
* @example
* // Set all commands to just this one
* client.application.commands.set([
* {
* name: 'test',
* description: 'A test command',
* },
* ])
* .then(console.log)
* .catch(console.error);
* @example
* // Remove all commands
* guild.commands.set([])
* .then(console.log)
* .catch(console.error);
*/
async set(commands, guildId) {
const data = await this.client.rest.put(this.commandPath({ guildId }), {
body: commands.map(command => this.constructor.transformCommand(command)),
});
return data.reduce(
(collection, command) => collection.set(command.id, this._add(command, true, guildId)),
new Collection(),
);
}
/**
* Edits an application command.
* @param {ApplicationCommandResolvable} command The command to edit
* @param {Partial<ApplicationCommandDataResolvable>} data The data to update the command with
* @param {Snowflake} [guildId] The guild's id where the command registered,
* ignored when using a {@link GuildApplicationCommandManager}
* @returns {Promise<ApplicationCommand>}
* @example
* // Edit an existing command
* client.application.commands.edit('123456789012345678', {
* description: 'New description',
* })
* .then(console.log)
* .catch(console.error);
*/
async edit(command, data, guildId) {
const id = this.resolveId(command);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable');
const patched = await this.client.rest.patch(this.commandPath({ id, guildId }), {
body: this.constructor.transformCommand(data),
});
return this._add(patched, true, guildId);
}
/**
* Deletes an application command.
* @param {ApplicationCommandResolvable} command The command to delete
* @param {Snowflake} [guildId] The guild's id where the command is registered,
* ignored when using a {@link GuildApplicationCommandManager}
* @returns {Promise<?ApplicationCommand>}
* @example
* // Delete a command
* guild.commands.delete('123456789012345678')
* .then(console.log)
* .catch(console.error);
*/
async delete(command, guildId) {
const id = this.resolveId(command);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable');
await this.client.rest.delete(this.commandPath({ id, guildId }));
const cached = this.cache.get(id);
this.cache.delete(id);
return cached ?? null;
}
/**
* Transforms an {@link ApplicationCommandData} object into something that can be used with the API.
* @param {ApplicationCommandDataResolvable} command The command to transform
* @returns {APIApplicationCommand}
* @private
*/
static transformCommand(command) {
if (isJSONEncodable(command)) return command.toJSON();
let default_member_permissions;
if ('default_member_permissions' in command) {
default_member_permissions = command.default_member_permissions
? new PermissionsBitField(BigInt(command.default_member_permissions)).bitfield.toString()
: command.default_member_permissions;
}
if ('defaultMemberPermissions' in command) {
default_member_permissions =
command.defaultMemberPermissions !== null
? new PermissionsBitField(command.defaultMemberPermissions).bitfield.toString()
: command.defaultMemberPermissions;
}
return {
name: command.name,
name_localizations: command.nameLocalizations ?? command.name_localizations,
description: command.description,
nsfw: command.nsfw,
description_localizations: command.descriptionLocalizations ?? command.description_localizations,
type: command.type,
options: command.options?.map(option => ApplicationCommand.transformOption(option)),
default_member_permissions,
dm_permission: command.dmPermission ?? command.dm_permission,
integration_types: command.integrationTypes ?? command.integration_types,
contexts: command.contexts,
};
}
}
module.exports = ApplicationCommandManager;

View File

@@ -0,0 +1,428 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { ApplicationCommandPermissionType, RESTJSONErrorCodes, Routes } = require('discord-api-types/v10');
const BaseManager = require('./BaseManager');
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
/**
* Manages API methods for permissions of Application Commands.
* @extends {BaseManager}
*/
class ApplicationCommandPermissionsManager extends BaseManager {
constructor(manager) {
super(manager.client);
/**
* The manager or command that this manager belongs to
* @type {ApplicationCommandManager|ApplicationCommand}
* @private
*/
this.manager = manager;
/**
* The guild that this manager acts on
* @type {?Guild}
*/
this.guild = manager.guild ?? null;
/**
* The id of the guild that this manager acts on
* @type {?Snowflake}
*/
this.guildId = manager.guildId ?? manager.guild?.id ?? null;
/**
* The id of the command this manager acts on
* @type {?Snowflake}
*/
this.commandId = manager.id ?? null;
}
/**
* The APIRouter path to the commands
* @param {Snowflake} guildId The guild's id to use in the path,
* @param {Snowflake} [commandId] The application command's id
* @returns {string}
* @private
*/
permissionsPath(guildId, commandId) {
if (commandId) {
return Routes.applicationCommandPermissions(this.client.application.id, guildId, commandId);
}
return Routes.guildApplicationCommandsPermissions(this.client.application.id, guildId);
}
/* eslint-disable max-len */
/**
* The object returned when fetching permissions for an application command.
* @typedef {Object} ApplicationCommandPermissions
* @property {Snowflake} id The role, user, or channel's id. Can also be a
* {@link https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-constants permission constant}.
* @property {ApplicationCommandPermissionType} type Whether this permission is for a role or a user
* @property {boolean} permission Whether the role or user has the permission to use this command
*/
/* eslint-enable max-len */
/**
* Options for managing permissions for one or more Application Commands
* <warn>When passing these options to a manager where `guildId` is `null`,
* `guild` is a required parameter</warn>
* @typedef {Object} BaseApplicationCommandPermissionsOptions
* @property {GuildResolvable} [guild] The guild to modify / check permissions for
* <warn>Ignored when the manager has a non-null `guildId` property</warn>
* @property {ApplicationCommandResolvable} [command] The command to modify / check permissions for
* <warn>Ignored when the manager has a non-null `commandId` property</warn>
*/
/**
* Fetches the permissions for one or multiple commands. Providing the client's id as the "command id" will fetch
* *only* the guild level permissions
* @param {BaseApplicationCommandPermissionsOptions} [options] Options used to fetch permissions
* @returns {Promise<ApplicationCommandPermissions[]|Collection<Snowflake, ApplicationCommandPermissions[]>>}
* @example
* // Fetch permissions for one command
* guild.commands.permissions.fetch({ command: '123456789012345678' })
* .then(perms => console.log(`Fetched ${perms.length} overwrites`))
* .catch(console.error);
* @example
* // Fetch permissions for all commands in a guild
* client.application.commands.permissions.fetch({ guild: '123456789012345678' })
* .then(perms => console.log(`Fetched permissions for ${perms.size} commands`))
* .catch(console.error);
* @example
* // Fetch guild level permissions
* guild.commands.permissions.fetch({ command: client.user.id })
* .then(perms => console.log(`Fetched ${perms.length} guild level permissions`))
* .catch(console.error);
*/
async fetch({ guild, command } = {}) {
const { guildId, commandId } = this._validateOptions(guild, command);
if (commandId) {
const data = await this.client.rest.get(this.permissionsPath(guildId, commandId));
return data.permissions;
}
const data = await this.client.rest.get(this.permissionsPath(guildId));
return data.reduce((coll, perm) => coll.set(perm.id, perm.permissions), new Collection());
}
/**
* Options used to set permissions for one or more Application Commands in a guild
* <warn>Omitting the `command` parameter edits the guild wide permissions
* when the manager's `commandId` is `null`</warn>
* @typedef {BaseApplicationCommandPermissionsOptions} ApplicationCommandPermissionsEditOptions
* @property {ApplicationCommandPermissions[]} permissions The new permissions for the guild or overwrite
* @property {string} token The bearer token to use that authorizes the permission edit
*/
/**
* Sets the permissions for the guild or a command overwrite.
* @param {ApplicationCommandPermissionsEditOptions} options Options used to set permissions
* @returns {Promise<ApplicationCommandPermissions[]|Collection<Snowflake, ApplicationCommandPermissions[]>>}
* @example
* // Set a permission overwrite for a command
* client.application.commands.permissions.set({
* guild: '892455839386304532',
* command: '123456789012345678',
* token: 'TotallyRealToken',
* permissions: [
* {
* id: '876543210987654321',
* type: ApplicationCommandPermissionType.User,
* permission: false,
* },
* ]})
* .then(console.log)
* .catch(console.error);
* @example
* // Set the permissions used for the guild (commands without overwrites)
* guild.commands.permissions.set({ token: 'TotallyRealToken', permissions: [
* {
* id: '123456789012345678',
* permissions: [{
* id: '876543210987654321',
* type: ApplicationCommandPermissionType.User,
* permission: false,
* }],
* },
* ]})
* .then(console.log)
* .catch(console.error);
*/
async set({ guild, command, permissions, token } = {}) {
if (!token) {
throw new DiscordjsError(ErrorCodes.ApplicationCommandPermissionsTokenMissing);
}
let { guildId, commandId } = this._validateOptions(guild, command);
if (!Array.isArray(permissions)) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'permissions',
'Array of ApplicationCommandPermissions',
true,
);
}
if (!commandId) {
commandId = this.client.user.id;
}
const data = await this.client.rest.put(this.permissionsPath(guildId, commandId), {
body: { permissions },
auth: false,
headers: { Authorization: `Bearer ${token}` },
});
return data.permissions;
}
/**
* Add permissions to a command.
* @param {ApplicationCommandPermissionsEditOptions} options Options used to add permissions
* @returns {Promise<ApplicationCommandPermissions[]>}
* @example
* // Add a rule to block a role from using a command
* guild.commands.permissions.add({ command: '123456789012345678', token: 'TotallyRealToken', permissions: [
* {
* id: '876543211234567890',
* type: ApplicationCommandPermissionType.Role,
* permission: false
* },
* ]})
* .then(console.log)
* .catch(console.error);
*/
async add({ guild, command, permissions, token } = {}) {
if (!token) {
throw new DiscordjsError(ErrorCodes.ApplicationCommandPermissionsTokenMissing);
}
let { guildId, commandId } = this._validateOptions(guild, command);
if (!commandId) {
commandId = this.client.user.id;
}
if (!Array.isArray(permissions)) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'permissions',
'Array of ApplicationCommandPermissions',
true,
);
}
let existingPermissions = [];
try {
existingPermissions = await this.fetch({ guild: guildId, command: commandId });
} catch (error) {
if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error;
}
const newPermissions = permissions.slice();
for (const existingPermission of existingPermissions) {
if (!newPermissions.some(newPermission => newPermission.id === existingPermission.id)) {
newPermissions.push(existingPermission);
}
}
return this.set({ guild: guildId, command: commandId, permissions: newPermissions, token });
}
/**
* A static snowflake that identifies the everyone role for application command permissions.
* It is the same as the guild id
* @typedef {Snowflake} RolePermissionConstant
*/
/**
* A static snowflake that identifies the "all channels" entity for application command permissions.
* It will be the result of the calculation `guildId - 1`
* @typedef {Snowflake} ChannelPermissionConstant
*/
/**
* Options used to remove permissions from a command
* <warn>Omitting the `command` parameter removes from the guild wide permissions
* when the managers `commandId` is `null`</warn>
* <warn>At least one of `users`, `roles`, and `channels` is required</warn>
* @typedef {BaseApplicationCommandPermissionsOptions} RemoveApplicationCommandPermissionsOptions
* @property {string} token The bearer token to use that authorizes the permission removal
* @property {UserResolvable[]} [users] The user(s) to remove
* @property {Array<RoleResolvable|RolePermissionConstant>} [roles] The role(s) to remove
* @property {Array<GuildChannelResolvable|ChannelPermissionConstant>} [channels] The channel(s) to remove
*/
/**
* Remove permissions from a command.
* @param {RemoveApplicationCommandPermissionsOptions} options Options used to remove permissions
* @returns {Promise<ApplicationCommandPermissions[]>}
* @example
* // Remove a user permission from this command
* guild.commands.permissions.remove({
* command: '123456789012345678', users: '876543210123456789', token: 'TotallyRealToken',
* })
* .then(console.log)
* .catch(console.error);
* @example
* // Remove multiple roles from this command
* guild.commands.permissions.remove({
* command: '123456789012345678', roles: ['876543210123456789', '765432101234567890'], token: 'TotallyRealToken',
* })
* .then(console.log)
* .catch(console.error);
*/
async remove({ guild, command, users, roles, channels, token } = {}) {
if (!token) {
throw new DiscordjsError(ErrorCodes.ApplicationCommandPermissionsTokenMissing);
}
let { guildId, commandId } = this._validateOptions(guild, command);
if (!commandId) {
commandId = this.client.user.id;
}
if (!users && !roles && !channels) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'users OR roles OR channels', 'Array or Resolvable', true);
}
let resolvedUserIds = [];
if (Array.isArray(users)) {
for (const user of users) {
const userId = this.client.users.resolveId(user);
if (!userId) throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'users', user);
resolvedUserIds.push(userId);
}
}
let resolvedRoleIds = [];
if (Array.isArray(roles)) {
for (const role of roles) {
if (typeof role === 'string') {
resolvedRoleIds.push(role);
continue;
}
if (!this.guild) throw new DiscordjsError(ErrorCodes.GuildUncachedEntityResolve, 'roles');
const roleId = this.guild.roles.resolveId(role);
if (!roleId) throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'users', role);
resolvedRoleIds.push(roleId);
}
}
let resolvedChannelIds = [];
if (Array.isArray(channels)) {
for (const channel of channels) {
if (typeof channel === 'string') {
resolvedChannelIds.push(channel);
continue;
}
if (!this.guild) throw new DiscordjsError(ErrorCodes.GuildUncachedEntityResolve, 'channels');
const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'channels', channel);
resolvedChannelIds.push(channelId);
}
}
let existing = [];
try {
existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) {
if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error;
}
const permissions = existing.filter(perm => {
switch (perm.type) {
case ApplicationCommandPermissionType.Role:
return !resolvedRoleIds.includes(perm.id);
case ApplicationCommandPermissionType.User:
return !resolvedUserIds.includes(perm.id);
case ApplicationCommandPermissionType.Channel:
return !resolvedChannelIds.includes(perm.id);
}
return true;
});
return this.set({ guild: guildId, command: commandId, permissions, token });
}
/**
* Options used to check the existence of permissions on a command
* <warn>The `command` parameter is not optional when the managers `commandId` is `null`</warn>
* @typedef {BaseApplicationCommandPermissionsOptions} HasApplicationCommandPermissionsOptions
* @property {ApplicationCommandPermissionIdResolvable} permissionId The entity to check if a permission exists for
* on this command.
* @property {ApplicationCommandPermissionType} [permissionType] Check for a specific type of permission
*/
/**
* Check whether a permission exists for a user, role, or channel
* @param {HasApplicationCommandPermissionsOptions} options Options used to check permissions
* @returns {Promise<boolean>}
* @example
* guild.commands.permissions.has({ command: '123456789012345678', permissionId: '876543210123456789' })
* .then(console.log)
* .catch(console.error);
*/
async has({ guild, command, permissionId, permissionType }) {
const { guildId, commandId } = this._validateOptions(guild, command);
if (!commandId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable');
if (!permissionId) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'permissionId',
'UserResolvable, RoleResolvable, ChannelResolvable, or Permission Constant',
);
}
let resolvedId = permissionId;
if (typeof permissionId !== 'string') {
resolvedId = this.client.users.resolveId(permissionId);
if (!resolvedId) {
if (!this.guild) throw new DiscordjsError(ErrorCodes.GuildUncachedEntityResolve, 'roles');
resolvedId = this.guild.roles.resolveId(permissionId);
}
if (!resolvedId) {
resolvedId = this.guild.channels.resolveId(permissionId);
}
if (!resolvedId) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'permissionId',
'UserResolvable, RoleResolvable, ChannelResolvable, or Permission Constant',
);
}
}
let existing = [];
try {
existing = await this.fetch({ guild: guildId, command: commandId });
} catch (error) {
if (error.code !== RESTJSONErrorCodes.UnknownApplicationCommandPermissions) throw error;
}
// Check permission type if provided for the single edge case where a channel id is the same as the everyone role id
return existing.some(perm => perm.id === resolvedId && (permissionType ?? perm.type) === perm.type);
}
_validateOptions(guild, command) {
const guildId = this.guildId ?? this.client.guilds.resolveId(guild);
if (!guildId) throw new DiscordjsError(ErrorCodes.GlobalCommandPermissions);
let commandId = this.commandId;
if (command && !commandId) {
commandId = this.manager.resolveId?.(command);
if (!commandId && this.guild) {
commandId = this.guild.commands.resolveId(command);
}
commandId ??= this.client.application?.commands.resolveId(command);
if (!commandId) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'command', 'ApplicationCommandResolvable', true);
}
}
return { guildId, commandId };
}
}
module.exports = ApplicationCommandPermissionsManager;
/* eslint-disable max-len */
/**
* Data that resolves to an id used for an application command permission
* @typedef {UserResolvable|RoleResolvable|GuildChannelResolvable|RolePermissionConstant|ChannelPermissionConstant} ApplicationCommandPermissionIdResolvable
*/

View File

@@ -0,0 +1,142 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const ApplicationEmoji = require('../structures/ApplicationEmoji');
const { resolveImage } = require('../util/DataResolver');
/**
* Manages API methods for ApplicationEmojis and stores their cache.
* @extends {CachedManager}
*/
class ApplicationEmojiManager extends CachedManager {
constructor(application, iterable) {
super(application.client, ApplicationEmoji, iterable);
/**
* The application this manager belongs to
* @type {ClientApplication}
*/
this.application = application;
}
_add(data, cache) {
return super._add(data, cache, { extras: [this.application] });
}
/**
* Options used for creating an emoji of the application
* @typedef {Object} ApplicationEmojiCreateOptions
* @property {BufferResolvable|Base64Resolvable} attachment The image for the emoji
* @property {string} name The name for the emoji
*/
/**
* Creates a new custom emoji of the application.
* @param {ApplicationEmojiCreateOptions} options Options for creating the emoji
* @returns {Promise<Emoji>} The created emoji
* @example
* // Create a new emoji from a URL
* application.emojis.create({ attachment: 'https://i.imgur.com/w3duR07.png', name: 'rip' })
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
* .catch(console.error);
* @example
* // Create a new emoji from a file on your computer
* application.emojis.create({ attachment: './memes/banana.png', name: 'banana' })
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
* .catch(console.error);
*/
async create({ attachment, name }) {
attachment = await resolveImage(attachment);
if (!attachment) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType);
const body = { image: attachment, name };
const emoji = await this.client.rest.post(Routes.applicationEmojis(this.application.id), { body });
return this._add(emoji);
}
/**
* Obtains one or more emojis from Discord, or the emoji cache if they're already available.
* @param {Snowflake} [id] The emoji's id
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<ApplicationEmoji|Collection<Snowflake, ApplicationEmoji>>}
* @example
* // Fetch all emojis from the application
* application.emojis.fetch()
* .then(emojis => console.log(`There are ${emojis.size} emojis.`))
* .catch(console.error);
* @example
* // Fetch a single emoji
* application.emojis.fetch('222078108977594368')
* .then(emoji => console.log(`The emoji name is: ${emoji.name}`))
* .catch(console.error);
*/
async fetch(id, { cache = true, force = false } = {}) {
if (id) {
if (!force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
const emoji = await this.client.rest.get(Routes.applicationEmoji(this.application.id, id));
return this._add(emoji, cache);
}
const { items: data } = await this.client.rest.get(Routes.applicationEmojis(this.application.id));
const emojis = new Collection();
for (const emoji of data) emojis.set(emoji.id, this._add(emoji, cache));
return emojis;
}
/**
* Deletes an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to delete
* @returns {Promise<void>}
*/
async delete(emoji) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
await this.client.rest.delete(Routes.applicationEmoji(this.application.id, id));
}
/**
* Edits an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to edit
* @param {ApplicationEmojiEditOptions} options The options to provide
* @returns {Promise<ApplicationEmoji>}
*/
async edit(emoji, options) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
const newData = await this.client.rest.patch(Routes.applicationEmoji(this.application.id, id), {
body: {
name: options.name,
},
});
const existing = this.cache.get(id);
if (existing) {
existing._patch(newData);
return existing;
}
return this._add(newData);
}
/**
* Fetches the author for this emoji
* @param {EmojiResolvable} emoji The emoji to fetch the author of
* @returns {Promise<User>}
*/
async fetchAuthor(emoji) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
const data = await this.client.rest.get(Routes.applicationEmoji(this.application.id, id));
return this._add(data).author;
}
}
module.exports = ApplicationEmojiManager;

View File

@@ -0,0 +1,292 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const AutoModerationRule = require('../structures/AutoModerationRule');
/**
* Manages API methods for auto moderation rules and stores their cache.
* @extends {CachedManager}
*/
class AutoModerationRuleManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, AutoModerationRule, iterable);
/**
* The guild this manager belongs to.
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of this manager
* @type {Collection<Snowflake, AutoModerationRule>}
* @name AutoModerationRuleManager#cache
*/
/**
* Resolves an {@link AutoModerationRuleResolvable} to an {@link AutoModerationRule} object.
* @method resolve
* @memberof AutoModerationRuleManager
* @instance
* @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve
* @returns {?AutoModerationRule}
*/
/**
* Resolves an {@link AutoModerationRuleResolvable} to a {@link AutoModerationRule} id.
* @method resolveId
* @memberof AutoModerationRuleManager
* @instance
* @param {AutoModerationRuleResolvable} autoModerationRule The AutoModerationRule resolvable to resolve
* @returns {?Snowflake}
*/
_add(data, cache) {
return super._add(data, cache, { extras: [this.guild] });
}
/**
* Options used to set the trigger metadata of an auto moderation rule.
* @typedef {Object} AutoModerationTriggerMetadataOptions
* @property {string[]} [keywordFilter] The substrings that will be searched for in the content
* @property {string[]} [regexPatterns] The regular expression patterns which will be matched against the content
* <info>Only Rust-flavored regular expressions are supported.</info>
* @property {AutoModerationRuleKeywordPresetType[]} [presets]
* The internally pre-defined wordsets which will be searched for in the content
* @property {string[]} [allowList] The substrings that will be exempt from triggering
* {@link AutoModerationRuleTriggerType.Keyword},
* {@link AutoModerationRuleTriggerType.KeywordPreset},
* and {@link AutoModerationRuleTriggerType.MemberProfile}
* @property {?number} [mentionTotalLimit] The total number of role & user mentions allowed per message
* @property {boolean} [mentionRaidProtectionEnabled] Whether to automatically detect mention raids
*/
/**
* Options used to set the actions of an auto moderation rule.
* @typedef {Object} AutoModerationActionOptions
* @property {AutoModerationActionType} type The type of this auto moderation rule action
* @property {AutoModerationActionMetadataOptions} [metadata] Additional metadata needed during execution
* <info>This property is required if using a `type` of
* {@link AutoModerationActionType.SendAlertMessage} or {@link AutoModerationActionType.Timeout}.</info>
*/
/**
* Options used to set the metadata of an auto moderation rule action.
* @typedef {Object} AutoModerationActionMetadataOptions
* @property {GuildTextChannelResolvable|ThreadChannel} [channel] The channel to which content will be logged
* @property {number} [durationSeconds] The timeout duration in seconds
* @property {string} [customMessage] The custom message that is shown whenever a message is blocked
*/
/**
* Options used to create an auto moderation rule.
* @typedef {Object} AutoModerationRuleCreateOptions
* @property {string} name The name of the auto moderation rule
* @property {AutoModerationRuleEventType} eventType The event type of the auto moderation rule
* @property {AutoModerationRuleTriggerType} triggerType The trigger type of the auto moderation rule
* @property {AutoModerationTriggerMetadataOptions} [triggerMetadata] The trigger metadata of the auto moderation rule
* <info>This property is required if using a `triggerType` of
* {@link AutoModerationRuleTriggerType.Keyword},
* {@link AutoModerationRuleTriggerType.KeywordPreset},
* {@link AutoModerationRuleTriggerType.MentionSpam},
* or {@link AutoModerationRuleTriggerType.MemberProfile}.</info>
* @property {AutoModerationActionOptions[]} actions
* The actions that will execute when the auto moderation rule is triggered
* @property {boolean} [enabled] Whether the auto moderation rule should be enabled
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [exemptRoles]
* The roles that should not be affected by the auto moderation rule
* @property {Collection<Snowflake, GuildChannel|ThreadChannel>|GuildChannelResolvable[]} [exemptChannels]
* The channels that should not be affected by the auto moderation rule
* @property {string} [reason] The reason for creating the auto moderation rule
*/
/**
* Creates a new auto moderation rule.
* @param {AutoModerationRuleCreateOptions} options Options for creating the auto moderation rule
* @returns {Promise<AutoModerationRule>}
*/
async create({
name,
eventType,
triggerType,
triggerMetadata,
actions,
enabled,
exemptRoles,
exemptChannels,
reason,
}) {
const data = await this.client.rest.post(Routes.guildAutoModerationRules(this.guild.id), {
body: {
name,
event_type: eventType,
trigger_type: triggerType,
trigger_metadata: triggerMetadata && {
keyword_filter: triggerMetadata.keywordFilter,
regex_patterns: triggerMetadata.regexPatterns,
presets: triggerMetadata.presets,
allow_list: triggerMetadata.allowList,
mention_total_limit: triggerMetadata.mentionTotalLimit,
mention_raid_protection_enabled: triggerMetadata.mentionRaidProtectionEnabled,
},
actions: actions.map(action => ({
type: action.type,
metadata: {
duration_seconds: action.metadata?.durationSeconds,
channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel),
custom_message: action.metadata?.customMessage,
},
})),
enabled,
exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)),
exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)),
},
reason,
});
return this._add(data);
}
/**
* Options used to edit an auto moderation rule.
* @typedef {Object} AutoModerationRuleEditOptions
* @property {string} [name] The name of the auto moderation rule
* @property {AutoModerationRuleEventType} [eventType] The event type of the auto moderation rule
* @property {AutoModerationTriggerMetadataOptions} [triggerMetadata] The trigger metadata of the auto moderation rule
* @property {AutoModerationActionOptions[]} [actions]
* The actions that will execute when the auto moderation rule is triggered
* @property {boolean} [enabled] Whether the auto moderation rule should be enabled
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [exemptRoles]
* The roles that should not be affected by the auto moderation rule
* @property {Collection<Snowflake, GuildChannel|ThreadChannel>|GuildChannelResolvable[]} [exemptChannels]
* The channels that should not be affected by the auto moderation rule
* @property {string} [reason] The reason for creating the auto moderation rule
*/
/**
* Edits an auto moderation rule.
* @param {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to edit
* @param {AutoModerationRuleEditOptions} options Options for editing the auto moderation rule
* @returns {Promise<AutoModerationRule>}
*/
async edit(
autoModerationRule,
{ name, eventType, triggerMetadata, actions, enabled, exemptRoles, exemptChannels, reason },
) {
const autoModerationRuleId = this.resolveId(autoModerationRule);
const data = await this.client.rest.patch(Routes.guildAutoModerationRule(this.guild.id, autoModerationRuleId), {
body: {
name,
event_type: eventType,
trigger_metadata: triggerMetadata && {
keyword_filter: triggerMetadata.keywordFilter,
regex_patterns: triggerMetadata.regexPatterns,
presets: triggerMetadata.presets,
allow_list: triggerMetadata.allowList,
mention_total_limit: triggerMetadata.mentionTotalLimit,
mention_raid_protection_enabled: triggerMetadata.mentionRaidProtectionEnabled,
},
actions: actions?.map(action => ({
type: action.type,
metadata: {
duration_seconds: action.metadata?.durationSeconds,
channel_id: action.metadata?.channel && this.guild.channels.resolveId(action.metadata.channel),
custom_message: action.metadata?.customMessage,
},
})),
enabled,
exempt_roles: exemptRoles?.map(exemptRole => this.guild.roles.resolveId(exemptRole)),
exempt_channels: exemptChannels?.map(exemptChannel => this.guild.channels.resolveId(exemptChannel)),
},
reason,
});
return this._add(data);
}
/**
* Data that can be resolved to give an AutoModerationRule object. This can be:
* * An AutoModerationRule
* * A Snowflake
* @typedef {AutoModerationRule|Snowflake} AutoModerationRuleResolvable
*/
/**
* Options used to fetch a single auto moderation rule from a guild.
* @typedef {BaseFetchOptions} FetchAutoModerationRuleOptions
* @property {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to fetch
*/
/**
* Options used to fetch all auto moderation rules from a guild.
* @typedef {Object} FetchAutoModerationRulesOptions
* @property {boolean} [cache] Whether to cache the fetched auto moderation rules
*/
/**
* Fetches auto moderation rules from Discord.
* @param {AutoModerationRuleResolvable|FetchAutoModerationRuleOptions|FetchAutoModerationRulesOptions} [options]
* Options for fetching auto moderation rule(s)
* @returns {Promise<AutoModerationRule|Collection<Snowflake, AutoModerationRule>>}
* @example
* // Fetch all auto moderation rules from a guild without caching
* guild.autoModerationRules.fetch({ cache: false })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single auto moderation rule
* guild.autoModerationRules.fetch('979083472868098119')
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single auto moderation rule without checking cache and without caching
* guild.autoModerationRules.fetch({ autoModerationRule: '979083472868098119', cache: false, force: true })
* .then(console.log)
* .catch(console.error)
*/
fetch(options) {
if (!options) return this._fetchMany();
const { autoModerationRule, cache, force } = options;
const resolvedAutoModerationRule = this.resolveId(autoModerationRule ?? options);
if (resolvedAutoModerationRule) {
return this._fetchSingle({ autoModerationRule: resolvedAutoModerationRule, cache, force });
}
return this._fetchMany(options);
}
async _fetchSingle({ autoModerationRule, cache, force = false }) {
if (!force) {
const existing = this.cache.get(autoModerationRule);
if (existing) return existing;
}
const data = await this.client.rest.get(Routes.guildAutoModerationRule(this.guild.id, autoModerationRule));
return this._add(data, cache);
}
async _fetchMany(options = {}) {
const data = await this.client.rest.get(Routes.guildAutoModerationRules(this.guild.id));
return data.reduce(
(col, autoModerationRule) => col.set(autoModerationRule.id, this._add(autoModerationRule, options.cache)),
new Collection(),
);
}
/**
* Deletes an auto moderation rule.
* @param {AutoModerationRuleResolvable} autoModerationRule The auto moderation rule to delete
* @param {string} [reason] The reason for deleting the auto moderation rule
* @returns {Promise<void>}
*/
async delete(autoModerationRule, reason) {
const autoModerationRuleId = this.resolveId(autoModerationRule);
await this.client.rest.delete(Routes.guildAutoModerationRule(this.guild.id, autoModerationRuleId), { reason });
}
}
module.exports = AutoModerationRuleManager;

View File

@@ -0,0 +1,80 @@
'use strict';
const CachedManager = require('./CachedManager');
const GuildEmoji = require('../structures/GuildEmoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
const { parseEmoji } = require('../util/Util');
/**
* Holds methods to resolve GuildEmojis and stores their cache.
* @extends {CachedManager}
*/
class BaseGuildEmojiManager extends CachedManager {
constructor(client, iterable) {
super(client, GuildEmoji, iterable);
}
/**
* The cache of GuildEmojis
* @type {Collection<Snowflake, GuildEmoji>}
* @name BaseGuildEmojiManager#cache
*/
/**
* Data that can be resolved into a GuildEmoji object. This can be:
* * A Snowflake
* * A GuildEmoji object
* * A ReactionEmoji object
* @typedef {Snowflake|GuildEmoji|ReactionEmoji} EmojiResolvable
*/
/**
* Resolves an EmojiResolvable to an Emoji object.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?GuildEmoji}
*/
resolve(emoji) {
if (emoji instanceof ReactionEmoji) return super.resolve(emoji.id);
return super.resolve(emoji);
}
/**
* Resolves an EmojiResolvable to an Emoji id string.
* @param {EmojiResolvable} emoji The Emoji resolvable to identify
* @returns {?Snowflake}
*/
resolveId(emoji) {
if (emoji instanceof ReactionEmoji) return emoji.id;
return super.resolveId(emoji);
}
/**
* Data that can be resolved to give an emoji identifier. This can be:
* * An EmojiResolvable
* * The `<a:name:id>`, `<:name:id>`, `a:name:id` or `name:id` emoji identifier string of an emoji
* * The Unicode representation of an emoji
* @typedef {string|EmojiResolvable} EmojiIdentifierResolvable
*/
/**
* Resolves an EmojiResolvable to an emoji identifier.
* @param {EmojiIdentifierResolvable} emoji The emoji resolvable to resolve
* @returns {?string}
*/
resolveIdentifier(emoji) {
const emojiResolvable = this.resolve(emoji);
if (emojiResolvable) return emojiResolvable.identifier;
if (emoji instanceof ReactionEmoji) return emoji.identifier;
if (typeof emoji === 'string') {
const res = parseEmoji(emoji);
if (res?.name.length) {
emoji = `${res.animated ? 'a:' : ''}${res.name}${res.id ? `:${res.id}` : ''}`;
}
if (!emoji.includes('%')) return encodeURIComponent(emoji);
return emoji;
}
return null;
}
}
module.exports = BaseGuildEmojiManager;

19
node_modules/discord.js/src/managers/BaseManager.js generated vendored Normal file
View File

@@ -0,0 +1,19 @@
'use strict';
/**
* Manages the API methods of a data model.
* @abstract
*/
class BaseManager {
constructor(client) {
/**
* The client that instantiated this Manager
* @name BaseManager#client
* @type {Client}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });
}
}
module.exports = BaseManager;

64
node_modules/discord.js/src/managers/CachedManager.js generated vendored Normal file
View File

@@ -0,0 +1,64 @@
'use strict';
const DataManager = require('./DataManager');
const { MakeCacheOverrideSymbol } = require('../util/Symbols');
/**
* Manages the API methods of a data model with a mutable cache of instances.
* @extends {DataManager}
* @abstract
*/
class CachedManager extends DataManager {
constructor(client, holds, iterable) {
super(client, holds);
/**
* The private cache of items for this manager.
* @type {Collection}
* @private
* @readonly
* @name CachedManager#_cache
*/
Object.defineProperty(this, '_cache', {
value: this.client.options.makeCache(
this.constructor[MakeCacheOverrideSymbol] ?? this.constructor,
this.holds,
this.constructor,
),
});
if (iterable) {
for (const item of iterable) {
this._add(item);
}
}
}
/**
* The cache of items for this manager.
* @type {Collection}
* @abstract
*/
get cache() {
return this._cache;
}
_add(data, cache = true, { id, extras = [] } = {}) {
const existing = this.cache.get(id ?? data.id);
if (existing) {
if (cache) {
existing._patch(data);
return existing;
}
const clone = existing._clone();
clone._patch(data);
return clone;
}
const entry = this.holds ? new this.holds(this.client, data, ...extras) : data;
if (cache) this.cache.set(id ?? entry.id, entry);
return entry;
}
}
module.exports = CachedManager;

View File

@@ -0,0 +1,79 @@
'use strict';
const DataManager = require('./DataManager');
const GuildChannel = require('../structures/GuildChannel');
/**
* Manages API methods for CategoryChannels' children.
* @extends {DataManager}
*/
class CategoryChannelChildManager extends DataManager {
constructor(channel) {
super(channel.client, GuildChannel);
/**
* The category channel this manager belongs to
* @type {CategoryChannel}
*/
this.channel = channel;
}
/**
* The channels that are a part of this category
* @type {Collection<Snowflake, GuildChannel>}
* @readonly
*/
get cache() {
return this.guild.channels.cache.filter(channel => channel.parentId === this.channel.id);
}
/**
* The guild this manager belongs to
* @type {Guild}
* @readonly
*/
get guild() {
return this.channel.guild;
}
/**
* Options for creating a channel using {@link CategoryChannelChildManager#create}.
* @typedef {Object} CategoryCreateChannelOptions
* @property {string} name The name for the new channel
* @property {ChannelType} [type=ChannelType.GuildText] The type of the new channel.
* @property {string} [topic] The topic for the new channel
* @property {boolean} [nsfw] Whether the new channel is NSFW
* @property {number} [bitrate] Bitrate of the new channel in bits (only voice)
* @property {number} [userLimit] Maximum amount of users allowed in the new channel (only voice)
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
* Permission overwrites of the new channel
* @property {number} [position] Position of the new channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the new channel in seconds
* @property {string} [rtcRegion] The specific region of the new channel.
* @property {VideoQualityMode} [videoQualityMode] The camera video quality mode of the voice channel
* @property {number} [defaultThreadRateLimitPerUser] The initial rate limit per user (slowmode)
* to set on newly created threads in a channel.
* @property {GuildForumTagData[]} [availableTags] The tags that can be used in this channel (forum only).
* @property {DefaultReactionEmoji} [defaultReactionEmoji]
* The emoji to show in the add reaction button on a thread in a guild forum channel.
* @property {ThreadAutoArchiveDuration} [defaultAutoArchiveDuration]
* The default auto archive duration for all new threads in this channel
* @property {SortOrderType} [defaultSortOrder] The default sort order mode used to order posts (forum only).
* @property {ForumLayoutType} [defaultForumLayout] The default layout used to display posts (forum only).
* @property {string} [reason] Reason for creating the new channel
*/
/**
* Creates a new channel within this category.
* <info>You cannot create a channel of type {@link ChannelType.GuildCategory} inside a CategoryChannel.</info>
* @param {CategoryCreateChannelOptions} options Options for creating the new channel
* @returns {Promise<GuildChannel>}
*/
create(options) {
return this.guild.channels.create({
...options,
parent: this.channel.id,
});
}
}
module.exports = CategoryChannelChildManager;

128
node_modules/discord.js/src/managers/ChannelManager.js generated vendored Normal file
View File

@@ -0,0 +1,128 @@
'use strict';
const process = require('node:process');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { BaseChannel } = require('../structures/BaseChannel');
const { createChannel } = require('../util/Channels');
const { ThreadChannelTypes } = require('../util/Constants');
const Events = require('../util/Events');
let cacheWarningEmitted = false;
/**
* A manager of channels belonging to a client
* @extends {CachedManager}
*/
class ChannelManager extends CachedManager {
constructor(client, iterable) {
super(client, BaseChannel, iterable);
const defaultCaching =
this._cache.constructor.name === 'Collection' ||
this._cache.maxSize === undefined ||
this._cache.maxSize === Infinity;
if (!cacheWarningEmitted && !defaultCaching) {
cacheWarningEmitted = true;
process.emitWarning(
`Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`,
'UnsupportedCacheOverwriteWarning',
);
}
}
/**
* The cache of Channels
* @type {Collection<Snowflake, BaseChannel>}
* @name ChannelManager#cache
*/
_add(data, guild, { cache = true, allowUnknownGuild = false } = {}) {
const existing = this.cache.get(data.id);
if (existing) {
if (cache) existing._patch(data);
guild?.channels?._add(existing);
if (ThreadChannelTypes.includes(existing.type)) {
existing.parent?.threads?._add(existing);
}
return existing;
}
const channel = createChannel(this.client, data, guild, { allowUnknownGuild });
if (!channel) {
this.client.emit(Events.Debug, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`);
return null;
}
if (cache && !allowUnknownGuild) this.cache.set(channel.id, channel);
return channel;
}
_remove(id) {
const channel = this.cache.get(id);
channel?.guild?.channels.cache.delete(id);
for (const [code, invite] of channel?.guild?.invites.cache ?? []) {
if (invite.channelId === id) channel.guild.invites.cache.delete(code);
}
channel?.parent?.threads?.cache.delete(id);
this.cache.delete(id);
}
/**
* Data that can be resolved to give a Channel object. This can be:
* * A Channel object
* * A Snowflake
* @typedef {BaseChannel|Snowflake} ChannelResolvable
*/
/**
* Resolves a ChannelResolvable to a Channel object.
* @method resolve
* @memberof ChannelManager
* @instance
* @param {ChannelResolvable} channel The channel resolvable to resolve
* @returns {?BaseChannel}
*/
/**
* Resolves a ChannelResolvable to a channel id string.
* @method resolveId
* @memberof ChannelManager
* @instance
* @param {ChannelResolvable} channel The channel resolvable to resolve
* @returns {?Snowflake}
*/
/**
* Options for fetching a channel from Discord
* @typedef {BaseFetchOptions} FetchChannelOptions
* @property {boolean} [allowUnknownGuild=false] Allows the channel to be returned even if the guild is not in cache,
* it will not be cached. <warn>Many of the properties and methods on the returned channel will throw errors</warn>
*/
/**
* Obtains a channel from Discord, or the channel cache if it's already available.
* @param {Snowflake} id The channel's id
* @param {FetchChannelOptions} [options] Additional options for this fetch
* @returns {Promise<?BaseChannel>}
* @example
* // Fetch a channel by its id
* client.channels.fetch('222109930545610754')
* .then(channel => console.log(channel.name))
* .catch(console.error);
*/
async fetch(id, { allowUnknownGuild = false, cache = true, force = false } = {}) {
if (!force) {
const existing = this.cache.get(id);
if (existing && !existing.partial) return existing;
}
const data = await this.client.rest.get(Routes.channel(id));
return this._add(data, null, { cache, allowUnknownGuild });
}
}
module.exports = ChannelManager;

View File

@@ -0,0 +1,17 @@
'use strict';
const MessageManager = require('./MessageManager');
/**
* Manages API methods for messages in direct message channels and holds their cache.
* @extends {MessageManager}
*/
class DMMessageManager extends MessageManager {
/**
* The channel that the messages belong to
* @name DMMessageManager#channel
* @type {DMChannel}
*/
}
module.exports = DMMessageManager;

61
node_modules/discord.js/src/managers/DataManager.js generated vendored Normal file
View File

@@ -0,0 +1,61 @@
'use strict';
const BaseManager = require('./BaseManager');
const { DiscordjsError, ErrorCodes } = require('../errors');
/**
* Manages the API methods of a data model along with a collection of instances.
* @extends {BaseManager}
* @abstract
*/
class DataManager extends BaseManager {
constructor(client, holds) {
super(client);
/**
* The data structure belonging to this manager.
* @name DataManager#holds
* @type {Function}
* @private
* @readonly
*/
Object.defineProperty(this, 'holds', { value: holds });
}
/**
* The cache of items for this manager.
* @type {Collection}
* @abstract
*/
get cache() {
throw new DiscordjsError(ErrorCodes.NotImplemented, 'get cache', this.constructor.name);
}
/**
* Resolves a data entry to a data Object.
* @param {string|Object} idOrInstance The id or instance of something in this Manager
* @returns {?Object} An instance from this Manager
*/
resolve(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance;
if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) ?? null;
return null;
}
/**
* Resolves a data entry to an instance id.
* @param {string|Object} idOrInstance The id or instance of something in this Manager
* @returns {?Snowflake}
*/
resolveId(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance.id;
if (typeof idOrInstance === 'string') return idOrInstance;
return null;
}
valueOf() {
return this.cache;
}
}
module.exports = DataManager;

View File

@@ -0,0 +1,139 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes, EntitlementOwnerType } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { ErrorCodes, DiscordjsTypeError } = require('../errors/index');
const { Entitlement } = require('../structures/Entitlement');
const { resolveSKUId } = require('../util/Util');
/**
* Manages API methods for entitlements and stores their cache.
* @extends {CachedManager}
*/
class EntitlementManager extends CachedManager {
constructor(client, iterable) {
super(client, Entitlement, iterable);
}
/**
* The cache of this manager
* @type {Collection<Snowflake, Entitlement>}
* @name EntitlementManager#cache
*/
/**
* Data that resolves to give an Entitlement object. This can be:
* * An Entitlement object
* * A Snowflake
* @typedef {Entitlement|Snowflake} EntitlementResolvable
*/
/**
* Data that resolves to give a SKU object. This can be:
* * A SKU object
* * A Snowflake
* @typedef {SKU|Snowflake} SKUResolvable
*/
/**
* Options used to fetch entitlements
* @typedef {Object} FetchEntitlementsOptions
* @property {number} [limit] The maximum number of entitlements to fetch
* @property {GuildResolvable} [guild] The guild to fetch entitlements for
* @property {UserResolvable} [user] The user to fetch entitlements for
* @property {SKUResolvable[]} [skus] The SKUs to fetch entitlements for
* @property {boolean} [excludeEnded] Whether to exclude ended entitlements
* @property {boolean} [cache=true] Whether to cache the fetched entitlements
* @property {Snowflake} [before] Consider only entitlements before this entitlement id
* @property {Snowflake} [after] Consider only entitlements after this entitlement id
* <warn>If both `before` and `after` are provided, only `before` is respected</warn>
*/
/**
* Fetches entitlements for this application
* @param {FetchEntitlementsOptions} [options={}] Options for fetching the entitlements
* @returns {Promise<Collection<Snowflake, Entitlement>>}
*/
async fetch({ limit, guild, user, skus, excludeEnded, cache = true, before, after } = {}) {
const query = makeURLSearchParams({
limit,
guild_id: guild && this.client.guilds.resolveId(guild),
user_id: user && this.client.users.resolveId(user),
sku_ids: skus?.map(sku => resolveSKUId(sku)).join(','),
exclude_ended: excludeEnded,
before,
after,
});
const entitlements = await this.client.rest.get(Routes.entitlements(this.client.application.id), { query });
return entitlements.reduce(
(coll, entitlement) => coll.set(entitlement.id, this._add(entitlement, cache)),
new Collection(),
);
}
/**
* Options used to create a test entitlement
* <info>Either `guild` or `user` must be provided, but not both</info>
* @typedef {Object} EntitlementCreateOptions
* @property {SKUResolvable} sku The id of the SKU to create the entitlement for
* @property {GuildResolvable} [guild] The guild to create the entitlement for
* @property {UserResolvable} [user] The user to create the entitlement for
*/
/**
* Creates a test entitlement
* @param {EntitlementCreateOptions} options Options for creating the test entitlement
* @returns {Promise<Entitlement>}
*/
async createTest({ sku, guild, user }) {
const skuId = resolveSKUId(sku);
if (!skuId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sku', 'SKUResolvable');
if ((guild && user) || (!guild && !user)) {
throw new DiscordjsTypeError(ErrorCodes.EntitlementCreateInvalidOwner);
}
const resolved = guild ? this.client.guilds.resolveId(guild) : this.client.users.resolveId(user);
if (!resolved) {
const name = guild ? 'guild' : 'user';
const type = guild ? 'GuildResolvable' : 'UserResolvable';
throw new DiscordjsTypeError(ErrorCodes.InvalidType, name, type);
}
const entitlement = await this.client.rest.post(Routes.entitlements(this.client.application.id), {
body: {
sku_id: skuId,
owner_id: resolved,
owner_type: guild ? EntitlementOwnerType.Guild : EntitlementOwnerType.User,
},
});
return new Entitlement(this.client, entitlement);
}
/**
* Deletes a test entitlement
* @param {EntitlementResolvable} entitlement The entitlement to delete
* @returns {Promise<void>}
*/
async deleteTest(entitlement) {
const resolved = this.resolveId(entitlement);
if (!resolved) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'entitlement', 'EntitlementResolvable');
await this.client.rest.delete(Routes.entitlement(this.client.application.id, resolved));
}
/**
* Marks an entitlement as consumed
* <info>Only available for One-Time Purchase consumable SKUs.</info>
* @param {Snowflake} entitlementId The id of the entitlement to consume
* @returns {Promise<void>}
*/
async consume(entitlementId) {
await this.client.rest.post(Routes.consumeEntitlement(this.client.application.id, entitlementId));
}
}
exports.EntitlementManager = EntitlementManager;

View File

@@ -0,0 +1,28 @@
'use strict';
const ApplicationCommandManager = require('./ApplicationCommandManager');
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
/**
* An extension for guild-specific application commands.
* @extends {ApplicationCommandManager}
*/
class GuildApplicationCommandManager extends ApplicationCommandManager {
constructor(guild, iterable) {
super(guild.client, iterable);
/**
* The guild that this manager belongs to
* @type {Guild}
*/
this.guild = guild;
/**
* The manager for permissions of arbitrary commands on this guild
* @type {ApplicationCommandPermissionsManager}
*/
this.permissions = new ApplicationCommandPermissionsManager(this);
}
}
module.exports = GuildApplicationCommandManager;

249
node_modules/discord.js/src/managers/GuildBanManager.js generated vendored Normal file
View File

@@ -0,0 +1,249 @@
'use strict';
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors');
const GuildBan = require('../structures/GuildBan');
const { GuildMember } = require('../structures/GuildMember');
let deprecationEmittedForDeleteMessageDays = false;
/**
* Manages API methods for guild bans and stores their cache.
* @extends {CachedManager}
*/
class GuildBanManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, GuildBan, iterable);
/**
* The guild this Manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of this Manager
* @type {Collection<Snowflake, GuildBan>}
* @name GuildBanManager#cache
*/
_add(data, cache) {
return super._add(data, cache, { id: data.user.id, extras: [this.guild] });
}
/**
* Data that resolves to give a GuildBan object. This can be:
* * A GuildBan object
* * A User resolvable
* @typedef {GuildBan|UserResolvable} GuildBanResolvable
*/
/**
* Resolves a GuildBanResolvable to a GuildBan object.
* @param {GuildBanResolvable} ban The ban that is in the guild
* @returns {?GuildBan}
*/
resolve(ban) {
return super.resolve(ban) ?? super.resolve(this.client.users.resolveId(ban));
}
/**
* Options used to fetch a single ban from a guild.
* @typedef {BaseFetchOptions} FetchBanOptions
* @property {UserResolvable} user The ban to fetch
*/
/**
* Options used to fetch multiple bans from a guild.
* @typedef {Object} FetchBansOptions
* @property {number} [limit] The maximum number of bans to return
* @property {Snowflake} [before] Consider only bans before this id
* @property {Snowflake} [after] Consider only bans after this id
* @property {boolean} [cache] Whether to cache the fetched bans
*/
/**
* Fetches ban(s) from Discord.
* @param {UserResolvable|FetchBanOptions|FetchBansOptions} [options] Options for fetching guild ban(s)
* @returns {Promise<GuildBan|Collection<Snowflake, GuildBan>>}
* @example
* // Fetch multiple bans from a guild
* guild.bans.fetch()
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a maximum of 5 bans from a guild without caching
* guild.bans.fetch({ limit: 5, cache: false })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single ban
* guild.bans.fetch('351871113346809860')
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single ban without checking cache
* guild.bans.fetch({ user, force: true })
* .then(console.log)
* .catch(console.error)
* @example
* // Fetch a single ban without caching
* guild.bans.fetch({ user, cache: false })
* .then(console.log)
* .catch(console.error);
*/
fetch(options) {
if (!options) return this._fetchMany();
const { user, cache, force, limit, before, after } = options;
const resolvedUser = this.client.users.resolveId(user ?? options);
if (resolvedUser) return this._fetchSingle({ user: resolvedUser, cache, force });
if (!before && !after && !limit && cache === undefined) {
return Promise.reject(new DiscordjsError(ErrorCodes.FetchBanResolveId));
}
return this._fetchMany(options);
}
async _fetchSingle({ user, cache, force = false }) {
if (!force) {
const existing = this.cache.get(user);
if (existing && !existing.partial) return existing;
}
const data = await this.client.rest.get(Routes.guildBan(this.guild.id, user));
return this._add(data, cache);
}
async _fetchMany(options = {}) {
const data = await this.client.rest.get(Routes.guildBans(this.guild.id), {
query: makeURLSearchParams(options),
});
return data.reduce((col, ban) => col.set(ban.user.id, this._add(ban, options.cache)), new Collection());
}
/**
* Options used to ban a user from a guild.
* @typedef {Object} BanOptions
* @property {number} [deleteMessageDays] Number of days of messages to delete, must be between 0 and 7, inclusive
* <warn>This property is deprecated. Use `deleteMessageSeconds` instead.</warn>
* @property {number} [deleteMessageSeconds] Number of seconds of messages to delete,
* must be between 0 and 604800 (7 days), inclusive
* @property {string} [reason] The reason for the ban
*/
/**
* Bans a user from the guild.
* @param {UserResolvable} user The user to ban
* @param {BanOptions} [options] Options for the ban
* @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible.
* If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
* be resolved, the user id will be the result.
* @example
* // Ban a user by id (or with a user/guild member object)
* guild.bans.create('84484653687267328')
* .then(banInfo => console.log(`Banned ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`))
* .catch(console.error);
*/
async create(user, options = {}) {
if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
const id = this.client.users.resolveId(user);
if (!id) throw new DiscordjsError(ErrorCodes.BanResolveId, true);
if (options.deleteMessageDays !== undefined && !deprecationEmittedForDeleteMessageDays) {
process.emitWarning(
// eslint-disable-next-line max-len
'The deleteMessageDays option for GuildBanManager#create() is deprecated. Use the deleteMessageSeconds option instead.',
'DeprecationWarning',
);
deprecationEmittedForDeleteMessageDays = true;
}
await this.client.rest.put(Routes.guildBan(this.guild.id, id), {
body: {
delete_message_seconds:
options.deleteMessageSeconds ??
(options.deleteMessageDays ? options.deleteMessageDays * 24 * 60 * 60 : undefined),
},
reason: options.reason,
});
if (user instanceof GuildMember) return user;
const _user = this.client.users.resolve(id);
if (_user) {
return this.guild.members.resolve(_user) ?? _user;
}
return id;
}
/**
* Unbans a user from the guild.
* @param {UserResolvable} user The user to unban
* @param {string} [reason] Reason for unbanning user
* @returns {Promise<?User>}
* @example
* // Unban a user by id (or with a user/guild member object)
* guild.bans.remove('84484653687267328')
* .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
* .catch(console.error);
*/
async remove(user, reason) {
const id = this.client.users.resolveId(user);
if (!id) throw new DiscordjsError(ErrorCodes.BanResolveId);
await this.client.rest.delete(Routes.guildBan(this.guild.id, id), { reason });
return this.client.users.resolve(user);
}
/**
* Options used for bulk banning users from a guild.
* @typedef {Object} BulkBanOptions
* @property {number} [deleteMessageSeconds] Number of seconds of messages to delete,
* must be between 0 and 604800 (7 days), inclusive
* @property {string} [reason] The reason for the bans
*/
/**
* Result of bulk banning users from a guild.
* @typedef {Object} BulkBanResult
* @property {Snowflake[]} bannedUsers IDs of the banned users
* @property {Snowflake[]} failedUsers IDs of the users that could not be banned or were already banned
*/
/**
* Bulk ban users from a guild, and optionally delete previous messages sent by them.
* @param {Collection<Snowflake, UserResolvable>|UserResolvable[]} users The users to ban
* @param {BulkBanOptions} [options] The options for bulk banning users
* @returns {Promise<BulkBanResult>} Returns an object with `bannedUsers` key containing the IDs of the banned users
* and the key `failedUsers` with the IDs that could not be banned or were already banned.
* @example
* // Bulk ban users by ids (or with user/guild member objects) and delete all their messages from the past 7 days
* guild.bans.bulkCreate(['84484653687267328'], { deleteMessageSeconds: 7 * 24 * 60 * 60 })
* .then(result => {
* console.log(`Banned ${result.bannedUsers.length} users, failed to ban ${result.failedUsers.length} users.`)
* })
* .catch(console.error);
*/
async bulkCreate(users, options = {}) {
if (!users || !(Array.isArray(users) || users instanceof Collection)) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'users', 'Array or Collection of UserResolvable', true);
}
if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
const userIds = users.map(user => this.client.users.resolveId(user));
if (userIds.length === 0) throw new DiscordjsError(ErrorCodes.BulkBanUsersOptionEmpty);
const result = await this.client.rest.post(Routes.guildBulkBan(this.guild.id), {
body: { delete_message_seconds: options.deleteMessageSeconds, user_ids: userIds },
reason: options.reason,
});
return { bannedUsers: result.banned_users, failedUsers: result.failed_users };
}
}
module.exports = GuildBanManager;

View File

@@ -0,0 +1,519 @@
'use strict';
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { ChannelType, Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const GuildTextThreadManager = require('./GuildTextThreadManager');
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
const GuildChannel = require('../structures/GuildChannel');
const PermissionOverwrites = require('../structures/PermissionOverwrites');
const ThreadChannel = require('../structures/ThreadChannel');
const Webhook = require('../structures/Webhook');
const ChannelFlagsBitField = require('../util/ChannelFlagsBitField');
const { transformGuildForumTag, transformGuildDefaultReaction } = require('../util/Channels');
const { ThreadChannelTypes } = require('../util/Constants');
const { resolveImage } = require('../util/DataResolver');
const { setPosition } = require('../util/Util');
let cacheWarningEmitted = false;
/**
* Manages API methods for GuildChannels and stores their cache.
* @extends {CachedManager}
*/
class GuildChannelManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, GuildChannel, iterable);
const defaultCaching =
this._cache.constructor.name === 'Collection' ||
this._cache.maxSize === undefined ||
this._cache.maxSize === Infinity;
if (!cacheWarningEmitted && !defaultCaching) {
cacheWarningEmitted = true;
process.emitWarning(
`Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`,
'UnsupportedCacheOverwriteWarning',
);
}
/**
* The guild this Manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The number of channels in this managers cache excluding thread channels
* that do not count towards a guild's maximum channels restriction.
* @type {number}
* @readonly
*/
get channelCountWithoutThreads() {
return this.cache.reduce((acc, channel) => {
if (ThreadChannelTypes.includes(channel.type)) return acc;
return ++acc;
}, 0);
}
/**
* The cache of this Manager
* @type {Collection<Snowflake, GuildChannel|ThreadChannel>}
* @name GuildChannelManager#cache
*/
_add(channel) {
const existing = this.cache.get(channel.id);
if (existing) return existing;
this.cache.set(channel.id, channel);
return channel;
}
/**
* Data that can be resolved to give a Guild Channel object. This can be:
* * A GuildChannel object
* * A ThreadChannel object
* * A Snowflake
* @typedef {GuildChannel|ThreadChannel|Snowflake} GuildChannelResolvable
*/
/**
* Resolves a GuildChannelResolvable to a Channel object.
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
* @returns {?(GuildChannel|ThreadChannel)}
*/
resolve(channel) {
if (channel instanceof ThreadChannel) return super.resolve(channel.id);
return super.resolve(channel);
}
/**
* Resolves a GuildChannelResolvable to a channel id.
* @param {GuildChannelResolvable} channel The GuildChannel resolvable to resolve
* @returns {?Snowflake}
*/
resolveId(channel) {
if (channel instanceof ThreadChannel) return super.resolveId(channel.id);
return super.resolveId(channel);
}
/**
* Data that can be resolved to a News Channel object. This can be:
* * A NewsChannel object
* * A Snowflake
* @typedef {NewsChannel|Snowflake} NewsChannelResolvable
*/
/**
* Adds the target channel to a channel's followers.
* @param {NewsChannelResolvable} channel The channel to follow
* @param {TextChannelResolvable} targetChannel The channel where published announcements will be posted at
* @param {string} [reason] Reason for creating the webhook
* @returns {Promise<Snowflake>} Returns created target webhook id.
*/
async addFollower(channel, targetChannel, reason) {
const channelId = this.resolveId(channel);
if (!channelId) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'NewsChannelResolvable');
}
const targetChannelId = this.resolveId(targetChannel);
if (!targetChannelId) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'targetChannel', 'TextChannelResolvable');
}
const { webhook_id } = await this.client.rest.post(Routes.channelFollowers(channelId), {
body: { webhook_channel_id: targetChannelId },
reason,
});
return webhook_id;
}
/**
* Options used to create a new channel in a guild.
* @typedef {CategoryCreateChannelOptions} GuildChannelCreateOptions
* @property {?CategoryChannelResolvable} [parent] Parent of the new channel
*/
/**
* Creates a new channel in the guild.
* @param {GuildChannelCreateOptions} options Options for creating the new channel
* @returns {Promise<GuildChannel>}
* @example
* // Create a new text channel
* guild.channels.create({ name: 'new-general', reason: 'Needed a cool new channel' })
* .then(console.log)
* .catch(console.error);
* @example
* // Create a new channel with permission overwrites
* guild.channels.create({
* name: 'new-general',
* type: ChannelType.GuildVoice,
* permissionOverwrites: [
* {
* id: message.author.id,
* deny: [PermissionFlagsBits.ViewChannel],
* },
* ],
* })
*/
async create({
name,
type,
topic,
nsfw,
bitrate,
userLimit,
parent,
permissionOverwrites,
position,
rateLimitPerUser,
rtcRegion,
videoQualityMode,
defaultThreadRateLimitPerUser,
availableTags,
defaultReactionEmoji,
defaultAutoArchiveDuration,
defaultSortOrder,
defaultForumLayout,
reason,
}) {
parent &&= this.client.channels.resolveId(parent);
permissionOverwrites &&= permissionOverwrites.map(overwrite => PermissionOverwrites.resolve(overwrite, this.guild));
const data = await this.client.rest.post(Routes.guildChannels(this.guild.id), {
body: {
name,
topic,
type,
nsfw,
bitrate,
user_limit: userLimit,
parent_id: parent,
position,
permission_overwrites: permissionOverwrites,
rate_limit_per_user: rateLimitPerUser,
rtc_region: rtcRegion,
video_quality_mode: videoQualityMode,
default_thread_rate_limit_per_user: defaultThreadRateLimitPerUser,
available_tags: availableTags?.map(availableTag => transformGuildForumTag(availableTag)),
default_reaction_emoji: defaultReactionEmoji && transformGuildDefaultReaction(defaultReactionEmoji),
default_auto_archive_duration: defaultAutoArchiveDuration,
default_sort_order: defaultSortOrder,
default_forum_layout: defaultForumLayout,
},
reason,
});
return this.client.actions.ChannelCreate.handle(data).channel;
}
/**
* @typedef {ChannelWebhookCreateOptions} WebhookCreateOptions
* @property {TextChannel|NewsChannel|VoiceChannel|StageChannel|ForumChannel|MediaChannel|Snowflake} channel
* The channel to create the webhook for
*/
/**
* Creates a webhook for the channel.
* @param {WebhookCreateOptions} options Options for creating the webhook
* @returns {Promise<Webhook>} Returns the created Webhook
* @example
* // Create a webhook for the current channel
* guild.channels.createWebhook({
* channel: '222197033908436994',
* name: 'Snek',
* avatar: 'https://i.imgur.com/mI8XcpG.jpg',
* reason: 'Needed a cool new Webhook'
* })
* .then(console.log)
* .catch(console.error)
*/
async createWebhook({ channel, name, avatar, reason }) {
const id = this.resolveId(channel);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable');
if (typeof avatar === 'string' && !avatar.startsWith('data:')) {
avatar = await resolveImage(avatar);
}
const data = await this.client.rest.post(Routes.channelWebhooks(id), {
body: {
name,
avatar,
},
reason,
});
return new Webhook(this.client, data);
}
/**
* Options used to edit a guild channel.
* @typedef {Object} GuildChannelEditOptions
* @property {string} [name] The name of the channel
* @property {ChannelType} [type] The type of the channel (only conversion between text and news is supported)
* @property {number} [position] The position of the channel
* @property {?string} [topic] The topic of the text channel
* @property {boolean} [nsfw] Whether the channel is NSFW
* @property {number} [bitrate] The bitrate of the voice channel
* @property {number} [userLimit] The user limit of the voice channel
* @property {?CategoryChannelResolvable} [parent] The parent of the channel
* @property {boolean} [lockPermissions]
* Lock the permissions of the channel to what the parent's permissions are
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
* Permission overwrites for the channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) for the channel in seconds
* @property {ThreadAutoArchiveDuration} [defaultAutoArchiveDuration]
* The default auto archive duration for all new threads in this channel
* @property {?string} [rtcRegion] The RTC region of the channel
* @property {?VideoQualityMode} [videoQualityMode] The camera video quality mode of the channel
* @property {GuildForumTagData[]} [availableTags] The tags to set as available in a forum channel
* @property {?DefaultReactionEmoji} [defaultReactionEmoji] The emoji to set as the default reaction emoji
* @property {number} [defaultThreadRateLimitPerUser] The rate limit per user (slowmode) to set on forum posts
* @property {ChannelFlagsResolvable} [flags] The flags to set on the channel
* @property {?SortOrderType} [defaultSortOrder] The default sort order mode to set on the channel
* @property {ForumLayoutType} [defaultForumLayout] The default forum layout to set on the channel
* @property {string} [reason] Reason for editing this channel
*/
/**
* Edits the channel.
* @param {GuildChannelResolvable} channel The channel to edit
* @param {GuildChannelEditOptions} options Options for editing the channel
* @returns {Promise<GuildChannel>}
* @example
* // Edit a channel
* guild.channels.edit('222197033908436994', { name: 'new-channel' })
* .then(console.log)
* .catch(console.error);
*/
async edit(channel, options) {
const resolvedChannel = this.resolve(channel);
if (!resolvedChannel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable');
const parent = options.parent && this.client.channels.resolveId(options.parent);
if (options.position !== undefined) {
await this.setPosition(resolvedChannel, options.position, { position: options.position, reason: options.reason });
}
let permission_overwrites = options.permissionOverwrites?.map(overwrite =>
PermissionOverwrites.resolve(overwrite, this.guild),
);
if (options.lockPermissions) {
if (parent) {
const newParent = this.guild.channels.resolve(parent);
if (newParent?.type === ChannelType.GuildCategory) {
permission_overwrites = newParent.permissionOverwrites.cache.map(overwrite =>
PermissionOverwrites.resolve(overwrite, this.guild),
);
}
} else if (resolvedChannel.parent) {
permission_overwrites = resolvedChannel.parent.permissionOverwrites.cache.map(overwrite =>
PermissionOverwrites.resolve(overwrite, this.guild),
);
}
}
const newData = await this.client.rest.patch(Routes.channel(resolvedChannel.id), {
body: {
name: options.name,
type: options.type,
topic: options.topic,
nsfw: options.nsfw,
bitrate: options.bitrate,
user_limit: options.userLimit,
rtc_region: options.rtcRegion,
video_quality_mode: options.videoQualityMode,
parent_id: parent,
lock_permissions: options.lockPermissions,
rate_limit_per_user: options.rateLimitPerUser,
default_auto_archive_duration: options.defaultAutoArchiveDuration,
permission_overwrites,
available_tags: options.availableTags?.map(availableTag => transformGuildForumTag(availableTag)),
default_reaction_emoji:
options.defaultReactionEmoji && transformGuildDefaultReaction(options.defaultReactionEmoji),
default_thread_rate_limit_per_user: options.defaultThreadRateLimitPerUser,
flags: 'flags' in options ? ChannelFlagsBitField.resolve(options.flags) : undefined,
default_sort_order: options.defaultSortOrder,
default_forum_layout: options.defaultForumLayout,
},
reason: options.reason,
});
return this.client.actions.ChannelUpdate.handle(newData).updated;
}
/**
* Sets a new position for the guild channel.
* @param {GuildChannelResolvable} channel The channel to set the position for
* @param {number} position The new position for the guild channel
* @param {SetChannelPositionOptions} options Options for setting position
* @returns {Promise<GuildChannel>}
* @example
* // Set a new channel position
* guild.channels.setPosition('222078374472843266', 2)
* .then(newChannel => console.log(`Channel's new position is ${newChannel.position}`))
* .catch(console.error);
*/
async setPosition(channel, position, { relative, reason } = {}) {
channel = this.resolve(channel);
if (!channel) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable');
const updatedChannels = await setPosition(
channel,
position,
relative,
this.guild._sortedChannels(channel),
this.client,
Routes.guildChannels(this.guild.id),
reason,
);
this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.guild.id,
channels: updatedChannels,
});
return channel;
}
/**
* Obtains one or more guild channels from Discord, or the channel cache if they're already available.
* @param {Snowflake} [id] The channel's id
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<?GuildChannel|ThreadChannel|Collection<Snowflake, ?GuildChannel>>}
* @example
* // Fetch all channels from the guild (excluding threads)
* message.guild.channels.fetch()
* .then(channels => console.log(`There are ${channels.size} channels.`))
* .catch(console.error);
* @example
* // Fetch a single channel
* message.guild.channels.fetch('222197033908436994')
* .then(channel => console.log(`The channel name is: ${channel.name}`))
* .catch(console.error);
*/
async fetch(id, { cache = true, force = false } = {}) {
if (id && !force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
if (id) {
const data = await this.client.rest.get(Routes.channel(id));
// Since this is the guild manager, throw if on a different guild
if (this.guild.id !== data.guild_id) throw new DiscordjsError(ErrorCodes.GuildChannelUnowned);
return this.client.channels._add(data, this.guild, { cache });
}
const data = await this.client.rest.get(Routes.guildChannels(this.guild.id));
const channels = new Collection();
for (const channel of data) channels.set(channel.id, this.client.channels._add(channel, this.guild, { cache }));
return channels;
}
/**
* Fetches all webhooks for the channel.
* @param {GuildChannelResolvable} channel The channel to fetch webhooks for
* @returns {Promise<Collection<Snowflake, Webhook>>}
* @example
* // Fetch webhooks
* guild.channels.fetchWebhooks('769862166131245066')
* .then(hooks => console.log(`This channel has ${hooks.size} hooks`))
* .catch(console.error);
*/
async fetchWebhooks(channel) {
const id = this.resolveId(channel);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable');
const data = await this.client.rest.get(Routes.channelWebhooks(id));
return data.reduce((hooks, hook) => hooks.set(hook.id, new Webhook(this.client, hook)), new Collection());
}
/**
* Data that can be resolved to give a Category Channel object. This can be:
* * A CategoryChannel object
* * A Snowflake
* @typedef {CategoryChannel|Snowflake} CategoryChannelResolvable
*/
/**
* The data needed for updating a channel's position.
* @typedef {Object} ChannelPosition
* @property {GuildChannel|Snowflake} channel Channel to update
* @property {number} [position] New position for the channel
* @property {CategoryChannelResolvable} [parent] Parent channel for this channel
* @property {boolean} [lockPermissions] If the overwrites should be locked to the parents overwrites
*/
/**
* Batch-updates the guild's channels' positions.
* <info>Only one channel's parent can be changed at a time</info>
* @param {ChannelPosition[]} channelPositions Channel positions to update
* @returns {Promise<Guild>}
* @example
* guild.channels.setPositions([{ channel: channelId, position: newChannelIndex }])
* .then(guild => console.log(`Updated channel positions for ${guild}`))
* .catch(console.error);
*/
async setPositions(channelPositions) {
channelPositions = channelPositions.map(channelPosition => ({
id: this.client.channels.resolveId(channelPosition.channel),
position: channelPosition.position,
lock_permissions: channelPosition.lockPermissions,
parent_id: channelPosition.parent !== undefined ? this.resolveId(channelPosition.parent) : undefined,
}));
await this.client.rest.patch(Routes.guildChannels(this.guild.id), { body: channelPositions });
return this.client.actions.GuildChannelsPositionUpdate.handle({
guild_id: this.guild.id,
channels: channelPositions,
}).guild;
}
/**
* Data returned from fetching threads.
* @typedef {Object} FetchedThreads
* @property {Collection<Snowflake, ThreadChannel>} threads The threads that were fetched
* @property {Collection<Snowflake, ThreadMember>} members The thread members in the received threads
*/
/**
* Obtains all active thread channels in the guild.
* @param {boolean} [cache=true] Whether to cache the fetched data
* @returns {Promise<FetchedThreads>}
* @example
* // Fetch all threads from the guild
* message.guild.channels.fetchActiveThreads()
* .then(fetched => console.log(`There are ${fetched.threads.size} threads.`))
* .catch(console.error);
*/
async fetchActiveThreads(cache = true) {
const data = await this.rawFetchGuildActiveThreads();
return GuildTextThreadManager._mapThreads(data, this.client, { guild: this.guild, cache });
}
/**
* `GET /guilds/{guild.id}/threads/active`
* @private
* @returns {Promise<RESTGetAPIGuildThreadsResult>}
*/
rawFetchGuildActiveThreads() {
return this.client.rest.get(Routes.guildActiveThreads(this.guild.id));
}
/**
* Deletes the channel.
* @param {GuildChannelResolvable} channel The channel to delete
* @param {string} [reason] Reason for deleting this channel
* @returns {Promise<void>}
* @example
* // Delete the channel
* guild.channels.delete('858850993013260338', 'making room for new channels')
* .then(console.log)
* .catch(console.error);
*/
async delete(channel, reason) {
const id = this.resolveId(channel);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable');
await this.client.rest.delete(Routes.channel(id), { reason });
this.client.actions.ChannelDelete.handle({ id });
}
}
module.exports = GuildChannelManager;

View File

@@ -0,0 +1,174 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes, PermissionFlagsBits } = require('discord-api-types/v10');
const BaseGuildEmojiManager = require('./BaseGuildEmojiManager');
const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors');
const { resolveImage } = require('../util/DataResolver');
/**
* Manages API methods for GuildEmojis and stores their cache.
* @extends {BaseGuildEmojiManager}
*/
class GuildEmojiManager extends BaseGuildEmojiManager {
constructor(guild, iterable) {
super(guild.client, iterable);
/**
* The guild this manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
_add(data, cache) {
return super._add(data, cache, { extras: [this.guild] });
}
/**
* Options used for creating an emoji in a guild.
* @typedef {Object} GuildEmojiCreateOptions
* @property {BufferResolvable|Base64Resolvable} attachment The image for the emoji
* @property {string} name The name for the emoji
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles to limit the emoji to
* @property {string} [reason] The reason for creating the emoji
*/
/**
* Creates a new custom emoji in the guild.
* @param {GuildEmojiCreateOptions} options Options for creating the emoji
* @returns {Promise<Emoji>} The created emoji
* @example
* // Create a new emoji from a URL
* guild.emojis.create({ attachment: 'https://i.imgur.com/w3duR07.png', name: 'rip' })
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
* .catch(console.error);
* @example
* // Create a new emoji from a file on your computer
* guild.emojis.create({ attachment: './memes/banana.png', name: 'banana' })
* .then(emoji => console.log(`Created new emoji with name ${emoji.name}!`))
* .catch(console.error);
*/
async create({ attachment, name, roles, reason }) {
attachment = await resolveImage(attachment);
if (!attachment) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType);
const body = { image: attachment, name };
if (roles) {
if (!Array.isArray(roles) && !(roles instanceof Collection)) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'options.roles',
'Array or Collection of Roles or Snowflakes',
true,
);
}
body.roles = [];
for (const role of roles.values()) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) {
throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'options.roles', role);
}
body.roles.push(resolvedRole);
}
}
const emoji = await this.client.rest.post(Routes.guildEmojis(this.guild.id), { body, reason });
return this.client.actions.GuildEmojiCreate.handle(this.guild, emoji).emoji;
}
/**
* Obtains one or more emojis from Discord, or the emoji cache if they're already available.
* @param {Snowflake} [id] The emoji's id
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<GuildEmoji|Collection<Snowflake, GuildEmoji>>}
* @example
* // Fetch all emojis from the guild
* message.guild.emojis.fetch()
* .then(emojis => console.log(`There are ${emojis.size} emojis.`))
* .catch(console.error);
* @example
* // Fetch a single emoji
* message.guild.emojis.fetch('222078108977594368')
* .then(emoji => console.log(`The emoji name is: ${emoji.name}`))
* .catch(console.error);
*/
async fetch(id, { cache = true, force = false } = {}) {
if (id) {
if (!force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
const emoji = await this.client.rest.get(Routes.guildEmoji(this.guild.id, id));
return this._add(emoji, cache);
}
const data = await this.client.rest.get(Routes.guildEmojis(this.guild.id));
const emojis = new Collection();
for (const emoji of data) emojis.set(emoji.id, this._add(emoji, cache));
return emojis;
}
/**
* Deletes an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to delete
* @param {string} [reason] Reason for deleting the emoji
* @returns {Promise<void>}
*/
async delete(emoji, reason) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
await this.client.rest.delete(Routes.guildEmoji(this.guild.id, id), { reason });
}
/**
* Edits an emoji.
* @param {EmojiResolvable} emoji The Emoji resolvable to edit
* @param {GuildEmojiEditOptions} options The options to provide
* @returns {Promise<GuildEmoji>}
*/
async edit(emoji, options) {
const id = this.resolveId(emoji);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
const roles = options.roles?.map(role => this.guild.roles.resolveId(role));
const newData = await this.client.rest.patch(Routes.guildEmoji(this.guild.id, id), {
body: {
name: options.name,
roles,
},
reason: options.reason,
});
const existing = this.cache.get(id);
if (existing) {
const clone = existing._clone();
clone._patch(newData);
return clone;
}
return this._add(newData);
}
/**
* Fetches the author for this emoji
* @param {EmojiResolvable} emoji The emoji to fetch the author of
* @returns {Promise<User>}
*/
async fetchAuthor(emoji) {
emoji = this.resolve(emoji);
if (!emoji) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'emoji', 'EmojiResolvable', true);
if (emoji.managed) {
throw new DiscordjsError(ErrorCodes.EmojiManaged);
}
const { me } = this.guild.members;
if (!me) throw new DiscordjsError(ErrorCodes.GuildUncachedMe);
if (!me.permissions.has(PermissionFlagsBits.ManageGuildExpressions)) {
throw new DiscordjsError(ErrorCodes.MissingManageGuildExpressionsPermission, this.guild);
}
const data = await this.client.rest.get(Routes.guildEmoji(this.guild.id, emoji.id));
emoji._patch(data);
return emoji.author;
}
}
module.exports = GuildEmojiManager;

View File

@@ -0,0 +1,118 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const DataManager = require('./DataManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Role } = require('../structures/Role');
/**
* Manages API methods for roles belonging to emojis and stores their cache.
* @extends {DataManager}
*/
class GuildEmojiRoleManager extends DataManager {
constructor(emoji) {
super(emoji.client, Role);
/**
* The emoji belonging to this manager
* @type {GuildEmoji}
*/
this.emoji = emoji;
/**
* The guild belonging to this manager
* @type {Guild}
*/
this.guild = emoji.guild;
}
/**
* The cache of roles belonging to this emoji
* @type {Collection<Snowflake, Role>}
* @readonly
*/
get cache() {
return this.guild.roles.cache.filter(role => this.emoji._roles.includes(role.id));
}
/**
* Adds a role (or multiple roles) to the list of roles that can use this emoji.
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add
* @returns {Promise<GuildEmoji>}
*/
add(roleOrRoles) {
if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles];
const resolvedRoles = [];
for (const role of roleOrRoles.values()) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) {
return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role));
}
resolvedRoles.push(resolvedRole);
}
const newRoles = [...new Set(resolvedRoles.concat(...this.cache.keys()))];
return this.set(newRoles);
}
/**
* Removes a role (or multiple roles) from the list of roles that can use this emoji.
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove
* @returns {Promise<GuildEmoji>}
*/
remove(roleOrRoles) {
if (!Array.isArray(roleOrRoles) && !(roleOrRoles instanceof Collection)) roleOrRoles = [roleOrRoles];
const resolvedRoleIds = [];
for (const role of roleOrRoles.values()) {
const roleId = this.guild.roles.resolveId(role);
if (!roleId) {
return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role));
}
resolvedRoleIds.push(roleId);
}
const newRoles = [...this.cache.keys()].filter(id => !resolvedRoleIds.includes(id));
return this.set(newRoles);
}
/**
* Sets the role(s) that can use this emoji.
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role ids to apply
* @returns {Promise<GuildEmoji>}
* @example
* // Set the emoji's roles to a single role
* guildEmoji.roles.set(['391156570408615936'])
* .then(console.log)
* .catch(console.error);
* @example
* // Remove all roles from an emoji
* guildEmoji.roles.set([])
* .then(console.log)
* .catch(console.error);
*/
set(roles) {
return this.emoji.edit({ roles });
}
clone() {
const clone = new this.constructor(this.emoji);
clone._patch([...this.cache.keys()]);
return clone;
}
/**
* Patches the roles for this manager's cache
* @param {Snowflake[]} roles The new roles
* @private
*/
_patch(roles) {
this.emoji._roles = roles;
}
valueOf() {
return this.cache;
}
}
module.exports = GuildEmojiRoleManager;

View File

@@ -0,0 +1,83 @@
'use strict';
const { Routes } = require('discord-api-types/v10');
const ThreadManager = require('./ThreadManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const MessagePayload = require('../structures/MessagePayload');
/**
* Manages API methods for threads in forum channels and stores their cache.
* @extends {ThreadManager}
*/
class GuildForumThreadManager extends ThreadManager {
/**
* The channel this Manager belongs to
* @name GuildForumThreadManager#channel
* @type {ForumChannel}
*/
/**
* @typedef {BaseMessageOptions} GuildForumThreadMessageCreateOptions
* @property {StickerResolvable} [stickers] The stickers to send with the message
* @property {BitFieldResolvable} [flags] The flags to send with the message
* <info>Only `MessageFlags.SuppressEmbeds` and `MessageFlags.SuppressNotifications` can be set.</info>
*/
/**
* Options for creating a thread.
* @typedef {StartThreadOptions} GuildForumThreadCreateOptions
* @property {GuildForumThreadMessageCreateOptions|MessagePayload} message The message associated with the thread post
* @property {Snowflake[]} [appliedTags] The tags to apply to the thread
*/
/**
* Creates a new thread in the channel.
* @param {GuildForumThreadCreateOptions} [options] Options to create a new thread
* @returns {Promise<ThreadChannel>}
* @example
* // Create a new forum post
* forum.threads
* .create({
* name: 'Food Talk',
* autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
* message: {
* content: 'Discuss your favorite food!',
* },
* reason: 'Needed a separate thread for food',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
*/
async create({
name,
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
message,
reason,
rateLimitPerUser,
appliedTags,
} = {}) {
if (!message) {
throw new DiscordjsTypeError(ErrorCodes.GuildForumMessageRequired);
}
const { body, files } = await (message instanceof MessagePayload ? message : MessagePayload.create(this, message))
.resolveBody()
.resolveFiles();
const data = await this.client.rest.post(Routes.threads(this.channel.id), {
body: {
name,
auto_archive_duration: autoArchiveDuration,
rate_limit_per_user: rateLimitPerUser,
applied_tags: appliedTags,
message: body,
},
files,
reason,
});
return this.client.actions.ThreadCreate.handle(data).thread;
}
}
module.exports = GuildForumThreadManager;

View File

@@ -0,0 +1,215 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsError, ErrorCodes } = require('../errors');
const Invite = require('../structures/Invite');
const { resolveInviteCode } = require('../util/DataResolver');
/**
* Manages API methods for GuildInvites and stores their cache.
* @extends {CachedManager}
*/
class GuildInviteManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, Invite, iterable);
/**
* The guild this Manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of this Manager
* @type {Collection<string, Invite>}
* @name GuildInviteManager#cache
*/
_add(data, cache) {
return super._add(data, cache, { id: data.code, extras: [this.guild] });
}
/**
* Data that resolves to give an Invite object. This can be:
* * An invite code
* * An invite URL
* @typedef {string} InviteResolvable
*/
/**
* Data that can be resolved to a channel that an invite can be created on. This can be:
* * TextChannel
* * VoiceChannel
* * NewsChannel
* * StageChannel
* * ForumChannel
* * MediaChannel
* * Snowflake
* @typedef {TextChannel|VoiceChannel|NewsChannel|StageChannel|ForumChannel|MediaChannel|Snowflake}
* GuildInvitableChannelResolvable
*/
/**
* Resolves an InviteResolvable to an Invite object.
* @method resolve
* @memberof GuildInviteManager
* @instance
* @param {InviteResolvable} invite The invite resolvable to resolve
* @returns {?Invite}
*/
/**
* Resolves an InviteResolvable to an invite code string.
* @method resolveId
* @memberof GuildInviteManager
* @instance
* @param {InviteResolvable} invite The invite resolvable to resolve
* @returns {?string}
*/
/**
* Options used to fetch a single invite from a guild.
* @typedef {Object} FetchInviteOptions
* @property {InviteResolvable} code The invite to fetch
* @property {boolean} [cache=true] Whether or not to cache the fetched invite
* @property {boolean} [force=false] Whether to skip the cache check and request the API
*/
/**
* Options used to fetch all invites from a guild.
* @typedef {Object} FetchInvitesOptions
* @property {GuildInvitableChannelResolvable} [channelId]
* The channel to fetch all invites from
* @property {boolean} [cache=true] Whether or not to cache the fetched invites
*/
/**
* Fetches invite(s) from Discord.
* @param {InviteResolvable|FetchInviteOptions|FetchInvitesOptions} [options] Options for fetching guild invite(s)
* @returns {Promise<Invite|Collection<string, Invite>>}
* @example
* // Fetch all invites from a guild
* guild.invites.fetch()
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch all invites from a guild without caching
* guild.invites.fetch({ cache: false })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch all invites from a channel
* guild.invites.fetch({ channelId: '222197033908436994' })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single invite
* guild.invites.fetch('bRCvFy9')
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single invite without checking cache
* guild.invites.fetch({ code: 'bRCvFy9', force: true })
* .then(console.log)
* .catch(console.error)
* @example
* // Fetch a single invite without caching
* guild.invites.fetch({ code: 'bRCvFy9', cache: false })
* .then(console.log)
* .catch(console.error);
*/
fetch(options) {
if (!options) return this._fetchMany();
if (typeof options === 'string') {
const code = resolveInviteCode(options);
if (!code) return Promise.reject(new DiscordjsError(ErrorCodes.InviteResolveCode));
return this._fetchSingle({ code, cache: true });
}
if (!options.code) {
if (options.channelId) {
const id = this.guild.channels.resolveId(options.channelId);
if (!id) return Promise.reject(new DiscordjsError(ErrorCodes.GuildChannelResolve));
return this._fetchChannelMany(id, options.cache);
}
if ('cache' in options) return this._fetchMany(options.cache);
return Promise.reject(new DiscordjsError(ErrorCodes.InviteResolveCode));
}
return this._fetchSingle({
...options,
code: resolveInviteCode(options.code),
});
}
async _fetchSingle({ code, cache, force = false }) {
if (!force) {
const existing = this.cache.get(code);
if (existing) return existing;
}
const invites = await this._fetchMany(cache);
const invite = invites.get(code);
if (!invite) throw new DiscordjsError(ErrorCodes.InviteNotFound);
return invite;
}
async _fetchMany(cache) {
const data = await this.client.rest.get(Routes.guildInvites(this.guild.id));
return data.reduce((col, invite) => col.set(invite.code, this._add(invite, cache)), new Collection());
}
async _fetchChannelMany(channelId, cache) {
const data = await this.client.rest.get(Routes.channelInvites(channelId));
return data.reduce((col, invite) => col.set(invite.code, this._add(invite, cache)), new Collection());
}
/**
* Create an invite to the guild from the provided channel.
* @param {GuildInvitableChannelResolvable} channel The options for creating the invite from a channel.
* @param {InviteCreateOptions} [options={}] The options for creating the invite from a channel.
* @returns {Promise<Invite>}
* @example
* // Create an invite to a selected channel
* guild.invites.create('599942732013764608')
* .then(console.log)
* .catch(console.error);
*/
async create(
channel,
{ temporary, maxAge, maxUses, unique, targetUser, targetApplication, targetType, reason } = {},
) {
const id = this.guild.channels.resolveId(channel);
if (!id) throw new DiscordjsError(ErrorCodes.GuildChannelResolve);
const invite = await this.client.rest.post(Routes.channelInvites(id), {
body: {
temporary,
max_age: maxAge,
max_uses: maxUses,
unique,
target_user_id: this.client.users.resolveId(targetUser),
target_application_id: targetApplication?.id ?? targetApplication?.applicationId ?? targetApplication,
target_type: targetType,
},
reason,
});
return new Invite(this.client, invite);
}
/**
* Deletes an invite.
* @param {InviteResolvable} invite The invite to delete
* @param {string} [reason] Reason for deleting the invite
* @returns {Promise<void>}
*/
async delete(invite, reason) {
const code = resolveInviteCode(invite);
await this.client.rest.delete(Routes.invite(code), { reason });
}
}
module.exports = GuildInviteManager;

299
node_modules/discord.js/src/managers/GuildManager.js generated vendored Normal file
View File

@@ -0,0 +1,299 @@
'use strict';
const process = require('node:process');
const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes, RouteBases } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const ShardClientUtil = require('../sharding/ShardClientUtil');
const { Guild } = require('../structures/Guild');
const GuildChannel = require('../structures/GuildChannel');
const GuildEmoji = require('../structures/GuildEmoji');
const { GuildMember } = require('../structures/GuildMember');
const Invite = require('../structures/Invite');
const OAuth2Guild = require('../structures/OAuth2Guild');
const { Role } = require('../structures/Role');
const { resolveImage } = require('../util/DataResolver');
const Events = require('../util/Events');
const PermissionsBitField = require('../util/PermissionsBitField');
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
const { resolveColor } = require('../util/Util');
let cacheWarningEmitted = false;
/**
* Manages API methods for Guilds and stores their cache.
* @extends {CachedManager}
*/
class GuildManager extends CachedManager {
constructor(client, iterable) {
super(client, Guild, iterable);
if (!cacheWarningEmitted && this._cache.constructor.name !== 'Collection') {
cacheWarningEmitted = true;
process.emitWarning(
`Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`,
'UnsupportedCacheOverwriteWarning',
);
}
}
/**
* The cache of this Manager
* @type {Collection<Snowflake, Guild>}
* @name GuildManager#cache
*/
/**
* Data that resolves to give a Guild object. This can be:
* * A Guild object
* * A GuildChannel object
* * A GuildEmoji object
* * A Role object
* * A Snowflake
* * An Invite object
* @typedef {Guild|GuildChannel|GuildMember|GuildEmoji|Role|Snowflake|Invite} GuildResolvable
*/
/**
* Partial data for a Role.
* @typedef {Object} PartialRoleData
* @property {Snowflake|number} [id] The role's id, used to set channel overrides.
* This is a placeholder and will be replaced by the API after consumption
* @property {string} [name] The name of the role
* @property {ColorResolvable} [color] The color of the role, either a hex string or a base 10 number
* @property {boolean} [hoist] Whether the role should be hoisted
* @property {number} [position] The position of the role
* @property {PermissionResolvable} [permissions] The permissions of the role
* @property {boolean} [mentionable] Whether the role should be mentionable
*/
/**
* Partial overwrite data.
* @typedef {Object} PartialOverwriteData
* @property {Snowflake|number} id The id of the {@link Role} or {@link User} this overwrite belongs to
* @property {OverwriteType} [type] The type of this overwrite
* @property {PermissionResolvable} [allow] The permissions to allow
* @property {PermissionResolvable} [deny] The permissions to deny
*/
/**
* Partial data for a Channel.
* @typedef {Object} PartialChannelData
* @property {Snowflake|number} [id] The channel's id, used to set its parent.
* This is a placeholder and will be replaced by the API after consumption
* @property {Snowflake|number} [parentId] The parent id for this channel
* @property {ChannelType.GuildText|ChannelType.GuildVoice|ChannelType.GuildCategory} [type] The type of the channel
* @property {string} name The name of the channel
* @property {?string} [topic] The topic of the text channel
* @property {boolean} [nsfw] Whether the channel is NSFW
* @property {number} [bitrate] The bitrate of the voice channel
* @property {number} [userLimit] The user limit of the channel
* @property {?string} [rtcRegion] The RTC region of the channel
* @property {VideoQualityMode} [videoQualityMode] The camera video quality mode of the channel
* @property {PartialOverwriteData[]} [permissionOverwrites]
* Overwrites of the channel
* @property {number} [rateLimitPerUser] The rate limit per user (slowmode) of the channel in seconds
*/
/**
* Resolves a {@link GuildResolvable} to a {@link Guild} object.
* @method resolve
* @memberof GuildManager
* @instance
* @param {GuildResolvable} guild The guild resolvable to identify
* @returns {?Guild}
*/
resolve(guild) {
if (
guild instanceof GuildChannel ||
guild instanceof GuildMember ||
guild instanceof GuildEmoji ||
guild instanceof Role ||
(guild instanceof Invite && guild.guild)
) {
return super.resolve(guild.guild);
}
return super.resolve(guild);
}
/**
* Resolves a {@link GuildResolvable} to a {@link Guild} id string.
* @method resolveId
* @memberof GuildManager
* @instance
* @param {GuildResolvable} guild The guild resolvable to identify
* @returns {?Snowflake}
*/
resolveId(guild) {
if (
guild instanceof GuildChannel ||
guild instanceof GuildMember ||
guild instanceof GuildEmoji ||
guild instanceof Role ||
(guild instanceof Invite && guild.guild)
) {
return super.resolveId(guild.guild.id);
}
return super.resolveId(guild);
}
/**
* Options used to create a guild.
* @typedef {Object} GuildCreateOptions
* @property {string} name The name of the guild
* @property {?(BufferResolvable|Base64Resolvable)} [icon=null] The icon for the guild
* @property {GuildVerificationLevel} [verificationLevel] The verification level for the guild
* @property {GuildDefaultMessageNotifications} [defaultMessageNotifications] The default message notifications
* for the guild
* @property {GuildExplicitContentFilter} [explicitContentFilter] The explicit content filter level for the guild
* @property {PartialRoleData[]} [roles=[]] The roles for this guild,
* @property {PartialChannelData[]} [channels=[]] The channels for this guild
* @property {Snowflake|number} [afkChannelId] The AFK channel's id
* @property {number} [afkTimeout] The AFK timeout in seconds
* the first element of this array is used to change properties of the guild's everyone role.
* @property {Snowflake|number} [systemChannelId] The system channel's id
* @property {SystemChannelFlagsResolvable} [systemChannelFlags] The flags of the system channel
*/
/* eslint-enable max-len */
/**
* Creates a guild.
* <warn>This is only available to bots in fewer than 10 guilds.</warn>
* @param {GuildCreateOptions} options Options for creating the guild
* @returns {Promise<Guild>} The guild that was created
*/
async create({
name,
icon = null,
verificationLevel,
defaultMessageNotifications,
explicitContentFilter,
roles = [],
channels = [],
afkChannelId,
afkTimeout,
systemChannelId,
systemChannelFlags,
}) {
const data = await this.client.rest.post(Routes.guilds(), {
body: {
name,
icon: icon && (await resolveImage(icon)),
verification_level: verificationLevel,
default_message_notifications: defaultMessageNotifications,
explicit_content_filter: explicitContentFilter,
roles: roles.map(({ color, permissions, ...options }) => ({
...options,
color: color && resolveColor(color),
permissions: permissions === undefined ? undefined : PermissionsBitField.resolve(permissions).toString(),
})),
channels: channels.map(
({
parentId,
userLimit,
rtcRegion,
videoQualityMode,
permissionOverwrites,
rateLimitPerUser,
...options
}) => ({
...options,
parent_id: parentId,
user_limit: userLimit,
rtc_region: rtcRegion,
video_quality_mode: videoQualityMode,
permission_overwrites: permissionOverwrites?.map(({ allow, deny, ...permissionOverwriteOptions }) => ({
...permissionOverwriteOptions,
allow: allow === undefined ? undefined : PermissionsBitField.resolve(allow).toString(),
deny: deny === undefined ? undefined : PermissionsBitField.resolve(deny).toString(),
})),
rate_limit_per_user: rateLimitPerUser,
}),
),
afk_channel_id: afkChannelId,
afk_timeout: afkTimeout,
system_channel_id: systemChannelId,
system_channel_flags:
systemChannelFlags === undefined ? undefined : SystemChannelFlagsBitField.resolve(systemChannelFlags),
},
});
return (
this.client.guilds.cache.get(data.id) ??
new Promise(resolve => {
const handleGuild = guild => {
if (guild.id === data.id) {
clearTimeout(timeout);
this.client.decrementMaxListeners();
resolve(guild);
}
};
this.client.incrementMaxListeners();
this.client.once(Events.GuildCreate, handleGuild);
const timeout = setTimeout(() => {
this.client.removeListener(Events.GuildCreate, handleGuild);
this.client.decrementMaxListeners();
resolve(this.client.guilds._add(data));
}, 10_000).unref();
})
);
}
/**
* Options used to fetch a single guild.
* @typedef {BaseFetchOptions} FetchGuildOptions
* @property {GuildResolvable} guild The guild to fetch
* @property {boolean} [withCounts=true] Whether the approximate member and presence counts should be returned
*/
/**
* Options used to fetch multiple guilds.
* @typedef {Object} FetchGuildsOptions
* @property {Snowflake} [before] Get guilds before this guild id
* @property {Snowflake} [after] Get guilds after this guild id
* @property {number} [limit] Maximum number of guilds to request (1-200)
*/
/**
* Obtains one or multiple guilds from Discord, or the guild cache if it's already available.
* @param {GuildResolvable|FetchGuildOptions|FetchGuildsOptions} [options] The guild's id or options
* @returns {Promise<Guild|Collection<Snowflake, OAuth2Guild>>}
*/
async fetch(options = {}) {
const id = this.resolveId(options) ?? this.resolveId(options.guild);
if (id) {
if (!options.force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
const data = await this.client.rest.get(Routes.guild(id), {
query: makeURLSearchParams({ with_counts: options.withCounts ?? true }),
});
data.shardId = ShardClientUtil.shardIdForGuildId(id, this.client.options.shardCount);
return this._add(data, options.cache);
}
const data = await this.client.rest.get(Routes.userGuilds(), { query: makeURLSearchParams(options) });
return data.reduce((coll, guild) => coll.set(guild.id, new OAuth2Guild(this.client, guild)), new Collection());
}
/**
* Returns a URL for the PNG widget of a guild.
* @param {GuildResolvable} guild The guild of the widget image
* @param {GuildWidgetStyle} [style] The style for the widget image
* @returns {string}
*/
widgetImageURL(guild, style) {
const urlSearchParams = String(makeURLSearchParams({ style }));
return `${RouteBases.api}${Routes.guildWidgetImage(this.resolveId(guild))}${
urlSearchParams ? `?${urlSearchParams}` : ''
}`;
}
}
module.exports = GuildManager;

View File

@@ -0,0 +1,560 @@
'use strict';
const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { DiscordSnowflake } = require('@sapphire/snowflake');
const { Routes, GatewayOpcodes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsError, DiscordjsTypeError, DiscordjsRangeError, ErrorCodes } = require('../errors');
const BaseGuildVoiceChannel = require('../structures/BaseGuildVoiceChannel');
const { GuildMember } = require('../structures/GuildMember');
const { Role } = require('../structures/Role');
const Events = require('../util/Events');
const { GuildMemberFlagsBitField } = require('../util/GuildMemberFlagsBitField');
const Partials = require('../util/Partials');
/**
* Manages API methods for GuildMembers and stores their cache.
* @extends {CachedManager}
*/
class GuildMemberManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, GuildMember, iterable);
/**
* The guild this manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of this Manager
* @type {Collection<Snowflake, GuildMember>}
* @name GuildMemberManager#cache
*/
_add(data, cache = true) {
return super._add(data, cache, { id: data.user.id, extras: [this.guild] });
}
/**
* Data that resolves to give a GuildMember object. This can be:
* * A GuildMember object
* * A User resolvable
* @typedef {GuildMember|UserResolvable} GuildMemberResolvable
*/
/**
* Resolves a {@link GuildMemberResolvable} to a {@link GuildMember} object.
* @param {GuildMemberResolvable} member The user that is part of the guild
* @returns {?GuildMember}
*/
resolve(member) {
const memberResolvable = super.resolve(member);
if (memberResolvable) return memberResolvable;
const userResolvable = this.client.users.resolveId(member);
if (userResolvable) return super.resolve(userResolvable);
return null;
}
/**
* Resolves a {@link GuildMemberResolvable} to a member id.
* @param {GuildMemberResolvable} member The user that is part of the guild
* @returns {?Snowflake}
*/
resolveId(member) {
const memberResolvable = super.resolveId(member);
if (memberResolvable) return memberResolvable;
const userResolvable = this.client.users.resolveId(member);
return this.cache.has(userResolvable) ? userResolvable : null;
}
/**
* Options used to add a user to a guild using OAuth2.
* @typedef {Object} AddGuildMemberOptions
* @property {string} accessToken An OAuth2 access token for the user with the {@link OAuth2Scopes.GuildsJoin}
* scope granted to the bot's application
* @property {string} [nick] The nickname to give to the member
* <info>This property requires the {@link PermissionFlagsBits.ManageNicknames} permission.</info>
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles to add to the member
* <info>This property requires the {@link PermissionFlagsBits.ManageRoles} permission.</info>
* @property {boolean} [mute] Whether the member should be muted
* <info>This property requires the {@link PermissionFlagsBits.MuteMembers} permission.</info>
* @property {boolean} [deaf] Whether the member should be deafened
* <info>This property requires the {@link PermissionFlagsBits.MuteMembers} permission.</info>
* @property {boolean} [force] Whether to skip the cache check and request the API directly
* @property {boolean} [fetchWhenExisting=true] Whether to fetch the user if not cached and already a member
*/
/**
* Adds a user to the guild using OAuth2.
* <info>This method requires the {@link PermissionFlagsBits.CreateInstantInvite} permission.
* @param {UserResolvable} user The user to add to the guild
* @param {AddGuildMemberOptions} options Options for adding the user to the guild
* @returns {Promise<?GuildMember>}
*/
async add(user, options) {
const userId = this.client.users.resolveId(user);
if (!userId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable');
if (!options.force) {
const cachedUser = this.cache.get(userId);
if (cachedUser) return cachedUser;
}
const resolvedOptions = {
access_token: options.accessToken,
nick: options.nick,
mute: options.mute,
deaf: options.deaf,
};
if (options.roles) {
if (!Array.isArray(options.roles) && !(options.roles instanceof Collection)) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'options.roles',
'Array or Collection of Roles or Snowflakes',
true,
);
}
const resolvedRoles = [];
for (const role of options.roles.values()) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) {
throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'options.roles', role);
}
resolvedRoles.push(resolvedRole);
}
resolvedOptions.roles = resolvedRoles;
}
const data = await this.client.rest.put(Routes.guildMember(this.guild.id, userId), { body: resolvedOptions });
// Data is an empty array buffer if the member is already part of the guild.
return data instanceof ArrayBuffer
? options.fetchWhenExisting === false
? null
: this.fetch(userId)
: this._add(data);
}
/**
* The client user as a GuildMember of this guild
* @type {?GuildMember}
* @readonly
*/
get me() {
return (
this.resolve(this.client.user.id) ??
(this.client.options.partials.includes(Partials.GuildMember)
? this._add({ user: { id: this.client.user.id } }, true)
: null)
);
}
/**
* Options used to fetch a single member from a guild.
* @typedef {BaseFetchOptions} FetchMemberOptions
* @property {UserResolvable} user The user to fetch
*/
/**
* Options used to fetch multiple members from a guild.
* @typedef {Object} FetchMembersOptions
* @property {UserResolvable|UserResolvable[]} [user] The user(s) to fetch
* @property {?string} [query] Limit fetch to members with similar usernames
* @property {number} [limit=0] Maximum number of members to request
* @property {boolean} [withPresences=false] Whether to include the presences
* @property {number} [time=120e3] Timeout for receipt of members
* @property {?string} [nonce] Nonce for this request (32 characters max - default to base 16 now timestamp)
*/
/**
* Fetches member(s) from a guild.
* @param {UserResolvable|FetchMemberOptions|FetchMembersOptions} [options] Options for fetching member(s).
* Omitting the parameter or providing `undefined` will fetch all members.
* @returns {Promise<GuildMember|Collection<Snowflake, GuildMember>>}
* @example
* // Fetch all members from a guild
* guild.members.fetch()
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single member
* guild.members.fetch('66564597481480192')
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch a single member without checking cache
* guild.members.fetch({ user, force: true })
* .then(console.log)
* .catch(console.error)
* @example
* // Fetch a single member without caching
* guild.members.fetch({ user, cache: false })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch by an array of users including their presences
* guild.members.fetch({ user: ['66564597481480192', '191615925336670208'], withPresences: true })
* .then(console.log)
* .catch(console.error);
* @example
* // Fetch by query
* guild.members.fetch({ query: 'hydra', limit: 1 })
* .then(console.log)
* .catch(console.error);
*/
fetch(options) {
if (!options) return this._fetchMany();
const { user: users, limit, withPresences, cache, force } = options;
const resolvedUser = this.client.users.resolveId(users ?? options);
if (resolvedUser && !limit && !withPresences) return this._fetchSingle({ user: resolvedUser, cache, force });
const resolvedUsers = users?.map?.(user => this.client.users.resolveId(user)) ?? resolvedUser ?? undefined;
return this._fetchMany({ ...options, users: resolvedUsers });
}
async _fetchSingle({ user, cache, force = false }) {
if (!force) {
const existing = this.cache.get(user);
if (existing && !existing.partial) return existing;
}
const data = await this.client.rest.get(Routes.guildMember(this.guild.id, user));
return this._add(data, cache);
}
_fetchMany({
limit = 0,
withPresences: presences,
users,
query,
time = 120e3,
nonce = DiscordSnowflake.generate().toString(),
} = {}) {
if (nonce.length > 32) return Promise.reject(new DiscordjsRangeError(ErrorCodes.MemberFetchNonceLength));
return new Promise((resolve, reject) => {
if (!query && !users) query = '';
this.guild.shard.send({
op: GatewayOpcodes.RequestGuildMembers,
d: {
guild_id: this.guild.id,
presences,
user_ids: users,
query,
nonce,
limit,
},
});
const fetchedMembers = new Collection();
let i = 0;
const handler = (members, _, chunk) => {
if (chunk.nonce !== nonce) return;
timeout.refresh();
i++;
for (const member of members.values()) {
fetchedMembers.set(member.id, member);
}
if (members.size < 1_000 || (limit && fetchedMembers.size >= limit) || i === chunk.count) {
clearTimeout(timeout);
this.client.removeListener(Events.GuildMembersChunk, handler);
this.client.decrementMaxListeners();
resolve(users && !Array.isArray(users) && fetchedMembers.size ? fetchedMembers.first() : fetchedMembers);
}
};
const timeout = setTimeout(() => {
this.client.removeListener(Events.GuildMembersChunk, handler);
this.client.decrementMaxListeners();
reject(new DiscordjsError(ErrorCodes.GuildMembersTimeout));
}, time).unref();
this.client.incrementMaxListeners();
this.client.on(Events.GuildMembersChunk, handler);
});
}
/**
* Fetches the client user as a GuildMember of the guild.
* @param {BaseFetchOptions} [options] The options for fetching the member
* @returns {Promise<GuildMember>}
*/
fetchMe(options) {
return this.fetch({ ...options, user: this.client.user.id });
}
/**
* Options used for searching guild members.
* @typedef {Object} GuildSearchMembersOptions
* @property {string} query Filter members whose username or nickname start with this query
* @property {number} [limit] Maximum number of members to search
* @property {boolean} [cache=true] Whether or not to cache the fetched member(s)
*/
/**
* Searches for members in the guild based on a query.
* @param {GuildSearchMembersOptions} options Options for searching members
* @returns {Promise<Collection<Snowflake, GuildMember>>}
*/
async search({ query, limit, cache = true } = {}) {
const data = await this.client.rest.get(Routes.guildMembersSearch(this.guild.id), {
query: makeURLSearchParams({ query, limit }),
});
return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection());
}
/**
* Options used for listing guild members.
* @typedef {Object} GuildListMembersOptions
* @property {Snowflake} [after] Limit fetching members to those with an id greater than the supplied id
* @property {number} [limit] Maximum number of members to list
* @property {boolean} [cache=true] Whether or not to cache the fetched member(s)
*/
/**
* Lists up to 1000 members of the guild.
* @param {GuildListMembersOptions} [options] Options for listing members
* @returns {Promise<Collection<Snowflake, GuildMember>>}
*/
async list({ after, limit, cache = true } = {}) {
const query = makeURLSearchParams({ limit, after });
const data = await this.client.rest.get(Routes.guildMembers(this.guild.id), { query });
return data.reduce((col, member) => col.set(member.user.id, this._add(member, cache)), new Collection());
}
/**
* The data for editing a guild member.
* @typedef {Object} GuildMemberEditOptions
* @property {?string} [nick] The nickname to set for the member
* @property {Collection<Snowflake, Role>|RoleResolvable[]} [roles] The roles or role ids to apply
* @property {boolean} [mute] Whether or not the member should be muted
* @property {boolean} [deaf] Whether or not the member should be deafened
* @property {?GuildVoiceChannelResolvable} [channel] Channel to move the member to
* (if they are connected to voice), or `null` if you want to disconnect them from voice
* @property {?DateResolvable} [communicationDisabledUntil] The date or timestamp
* for the member's communication to be disabled until. Provide `null` to enable communication again.
* @property {GuildMemberFlagsResolvable} [flags] The flags to set for the member
* @property {string} [reason] Reason for editing this user
*/
/**
* Edits a member of the guild.
* <info>The user must be a member of the guild</info>
* @param {UserResolvable} user The member to edit
* @param {GuildMemberEditOptions} options The options to provide
* @returns {Promise<GuildMember>}
*/
async edit(user, { reason, ...options }) {
const id = this.client.users.resolveId(user);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable');
if (options.channel) {
options.channel = this.guild.channels.resolve(options.channel);
if (!(options.channel instanceof BaseGuildVoiceChannel)) {
throw new DiscordjsError(ErrorCodes.GuildVoiceChannelResolve);
}
options.channel_id = options.channel.id;
options.channel = undefined;
} else if (options.channel === null) {
options.channel_id = null;
options.channel = undefined;
}
options.roles &&= options.roles.map(role => (role instanceof Role ? role.id : role));
if (options.communicationDisabledUntil !== undefined) {
options.communication_disabled_until =
// eslint-disable-next-line eqeqeq
options.communicationDisabledUntil != null
? new Date(options.communicationDisabledUntil).toISOString()
: options.communicationDisabledUntil;
}
if (options.flags !== undefined) {
options.flags = GuildMemberFlagsBitField.resolve(options.flags);
}
let endpoint;
if (id === this.client.user.id) {
const keys = Object.keys(options);
if (keys.length === 1 && keys[0] === 'nick') endpoint = Routes.guildMember(this.guild.id);
else endpoint = Routes.guildMember(this.guild.id, id);
} else {
endpoint = Routes.guildMember(this.guild.id, id);
}
const d = await this.client.rest.patch(endpoint, { body: options, reason });
const clone = this.cache.get(id)?._clone();
clone?._patch(d);
return clone ?? this._add(d, false);
}
/**
* Options used for pruning guild members.
* <info>It's recommended to set {@link GuildPruneMembersOptions#count options.count}
* to `false` for large guilds.</info>
* @typedef {Object} GuildPruneMembersOptions
* @property {number} [days] Number of days of inactivity required to kick
* @property {boolean} [dry=false] Get the number of users that will be kicked, without actually kicking them
* @property {boolean} [count] Whether or not to return the number of users that have been kicked.
* @property {RoleResolvable[]} [roles] Array of roles to bypass the "...and no roles" constraint when pruning
* @property {string} [reason] Reason for this prune
*/
/**
* Prunes members from the guild based on how long they have been inactive.
* @param {GuildPruneMembersOptions} [options] Options for pruning
* @returns {Promise<?number>} The number of members that were/will be kicked
* @example
* // See how many members will be pruned
* guild.members.prune({ dry: true })
* .then(pruned => console.log(`This will prune ${pruned} people!`))
* .catch(console.error);
* @example
* // Actually prune the members
* guild.members.prune({ days: 1, reason: 'too many people!' })
* .then(pruned => console.log(`I just pruned ${pruned} people!`))
* .catch(console.error);
* @example
* // Include members with a specified role
* guild.members.prune({ days: 7, roles: ['657259391652855808'] })
* .then(pruned => console.log(`I just pruned ${pruned} people!`))
* .catch(console.error);
*/
async prune({ days, dry = false, count: compute_prune_count, roles = [], reason } = {}) {
if (typeof days !== 'number') throw new DiscordjsTypeError(ErrorCodes.PruneDaysType);
const query = { days };
const resolvedRoles = [];
for (const role of roles) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) {
throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array', 'options.roles', role);
}
resolvedRoles.push(resolvedRole);
}
if (resolvedRoles.length) {
query.include_roles = dry ? resolvedRoles.join(',') : resolvedRoles;
}
const endpoint = Routes.guildPrune(this.guild.id);
const { pruned } = await (dry
? this.client.rest.get(endpoint, { query: makeURLSearchParams(query), reason })
: this.client.rest.post(endpoint, { body: { ...query, compute_prune_count }, reason }));
return pruned;
}
/**
* Kicks a user from the guild.
* <info>The user must be a member of the guild</info>
* @param {UserResolvable} user The member to kick
* @param {string} [reason] Reason for kicking
* @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible.
* If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
* be resolved, the user's id will be the result.
* @example
* // Kick a user by id (or with a user/guild member object)
* guild.members.kick('84484653687267328')
* .then(kickInfo => console.log(`Kicked ${kickInfo.user?.tag ?? kickInfo.tag ?? kickInfo}`))
* .catch(console.error);
*/
async kick(user, reason) {
const id = this.client.users.resolveId(user);
if (!id) return Promise.reject(new DiscordjsTypeError(ErrorCodes.InvalidType, 'user', 'UserResolvable'));
await this.client.rest.delete(Routes.guildMember(this.guild.id, id), { reason });
return this.resolve(user) ?? this.client.users.resolve(user) ?? id;
}
/**
* Bans a user from the guild.
* @param {UserResolvable} user The user to ban
* @param {BanOptions} [options] Options for the ban
* @returns {Promise<GuildMember|User|Snowflake>} Result object will be resolved as specifically as possible.
* If the GuildMember cannot be resolved, the User will instead be attempted to be resolved. If that also cannot
* be resolved, the user id will be the result.
* Internally calls the GuildBanManager#create method.
* @example
* // Ban a user by id (or with a user/guild member object)
* guild.members.ban('84484653687267328')
* .then(banInfo => console.log(`Banned ${banInfo.user?.tag ?? banInfo.tag ?? banInfo}`))
* .catch(console.error);
*/
ban(user, options) {
return this.guild.bans.create(user, options);
}
/**
* Unbans a user from the guild. Internally calls the {@link GuildBanManager#remove} method.
* @param {UserResolvable} user The user to unban
* @param {string} [reason] Reason for unbanning user
* @returns {Promise<?User>} The user that was unbanned
* @example
* // Unban a user by id (or with a user/guild member object)
* guild.members.unban('84484653687267328')
* .then(user => console.log(`Unbanned ${user.username} from ${guild.name}`))
* .catch(console.error);
*/
unban(user, reason) {
return this.guild.bans.remove(user, reason);
}
/**
* Bulk ban users from a guild, and optionally delete previous messages sent by them.
* @param {Collection<Snowflake, UserResolvable>|UserResolvable[]} users The users to ban
* @param {BulkBanOptions} [options] The options for bulk banning users
* @returns {Promise<BulkBanResult>} Returns an object with `bannedUsers` key containing the IDs of the banned users
* and the key `failedUsers` with the IDs that could not be banned or were already banned.
* Internally calls the GuildBanManager#bulkCreate method.
* @example
* // Bulk ban users by ids (or with user/guild member objects) and delete all their messages from the past 7 days
* guild.members.bulkBan(['84484653687267328'], { deleteMessageSeconds: 7 * 24 * 60 * 60 })
* .then(result => {
* console.log(`Banned ${result.bannedUsers.length} users, failed to ban ${result.failedUsers.length} users.`)
* })
* .catch(console.error);
*/
bulkBan(users, options = {}) {
return this.guild.bans.bulkCreate(users, options);
}
/**
* Options used for adding or removing a role from a member.
* @typedef {Object} AddOrRemoveGuildMemberRoleOptions
* @property {GuildMemberResolvable} user The user to add/remove the role from
* @property {RoleResolvable} role The role to add/remove
* @property {string} [reason] Reason for adding/removing the role
*/
/**
* Adds a role to a member.
* @param {AddOrRemoveGuildMemberRoleOptions} options Options for adding the role
* @returns {Promise<GuildMember|User|Snowflake>}
*/
async addRole(options) {
const { user, role, reason } = options;
const userId = this.guild.members.resolveId(user);
const roleId = this.guild.roles.resolveId(role);
await this.client.rest.put(Routes.guildMemberRole(this.guild.id, userId, roleId), { reason });
return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
}
/**
* Removes a role from a member.
* @param {AddOrRemoveGuildMemberRoleOptions} options Options for removing the role
* @returns {Promise<GuildMember|User|Snowflake>}
*/
async removeRole(options) {
const { user, role, reason } = options;
const userId = this.guild.members.resolveId(user);
const roleId = this.guild.roles.resolveId(role);
await this.client.rest.delete(Routes.guildMemberRole(this.guild.id, userId, roleId), { reason });
return this.resolve(user) ?? this.client.users.resolve(user) ?? userId;
}
}
module.exports = GuildMemberManager;

View File

@@ -0,0 +1,204 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v10');
const DataManager = require('./DataManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Role } = require('../structures/Role');
/**
* Manages API methods for roles of a GuildMember and stores their cache.
* @extends {DataManager}
*/
class GuildMemberRoleManager extends DataManager {
constructor(member) {
super(member.client, Role);
/**
* The GuildMember this manager belongs to
* @type {GuildMember}
*/
this.member = member;
/**
* The Guild this manager belongs to
* @type {Guild}
*/
this.guild = member.guild;
}
/**
* The roles of this member
* @type {Collection<Snowflake, Role>}
* @readonly
*/
get cache() {
const everyone = this.guild.roles.everyone;
return this.guild.roles.cache.filter(role => this.member._roles.includes(role.id)).set(everyone.id, everyone);
}
/**
* The role of the member used to hoist them in a separate category in the users list
* @type {?Role}
* @readonly
*/
get hoist() {
const hoistedRoles = this.cache.filter(role => role.hoist);
if (!hoistedRoles.size) return null;
return hoistedRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev));
}
/**
* The role of the member used to set their role icon
* @type {?Role}
* @readonly
*/
get icon() {
const iconRoles = this.cache.filter(role => role.icon || role.unicodeEmoji);
if (!iconRoles.size) return null;
return iconRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev));
}
/**
* The role of the member used to set their color
* @type {?Role}
* @readonly
*/
get color() {
const coloredRoles = this.cache.filter(role => role.color);
if (!coloredRoles.size) return null;
return coloredRoles.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev));
}
/**
* The role of the member with the highest position
* @type {Role}
* @readonly
*/
get highest() {
return this.cache.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this.cache.first());
}
/**
* The premium subscriber role of the guild, if present on the member
* @type {?Role}
* @readonly
*/
get premiumSubscriberRole() {
return this.cache.find(role => role.tags?.premiumSubscriberRole) ?? null;
}
/**
* The managed role this member created when joining the guild, if any
* <info>Only ever available on bots</info>
* @type {?Role}
* @readonly
*/
get botRole() {
if (!this.member.user.bot) return null;
return this.cache.find(role => role.tags?.botId === this.member.user.id) ?? null;
}
/**
* Adds a role (or multiple roles) to the member.
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to add
* @param {string} [reason] Reason for adding the role(s)
* @returns {Promise<GuildMember>}
*/
async add(roleOrRoles, reason) {
if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) {
const resolvedRoles = [];
for (const role of roleOrRoles.values()) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) {
throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role);
}
resolvedRoles.push(resolvedRole);
}
const newRoles = [...new Set(resolvedRoles.concat(...this.cache.keys()))];
return this.set(newRoles, reason);
} else {
roleOrRoles = this.guild.roles.resolveId(roleOrRoles);
if (roleOrRoles === null) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'roles',
'Role, Snowflake or Array or Collection of Roles or Snowflakes',
);
}
await this.client.rest.put(Routes.guildMemberRole(this.guild.id, this.member.id, roleOrRoles), { reason });
const clone = this.member._clone();
clone._roles = [...this.cache.keys(), roleOrRoles];
return clone;
}
}
/**
* Removes a role (or multiple roles) from the member.
* @param {RoleResolvable|RoleResolvable[]|Collection<Snowflake, Role>} roleOrRoles The role or roles to remove
* @param {string} [reason] Reason for removing the role(s)
* @returns {Promise<GuildMember>}
*/
async remove(roleOrRoles, reason) {
if (roleOrRoles instanceof Collection || Array.isArray(roleOrRoles)) {
const resolvedRoles = [];
for (const role of roleOrRoles.values()) {
const resolvedRole = this.guild.roles.resolveId(role);
if (!resolvedRole) {
throw new DiscordjsTypeError(ErrorCodes.InvalidElement, 'Array or Collection', 'roles', role);
}
resolvedRoles.push(resolvedRole);
}
const newRoles = this.cache.filter(role => !resolvedRoles.includes(role.id));
return this.set(newRoles, reason);
} else {
roleOrRoles = this.guild.roles.resolveId(roleOrRoles);
if (roleOrRoles === null) {
throw new DiscordjsTypeError(
ErrorCodes.InvalidType,
'roles',
'Role, Snowflake or Array or Collection of Roles or Snowflakes',
);
}
await this.client.rest.delete(Routes.guildMemberRole(this.guild.id, this.member.id, roleOrRoles), { reason });
const clone = this.member._clone();
const newRoles = this.cache.filter(role => role.id !== roleOrRoles);
clone._roles = [...newRoles.keys()];
return clone;
}
}
/**
* Sets the roles applied to the member.
* @param {Collection<Snowflake, Role>|RoleResolvable[]} roles The roles or role ids to apply
* @param {string} [reason] Reason for applying the roles
* @returns {Promise<GuildMember>}
* @example
* // Set the member's roles to a single role
* guildMember.roles.set(['391156570408615936'])
* .then(console.log)
* .catch(console.error);
* @example
* // Remove all the roles from a member
* guildMember.roles.set([])
* .then(member => console.log(`Member roles is now of ${member.roles.cache.size} size`))
* .catch(console.error);
*/
set(roles, reason) {
return this.member.edit({ roles, reason });
}
clone() {
const clone = new this.constructor(this.member);
clone.member._roles = [...this.cache.keys()];
return clone;
}
}
module.exports = GuildMemberRoleManager;

View File

@@ -0,0 +1,17 @@
'use strict';
const MessageManager = require('./MessageManager');
/**
* Manages API methods for messages in a guild and holds their cache.
* @extends {MessageManager}
*/
class GuildMessageManager extends MessageManager {
/**
* The channel that the messages belong to
* @name GuildMessageManager#channel
* @type {GuildTextBasedChannel}
*/
}
module.exports = GuildMessageManager;

View File

@@ -0,0 +1,297 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { GuildScheduledEventEntityType, Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors');
const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent');
const { resolveImage } = require('../util/DataResolver');
/**
* Manages API methods for GuildScheduledEvents and stores their cache.
* @extends {CachedManager}
*/
class GuildScheduledEventManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, GuildScheduledEvent, iterable);
/**
* The guild this manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of this manager
* @type {Collection<Snowflake, GuildScheduledEvent>}
* @name GuildScheduledEventManager#cache
*/
/**
* Data that resolves to give a GuildScheduledEvent object. This can be:
* * A Snowflake
* * A GuildScheduledEvent object
* @typedef {Snowflake|GuildScheduledEvent} GuildScheduledEventResolvable
*/
/**
* Options used to create a guild scheduled event.
* @typedef {Object} GuildScheduledEventCreateOptions
* @property {string} name The name of the guild scheduled event
* @property {DateResolvable} scheduledStartTime The time to schedule the event at
* @property {DateResolvable} [scheduledEndTime] The time to end the event at
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
* @property {GuildScheduledEventPrivacyLevel} privacyLevel The privacy level of the guild scheduled event
* @property {GuildScheduledEventEntityType} entityType The scheduled entity type of the event
* @property {string} [description] The description of the guild scheduled event
* @property {GuildVoiceChannelResolvable} [channel] The channel of the guild scheduled event
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.StageInstance} or
* {@link GuildScheduledEventEntityType.Voice}</warn>
* @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the
* guild scheduled event
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* @property {string} [reason] The reason for creating the guild scheduled event
*/
/**
* Options used to set entity metadata of a guild scheduled event.
* @typedef {Object} GuildScheduledEventEntityMetadataOptions
* @property {string} [location] The location of the guild scheduled event
* <warn>This is required if `entityType` is {@link GuildScheduledEventEntityType.External}</warn>
*/
/**
* Creates a new guild scheduled event.
* @param {GuildScheduledEventCreateOptions} options Options for creating the guild scheduled event
* @returns {Promise<GuildScheduledEvent>}
*/
async create(options) {
if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
let {
privacyLevel,
entityType,
channel,
name,
scheduledStartTime,
description,
scheduledEndTime,
entityMetadata,
reason,
image,
} = options;
let entity_metadata, channel_id;
if (entityType === GuildScheduledEventEntityType.External) {
channel_id = channel === undefined ? channel : null;
entity_metadata = { location: entityMetadata?.location };
} else {
channel_id = this.guild.channels.resolveId(channel);
if (!channel_id) throw new DiscordjsError(ErrorCodes.GuildVoiceChannelResolve);
entity_metadata = entityMetadata === undefined ? entityMetadata : null;
}
const data = await this.client.rest.post(Routes.guildScheduledEvents(this.guild.id), {
body: {
channel_id,
name,
privacy_level: privacyLevel,
scheduled_start_time: new Date(scheduledStartTime).toISOString(),
scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime,
description,
entity_type: entityType,
entity_metadata,
image: image && (await resolveImage(image)),
},
reason,
});
return this._add(data);
}
/**
* Options used to fetch a single guild scheduled event from a guild.
* @typedef {BaseFetchOptions} FetchGuildScheduledEventOptions
* @property {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to fetch
* @property {boolean} [withUserCount=true] Whether to fetch the number of users subscribed to the scheduled event
*/
/**
* Options used to fetch multiple guild scheduled events from a guild.
* @typedef {Object} FetchGuildScheduledEventsOptions
* @property {boolean} [cache] Whether or not to cache the fetched guild scheduled events
* @property {boolean} [withUserCount=true] Whether to fetch the number of users subscribed to each scheduled event
* should be returned
*/
/**
* Obtains one or more guild scheduled events from Discord, or the guild cache if it's already available.
* @param {GuildScheduledEventResolvable|FetchGuildScheduledEventOptions|FetchGuildScheduledEventsOptions} [options]
* The id of the guild scheduled event or options
* @returns {Promise<GuildScheduledEvent|Collection<Snowflake, GuildScheduledEvent>>}
*/
async fetch(options = {}) {
const id = this.resolveId(options.guildScheduledEvent ?? options);
if (id) {
if (!options.force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
const data = await this.client.rest.get(Routes.guildScheduledEvent(this.guild.id, id), {
query: makeURLSearchParams({ with_user_count: options.withUserCount ?? true }),
});
return this._add(data, options.cache);
}
const data = await this.client.rest.get(Routes.guildScheduledEvents(this.guild.id), {
query: makeURLSearchParams({ with_user_count: options.withUserCount ?? true }),
});
return data.reduce(
(coll, rawGuildScheduledEventData) =>
coll.set(
rawGuildScheduledEventData.id,
this.guild.scheduledEvents._add(rawGuildScheduledEventData, options.cache),
),
new Collection(),
);
}
/**
* Options used to edit a guild scheduled event.
* @typedef {Object} GuildScheduledEventEditOptions
* @property {string} [name] The name of the guild scheduled event
* @property {DateResolvable} [scheduledStartTime] The time to schedule the event at
* @property {DateResolvable} [scheduledEndTime] The time to end the event at
* @property {GuildScheduledEventPrivacyLevel} [privacyLevel] The privacy level of the guild scheduled event
* @property {GuildScheduledEventEntityType} [entityType] The scheduled entity type of the event
* @property {string} [description] The description of the guild scheduled event
* @property {?GuildVoiceChannelResolvable} [channel] The channel of the guild scheduled event
* @property {GuildScheduledEventStatus} [status] The status of the guild scheduled event
* @property {GuildScheduledEventEntityMetadataOptions} [entityMetadata] The entity metadata of the
* guild scheduled event
* <warn>This can be modified only if `entityType` of the `GuildScheduledEvent` to be edited is
* {@link GuildScheduledEventEntityType.External}</warn>
* @property {?(BufferResolvable|Base64Resolvable)} [image] The cover image of the guild scheduled event
* @property {string} [reason] The reason for editing the guild scheduled event
*/
/**
* Edits a guild scheduled event.
* @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to edit
* @param {GuildScheduledEventEditOptions} options Options to edit the guild scheduled event
* @returns {Promise<GuildScheduledEvent>}
*/
async edit(guildScheduledEvent, options) {
const guildScheduledEventId = this.resolveId(guildScheduledEvent);
if (!guildScheduledEventId) throw new DiscordjsError(ErrorCodes.GuildScheduledEventResolve);
if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
let {
privacyLevel,
entityType,
channel,
status,
name,
scheduledStartTime,
description,
scheduledEndTime,
entityMetadata,
reason,
image,
} = options;
let entity_metadata;
if (entityMetadata) {
entity_metadata = {
location: entityMetadata.location,
};
}
const data = await this.client.rest.patch(Routes.guildScheduledEvent(this.guild.id, guildScheduledEventId), {
body: {
channel_id: channel === undefined ? channel : this.guild.channels.resolveId(channel),
name,
privacy_level: privacyLevel,
scheduled_start_time: scheduledStartTime ? new Date(scheduledStartTime).toISOString() : undefined,
scheduled_end_time: scheduledEndTime ? new Date(scheduledEndTime).toISOString() : scheduledEndTime,
description,
entity_type: entityType,
status,
image: image && (await resolveImage(image)),
entity_metadata,
},
reason,
});
return this._add(data);
}
/**
* Deletes a guild scheduled event.
* @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to delete
* @returns {Promise<void>}
*/
async delete(guildScheduledEvent) {
const guildScheduledEventId = this.resolveId(guildScheduledEvent);
if (!guildScheduledEventId) throw new DiscordjsError(ErrorCodes.GuildScheduledEventResolve);
await this.client.rest.delete(Routes.guildScheduledEvent(this.guild.id, guildScheduledEventId));
}
/**
* Options used to fetch subscribers of a guild scheduled event
* @typedef {Object} FetchGuildScheduledEventSubscribersOptions
* @property {number} [limit] The maximum numbers of users to fetch
* @property {boolean} [withMember] Whether to fetch guild member data of the users
* @property {Snowflake} [before] Consider only users before this user id
* @property {Snowflake} [after] Consider only users after this user id
* <warn>If both `before` and `after` are provided, only `before` is respected</warn>
*/
/**
* Represents a subscriber of a {@link GuildScheduledEvent}
* @typedef {Object} GuildScheduledEventUser
* @property {Snowflake} guildScheduledEventId The id of the guild scheduled event which the user subscribed to
* @property {User} user The user that subscribed to the guild scheduled event
* @property {?GuildMember} member The guild member associated with the user, if any
*/
/**
* Fetches subscribers of a guild scheduled event.
* @param {GuildScheduledEventResolvable} guildScheduledEvent The guild scheduled event to fetch subscribers of
* @param {FetchGuildScheduledEventSubscribersOptions} [options={}] Options for fetching the subscribers
* @returns {Promise<Collection<Snowflake, GuildScheduledEventUser>>}
*/
async fetchSubscribers(guildScheduledEvent, options = {}) {
const guildScheduledEventId = this.resolveId(guildScheduledEvent);
if (!guildScheduledEventId) throw new DiscordjsError(ErrorCodes.GuildScheduledEventResolve);
const query = makeURLSearchParams({
limit: options.limit,
with_member: options.withMember,
before: options.before,
after: options.after,
});
const data = await this.client.rest.get(Routes.guildScheduledEventUsers(this.guild.id, guildScheduledEventId), {
query,
});
return data.reduce(
(coll, rawData) =>
coll.set(rawData.user.id, {
guildScheduledEventId: rawData.guild_scheduled_event_id,
user: this.client.users._add(rawData.user),
member: rawData.member ? this.guild.members._add({ ...rawData.member, user: rawData.user }) : null,
}),
new Collection(),
);
}
}
module.exports = GuildScheduledEventManager;

View File

@@ -0,0 +1,182 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const MessagePayload = require('../structures/MessagePayload');
const { Sticker } = require('../structures/Sticker');
/**
* Manages API methods for Guild Stickers and stores their cache.
* @extends {CachedManager}
*/
class GuildStickerManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, Sticker, iterable);
/**
* The guild this manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of Guild Stickers
* @type {Collection<Snowflake, Sticker>}
* @name GuildStickerManager#cache
*/
_add(data, cache) {
return super._add(data, cache, { extras: [this.guild] });
}
/**
* Options used to create a guild sticker.
* @typedef {Object} GuildStickerCreateOptions
* @property {AttachmentPayload|BufferResolvable|Stream} file The file for the sticker
* @property {string} name The name for the sticker
* @property {string} tags The Discord name of a unicode emoji representing the sticker's expression
* @property {?string} [description] The description for the sticker
* @property {string} [reason] Reason for creating the sticker
*/
/**
* Creates a new custom sticker in the guild.
* @param {GuildStickerCreateOptions} options Options for creating a guild sticker
* @returns {Promise<Sticker>} The created sticker
* @example
* // Create a new sticker from a URL
* guild.stickers.create({ file: 'https://i.imgur.com/w3duR07.png', name: 'rip', tags: 'headstone' })
* .then(sticker => console.log(`Created new sticker with name ${sticker.name}!`))
* .catch(console.error);
* @example
* // Create a new sticker from a file on your computer
* guild.stickers.create({ file: './memes/banana.png', name: 'banana', tags: 'banana' })
* .then(sticker => console.log(`Created new sticker with name ${sticker.name}!`))
* .catch(console.error);
*/
async create({ file, name, tags, description, reason } = {}) {
const resolvedFile = await MessagePayload.resolveFile(file);
if (!resolvedFile) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType);
file = { ...resolvedFile, key: 'file' };
const body = { name, tags, description: description ?? '' };
const sticker = await this.client.rest.post(Routes.guildStickers(this.guild.id), {
appendToFormData: true,
body,
files: [file],
reason,
});
return this.client.actions.GuildStickerCreate.handle(this.guild, sticker).sticker;
}
/**
* Data that resolves to give a Sticker object. This can be:
* * A Sticker object
* * A Snowflake
* @typedef {Sticker|Snowflake} StickerResolvable
*/
/**
* Resolves a StickerResolvable to a Sticker object.
* @method resolve
* @memberof GuildStickerManager
* @instance
* @param {StickerResolvable} sticker The Sticker resolvable to identify
* @returns {?Sticker}
*/
/**
* Resolves a StickerResolvable to a Sticker id string.
* @method resolveId
* @memberof GuildStickerManager
* @instance
* @param {StickerResolvable} sticker The Sticker resolvable to identify
* @returns {?Snowflake}
*/
/**
* Edits a sticker.
* @param {StickerResolvable} sticker The sticker to edit
* @param {GuildStickerEditOptions} [options={}] The new data for the sticker
* @returns {Promise<Sticker>}
*/
async edit(sticker, options = {}) {
const stickerId = this.resolveId(sticker);
if (!stickerId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sticker', 'StickerResolvable');
const d = await this.client.rest.patch(Routes.guildSticker(this.guild.id, stickerId), {
body: options,
reason: options.reason,
});
const existing = this.cache.get(stickerId);
if (existing) {
const clone = existing._clone();
clone._patch(d);
return clone;
}
return this._add(d);
}
/**
* Deletes a sticker.
* @param {StickerResolvable} sticker The sticker to delete
* @param {string} [reason] Reason for deleting this sticker
* @returns {Promise<void>}
*/
async delete(sticker, reason) {
sticker = this.resolveId(sticker);
if (!sticker) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sticker', 'StickerResolvable');
await this.client.rest.delete(Routes.guildSticker(this.guild.id, sticker), { reason });
}
/**
* Obtains one or more stickers from Discord, or the sticker cache if they're already available.
* @param {Snowflake} [id] The Sticker's id
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<Sticker|Collection<Snowflake, Sticker>>}
* @example
* // Fetch all stickers from the guild
* message.guild.stickers.fetch()
* .then(stickers => console.log(`There are ${stickers.size} stickers.`))
* .catch(console.error);
* @example
* // Fetch a single sticker
* message.guild.stickers.fetch('222078108977594368')
* .then(sticker => console.log(`The sticker name is: ${sticker.name}`))
* .catch(console.error);
*/
async fetch(id, { cache = true, force = false } = {}) {
if (id) {
if (!force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
const sticker = await this.client.rest.get(Routes.guildSticker(this.guild.id, id));
return this._add(sticker, cache);
}
const data = await this.client.rest.get(Routes.guildStickers(this.guild.id));
return new Collection(data.map(sticker => [sticker.id, this._add(sticker, cache)]));
}
/**
* Fetches the user who uploaded this sticker, if this is a guild sticker.
* @param {StickerResolvable} sticker The sticker to fetch the user for
* @returns {Promise<?User>}
*/
async fetchUser(sticker) {
sticker = this.resolve(sticker);
if (!sticker) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sticker', 'StickerResolvable');
const data = await this.client.rest.get(Routes.guildSticker(this.guild.id, sticker.id));
sticker._patch(data);
return sticker.user;
}
}
module.exports = GuildStickerManager;

View File

@@ -0,0 +1,91 @@
'use strict';
const { ChannelType, Routes } = require('discord-api-types/v10');
const ThreadManager = require('./ThreadManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
/**
* Manages API methods for {@link ThreadChannel} objects and stores their cache.
* @extends {ThreadManager}
*/
class GuildTextThreadManager extends ThreadManager {
/**
* The channel this Manager belongs to
* @name GuildTextThreadManager#channel
* @type {TextChannel|NewsChannel}
*/
/**
* Options for creating a thread. <warn>Only one of `startMessage` or `type` can be defined.</warn>
* @typedef {StartThreadOptions} GuildTextThreadCreateOptions
* @property {MessageResolvable} [startMessage] The message to start a thread from.
* <warn>If this is defined, then the `type` of thread gets inferred automatically and cannot be changed.</warn>
* @property {ThreadChannelTypes} [type] The type of thread to create.
* Defaults to {@link ChannelType.PublicThread} if created in a {@link TextChannel}
* <warn>When creating threads in a {@link NewsChannel}, this is ignored and is always
* {@link ChannelType.AnnouncementThread}</warn>
* @property {boolean} [invitable] Whether non-moderators can add other non-moderators to the thread
* <info>Can only be set when type will be {@link ChannelType.PrivateThread}</info>
*/
/**
* Creates a new thread in the channel.
* @param {GuildTextThreadCreateOptions} [options] Options to create a new thread
* @returns {Promise<ThreadChannel>}
* @example
* // Create a new public thread
* channel.threads
* .create({
* name: 'food-talk',
* autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
* reason: 'Needed a separate thread for food',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
* @example
* // Create a new private thread
* channel.threads
* .create({
* name: 'mod-talk',
* autoArchiveDuration: ThreadAutoArchiveDuration.OneHour,
* type: ChannelType.PrivateThread,
* reason: 'Needed a separate thread for moderation',
* })
* .then(threadChannel => console.log(threadChannel))
* .catch(console.error);
*/
async create({
name,
autoArchiveDuration = this.channel.defaultAutoArchiveDuration,
startMessage,
type,
invitable,
reason,
rateLimitPerUser,
} = {}) {
let resolvedType =
this.channel.type === ChannelType.GuildAnnouncement ? ChannelType.AnnouncementThread : ChannelType.PublicThread;
let startMessageId;
if (startMessage) {
startMessageId = this.channel.messages.resolveId(startMessage);
if (!startMessageId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'startMessage', 'MessageResolvable');
} else if (this.channel.type !== ChannelType.GuildAnnouncement) {
resolvedType = type ?? resolvedType;
}
const data = await this.client.rest.post(Routes.threads(this.channel.id, startMessageId), {
body: {
name,
auto_archive_duration: autoArchiveDuration,
type: resolvedType,
invitable: resolvedType === ChannelType.PrivateThread ? invitable : undefined,
rate_limit_per_user: rateLimitPerUser,
},
reason,
});
return this.client.actions.ThreadCreate.handle(data).thread;
}
}
module.exports = GuildTextThreadManager;

301
node_modules/discord.js/src/managers/MessageManager.js generated vendored Normal file
View File

@@ -0,0 +1,301 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Message } = require('../structures/Message');
const MessagePayload = require('../structures/MessagePayload');
const { MakeCacheOverrideSymbol } = require('../util/Symbols');
const { resolvePartialEmoji } = require('../util/Util');
/**
* Manages API methods for Messages and holds their cache.
* @extends {CachedManager}
* @abstract
*/
class MessageManager extends CachedManager {
static [MakeCacheOverrideSymbol] = MessageManager;
constructor(channel, iterable) {
super(channel.client, Message, iterable);
/**
* The channel that the messages belong to
* @type {TextBasedChannels}
*/
this.channel = channel;
}
/**
* The cache of Messages
* @type {Collection<Snowflake, Message>}
* @name MessageManager#cache
*/
_add(data, cache) {
return super._add(data, cache);
}
/**
* Data that can be resolved to a Message object. This can be:
* * A Message
* * A Snowflake
* @typedef {Message|Snowflake} MessageResolvable
*/
/**
* Options used to fetch a message.
* @typedef {BaseFetchOptions} FetchMessageOptions
* @property {MessageResolvable} message The message to fetch
*/
/**
* Options used to fetch multiple messages.
* <info>The `before`, `after`, and `around` parameters are mutually exclusive.</info>
* @typedef {Object} FetchMessagesOptions
* @property {number} [limit] The maximum number of messages to return
* @property {Snowflake} [before] Consider only messages before this id
* @property {Snowflake} [after] Consider only messages after this id
* @property {Snowflake} [around] Consider only messages around this id
* @property {boolean} [cache] Whether to cache the fetched messages
*/
/**
* Fetches message(s) from a channel.
* <info>The returned Collection does not contain reaction users of the messages if they were not cached.
* Those need to be fetched separately in such a case.</info>
* @param {MessageResolvable|FetchMessageOptions|FetchMessagesOptions} [options] Options for fetching message(s)
* @returns {Promise<Message|Collection<Snowflake, Message>>}
* @example
* // Fetch a message
* channel.messages.fetch('99539446449315840')
* .then(message => console.log(message.content))
* .catch(console.error);
* @example
* // Fetch a maximum of 10 messages without caching
* channel.messages.fetch({ limit: 10, cache: false })
* .then(messages => console.log(`Received ${messages.size} messages`))
* .catch(console.error);
* @example
* // Fetch a maximum of 10 messages without caching around a message id
* channel.messages.fetch({ limit: 10, cache: false, around: '99539446449315840' })
* .then(messages => console.log(`Received ${messages.size} messages`))
* .catch(console.error);
* @example
* // Fetch messages and filter by a user id
* channel.messages.fetch()
* .then(messages => console.log(`${messages.filter(message =>
* message.author.id === '84484653687267328').size} messages`))
* .catch(console.error);
*/
fetch(options) {
if (!options) return this._fetchMany();
const { message, cache, force } = options;
const resolvedMessage = this.resolveId(message ?? options);
if (resolvedMessage) return this._fetchSingle({ message: resolvedMessage, cache, force });
return this._fetchMany(options);
}
async _fetchSingle({ message, cache, force = false }) {
if (!force) {
const existing = this.cache.get(message);
if (existing && !existing.partial) return existing;
}
const data = await this.client.rest.get(Routes.channelMessage(this.channel.id, message));
return this._add(data, cache);
}
async _fetchMany(options = {}) {
const data = await this.client.rest.get(Routes.channelMessages(this.channel.id), {
query: makeURLSearchParams(options),
});
return data.reduce((_data, message) => _data.set(message.id, this._add(message, options.cache)), new Collection());
}
/**
* Fetches the pinned messages of this channel and returns a collection of them.
* <info>The returned Collection does not contain any reaction data of the messages.
* Those need to be fetched separately.</info>
* @param {boolean} [cache=true] Whether to cache the message(s)
* @returns {Promise<Collection<Snowflake, Message>>}
* @example
* // Get pinned messages
* channel.messages.fetchPinned()
* .then(messages => console.log(`Received ${messages.size} messages`))
* .catch(console.error);
*/
async fetchPinned(cache = true) {
const data = await this.client.rest.get(Routes.channelPins(this.channel.id));
const messages = new Collection();
for (const message of data) messages.set(message.id, this._add(message, cache));
return messages;
}
/**
* Resolves a {@link MessageResolvable} to a {@link Message} object.
* @method resolve
* @memberof MessageManager
* @instance
* @param {MessageResolvable} message The message resolvable to resolve
* @returns {?Message}
*/
/**
* Resolves a {@link MessageResolvable} to a {@link Message} id.
* @method resolveId
* @memberof MessageManager
* @instance
* @param {MessageResolvable} message The message resolvable to resolve
* @returns {?Snowflake}
*/
/**
* Data used to reference an attachment.
* @typedef {Object} MessageEditAttachmentData
* @property {Snowflake} id The id of the attachment
*/
/**
* Options that can be passed to edit a message.
* @typedef {BaseMessageOptions} MessageEditOptions
* @property {Array<Attachment|MessageEditAttachmentData>} [attachments] An array of attachments to keep.
* All attachments will be kept if omitted
* @property {MessageFlags} [flags] Which flags to set for the message
* <info>Only the {@link MessageFlags.SuppressEmbeds} flag can be modified.</info>
*/
/**
* Edits a message, even if it's not cached.
* @param {MessageResolvable} message The message to edit
* @param {string|MessageEditOptions|MessagePayload} options The options to edit the message
* @returns {Promise<Message>}
*/
async edit(message, options) {
const messageId = this.resolveId(message);
if (!messageId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable');
const { body, files } = await (
options instanceof MessagePayload
? options
: MessagePayload.create(message instanceof Message ? message : this, options)
)
.resolveBody()
.resolveFiles();
const d = await this.client.rest.patch(Routes.channelMessage(this.channel.id, messageId), { body, files });
const existing = this.cache.get(messageId);
if (existing) {
const clone = existing._clone();
clone._patch(d);
return clone;
}
return this._add(d);
}
/**
* Publishes a message in an announcement channel to all channels following it, even if it's not cached.
* @param {MessageResolvable} message The message to publish
* @returns {Promise<Message>}
*/
async crosspost(message) {
message = this.resolveId(message);
if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable');
const data = await this.client.rest.post(Routes.channelMessageCrosspost(this.channel.id, message));
return this.cache.get(data.id) ?? this._add(data);
}
/**
* Pins a message to the channel's pinned messages, even if it's not cached.
* @param {MessageResolvable} message The message to pin
* @param {string} [reason] Reason for pinning
* @returns {Promise<void>}
*/
async pin(message, reason) {
message = this.resolveId(message);
if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable');
await this.client.rest.put(Routes.channelPin(this.channel.id, message), { reason });
}
/**
* Unpins a message from the channel's pinned messages, even if it's not cached.
* @param {MessageResolvable} message The message to unpin
* @param {string} [reason] Reason for unpinning
* @returns {Promise<void>}
*/
async unpin(message, reason) {
message = this.resolveId(message);
if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable');
await this.client.rest.delete(Routes.channelPin(this.channel.id, message), { reason });
}
/**
* Adds a reaction to a message, even if it's not cached.
* @param {MessageResolvable} message The message to react to
* @param {EmojiIdentifierResolvable} emoji The emoji to react with
* @returns {Promise<void>}
*/
async react(message, emoji) {
message = this.resolveId(message);
if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable');
emoji = resolvePartialEmoji(emoji);
if (!emoji) throw new DiscordjsTypeError(ErrorCodes.EmojiType, 'emoji', 'EmojiIdentifierResolvable');
const emojiId = emoji.id
? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}`
: encodeURIComponent(emoji.name);
await this.client.rest.put(Routes.channelMessageOwnReaction(this.channel.id, message, emojiId));
}
/**
* Deletes a message, even if it's not cached.
* @param {MessageResolvable} message The message to delete
* @returns {Promise<void>}
*/
async delete(message) {
message = this.resolveId(message);
if (!message) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'message', 'MessageResolvable');
await this.client.rest.delete(Routes.channelMessage(this.channel.id, message));
}
/**
* Ends a poll.
* @param {Snowflake} messageId The id of the message
* @returns {Promise<Message>}
*/
async endPoll(messageId) {
const message = await this.client.rest.post(Routes.expirePoll(this.channel.id, messageId));
return this._add(message, false);
}
/**
* Options used for fetching voters of an answer in a poll.
* @typedef {BaseFetchPollAnswerVotersOptions} FetchPollAnswerVotersOptions
* @param {Snowflake} messageId The id of the message
* @param {number} answerId The id of the answer
*/
/**
* Fetches the users that voted for a poll answer.
* @param {FetchPollAnswerVotersOptions} options The options for fetching the poll answer voters
* @returns {Promise<Collection<Snowflake, User>>}
*/
async fetchPollAnswerVoters({ messageId, answerId, after, limit }) {
const voters = await this.client.rest.get(Routes.pollAnswerVoters(this.channel.id, messageId, answerId), {
query: makeURLSearchParams({ limit, after }),
});
return voters.users.reduce((acc, user) => acc.set(user.id, this.client.users._add(user, false)), new Collection());
}
}
module.exports = MessageManager;

View File

@@ -0,0 +1,17 @@
'use strict';
const MessageManager = require('./MessageManager');
/**
* Manages API methods for messages in group direct message channels and holds their cache.
* @extends {MessageManager}
*/
class PartialGroupDMMessageManager extends MessageManager {
/**
* The channel that the messages belong to
* @name PartialGroupDMMessageManager#channel
* @type {PartialGroupDMChannel}
*/
}
module.exports = PartialGroupDMMessageManager;

View File

@@ -0,0 +1,168 @@
'use strict';
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { OverwriteType, Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const PermissionOverwrites = require('../structures/PermissionOverwrites');
const { Role } = require('../structures/Role');
let cacheWarningEmitted = false;
/**
* Manages API methods for guild channel permission overwrites and stores their cache.
* @extends {CachedManager}
*/
class PermissionOverwriteManager extends CachedManager {
constructor(channel, iterable) {
super(channel.client, PermissionOverwrites);
if (!cacheWarningEmitted && this._cache.constructor.name !== 'Collection') {
cacheWarningEmitted = true;
process.emitWarning(
`Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`,
'UnsupportedCacheOverwriteWarning',
);
}
/**
* The channel of the permission overwrite this manager belongs to
* @type {GuildChannel}
*/
this.channel = channel;
if (iterable) {
for (const item of iterable) {
this._add(item);
}
}
}
/**
* The cache of this Manager
* @type {Collection<Snowflake, PermissionOverwrites>}
* @name PermissionOverwriteManager#cache
*/
_add(data, cache) {
return super._add(data, cache, { extras: [this.channel] });
}
/**
* Replaces the permission overwrites in this channel.
* @param {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} overwrites
* Permission overwrites the channel gets updated with
* @param {string} [reason] Reason for updating the channel overwrites
* @returns {Promise<GuildChannel>}
* @example
* message.channel.permissionOverwrites.set([
* {
* id: message.author.id,
* deny: [PermissionFlagsBits.ViewChannel],
* },
* ], 'Needed to change permissions');
*/
set(overwrites, reason) {
if (!Array.isArray(overwrites) && !(overwrites instanceof Collection)) {
return Promise.reject(
new DiscordjsTypeError(
ErrorCodes.InvalidType,
'overwrites',
'Array or Collection of Permission Overwrites',
true,
),
);
}
return this.channel.edit({ permissionOverwrites: overwrites, reason });
}
/**
* Extra information about the overwrite.
* @typedef {Object} GuildChannelOverwriteOptions
* @property {string} [reason] The reason for creating/editing this overwrite
* @property {OverwriteType} [type] The type of overwrite. Use this to bypass automatic resolution of `type`
* that results in an error for an uncached structure
*/
/**
* Creates or edits permission overwrites for a user or role in this channel.
* @param {RoleResolvable|UserResolvable} userOrRole The user or role to update
* @param {PermissionOverwriteOptions} options The options for the update
* @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update
* @param {PermissionOverwrites} [existing] The existing overwrites to merge with this update
* @returns {Promise<GuildChannel>}
* @private
*/
async upsert(userOrRole, options, overwriteOptions = {}, existing) {
let userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole);
let { type, reason } = overwriteOptions;
if (typeof type !== 'number') {
userOrRole = this.channel.guild.roles.resolve(userOrRole) ?? this.client.users.resolve(userOrRole);
if (!userOrRole) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'parameter', 'User nor a Role');
type = userOrRole instanceof Role ? OverwriteType.Role : OverwriteType.Member;
}
const { allow, deny } = PermissionOverwrites.resolveOverwriteOptions(options, existing);
await this.client.rest.put(Routes.channelPermission(this.channel.id, userOrRoleId), {
body: { id: userOrRoleId, type, allow, deny },
reason,
});
return this.channel;
}
/**
* Creates permission overwrites for a user or role in this channel, or replaces them if already present.
* @param {RoleResolvable|UserResolvable} userOrRole The user or role to update
* @param {PermissionOverwriteOptions} options The options for the update
* @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update
* @returns {Promise<GuildChannel>}
* @example
* // Create or Replace permission overwrites for a message author
* message.channel.permissionOverwrites.create(message.author, {
* SendMessages: false
* })
* .then(channel => console.log(channel.permissionOverwrites.cache.get(message.author.id)))
* .catch(console.error);
*/
create(userOrRole, options, overwriteOptions) {
return this.upsert(userOrRole, options, overwriteOptions);
}
/**
* Edits permission overwrites for a user or role in this channel, or creates an entry if not already present.
* @param {RoleResolvable|UserResolvable} userOrRole The user or role to update
* @param {PermissionOverwriteOptions} options The options for the update
* @param {GuildChannelOverwriteOptions} [overwriteOptions] The extra information for the update
* @returns {Promise<GuildChannel>}
* @example
* // Edit or Create permission overwrites for a message author
* message.channel.permissionOverwrites.edit(message.author, {
* SendMessages: false
* })
* .then(channel => console.log(channel.permissionOverwrites.cache.get(message.author.id)))
* .catch(console.error);
*/
edit(userOrRole, options, overwriteOptions) {
const existing = this.cache.get(
this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole),
);
return this.upsert(userOrRole, options, overwriteOptions, existing);
}
/**
* Deletes permission overwrites for a user or role in this channel.
* @param {UserResolvable|RoleResolvable} userOrRole The user or role to delete
* @param {string} [reason] The reason for deleting the overwrite
* @returns {Promise<GuildChannel>}
*/
async delete(userOrRole, reason) {
const userOrRoleId = this.channel.guild.roles.resolveId(userOrRole) ?? this.client.users.resolveId(userOrRole);
if (!userOrRoleId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'parameter', 'User nor a Role');
await this.client.rest.delete(Routes.channelPermission(this.channel.id, userOrRoleId), { reason });
return this.channel;
}
}
module.exports = PermissionOverwriteManager;

View File

@@ -0,0 +1,58 @@
'use strict';
const CachedManager = require('./CachedManager');
const { Presence } = require('../structures/Presence');
/**
* Manages API methods for Presences and holds their cache.
* @extends {CachedManager}
*/
class PresenceManager extends CachedManager {
constructor(client, iterable) {
super(client, Presence, iterable);
}
/**
* The cache of Presences
* @type {Collection<Snowflake, Presence>}
* @name PresenceManager#cache
*/
_add(data, cache) {
return super._add(data, cache, { id: data.user.id });
}
/**
* Data that can be resolved to a Presence object. This can be:
* * A Presence
* * A UserResolvable
* * A Snowflake
* @typedef {Presence|UserResolvable|Snowflake} PresenceResolvable
*/
/**
* Resolves a {@link PresenceResolvable} to a {@link Presence} object.
* @param {PresenceResolvable} presence The presence resolvable to resolve
* @returns {?Presence}
*/
resolve(presence) {
const presenceResolvable = super.resolve(presence);
if (presenceResolvable) return presenceResolvable;
const UserResolvable = this.client.users.resolveId(presence);
return super.resolve(UserResolvable);
}
/**
* Resolves a {@link PresenceResolvable} to a {@link Presence} id.
* @param {PresenceResolvable} presence The presence resolvable to resolve
* @returns {?Snowflake}
*/
resolveId(presence) {
const presenceResolvable = super.resolveId(presence);
if (presenceResolvable) return presenceResolvable;
const userResolvable = this.client.users.resolveId(presence);
return this.cache.has(userResolvable) ? userResolvable : null;
}
}
module.exports = PresenceManager;

View File

@@ -0,0 +1,68 @@
'use strict';
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const MessageReaction = require('../structures/MessageReaction');
/**
* Manages API methods for reactions and holds their cache.
* @extends {CachedManager}
*/
class ReactionManager extends CachedManager {
constructor(message, iterable) {
super(message.client, MessageReaction, iterable);
/**
* The message that this manager belongs to
* @type {Message}
*/
this.message = message;
}
_add(data, cache) {
return super._add(data, cache, { id: data.emoji.id ?? data.emoji.name, extras: [this.message] });
}
/**
* The reaction cache of this manager
* @type {Collection<string|Snowflake, MessageReaction>}
* @name ReactionManager#cache
*/
/**
* Data that can be resolved to a MessageReaction object. This can be:
* * A MessageReaction
* * A Snowflake
* * The Unicode representation of an emoji
* @typedef {MessageReaction|Snowflake} MessageReactionResolvable
*/
/**
* Resolves a {@link MessageReactionResolvable} to a {@link MessageReaction} object.
* @method resolve
* @memberof ReactionManager
* @instance
* @param {MessageReactionResolvable} reaction The MessageReaction to resolve
* @returns {?MessageReaction}
*/
/**
* Resolves a {@link MessageReactionResolvable} to a {@link MessageReaction} id.
* @method resolveId
* @memberof ReactionManager
* @instance
* @param {MessageReactionResolvable} reaction The MessageReaction to resolve
* @returns {?Snowflake}
*/
/**
* Removes all reactions from a message.
* @returns {Promise<Message>}
*/
async removeAll() {
await this.client.rest.delete(Routes.channelMessageAllReactions(this.message.channelId, this.message.id));
return this.message;
}
}
module.exports = ReactionManager;

View File

@@ -0,0 +1,78 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { ReactionType, Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsError, ErrorCodes } = require('../errors');
const User = require('../structures/User');
/**
* Manages API methods for users who reacted to a reaction and stores their cache.
* @extends {CachedManager}
*/
class ReactionUserManager extends CachedManager {
constructor(reaction, iterable) {
super(reaction.client, User, iterable);
/**
* The reaction that this manager belongs to
* @type {MessageReaction}
*/
this.reaction = reaction;
}
/**
* The cache of this manager
* @type {Collection<Snowflake, User>}
* @name ReactionUserManager#cache
*/
/**
* Options used to fetch users who gave a reaction.
* @typedef {Object} FetchReactionUsersOptions
* @property {ReactionType} [type=ReactionType.Normal] The reaction type to fetch
* @property {number} [limit=100] The maximum amount of users to fetch, defaults to `100`
* @property {Snowflake} [after] Limit fetching users to those with an id greater than the supplied id
*/
/**
* Fetches all the users that gave this reaction. Resolves with a collection of users, mapped by their ids.
* @param {FetchReactionUsersOptions} [options] Options for fetching the users
* @returns {Promise<Collection<Snowflake, User>>}
*/
async fetch({ type = ReactionType.Normal, limit = 100, after } = {}) {
const message = this.reaction.message;
const query = makeURLSearchParams({ limit, after, type });
const data = await this.client.rest.get(
Routes.channelMessageReaction(message.channelId, message.id, this.reaction.emoji.identifier),
{ query },
);
const users = new Collection();
for (const rawUser of data) {
const user = this.client.users._add(rawUser);
this.cache.set(user.id, user);
users.set(user.id, user);
}
return users;
}
/**
* Removes a user from this reaction.
* @param {UserResolvable} [user=this.client.user] The user to remove the reaction of
* @returns {Promise<MessageReaction>}
*/
async remove(user = this.client.user) {
const userId = this.client.users.resolveId(user);
if (!userId) throw new DiscordjsError(ErrorCodes.ReactionResolveUser);
const message = this.reaction.message;
const route =
userId === this.client.user.id
? Routes.channelMessageOwnReaction(message.channelId, message.id, this.reaction.emoji.identifier)
: Routes.channelMessageUserReaction(message.channelId, message.id, this.reaction.emoji.identifier, userId);
await this.client.rest.delete(route);
return this.reaction;
}
}
module.exports = ReactionUserManager;

374
node_modules/discord.js/src/managers/RoleManager.js generated vendored Normal file
View File

@@ -0,0 +1,374 @@
'use strict';
const process = require('node:process');
const { Collection } = require('@discordjs/collection');
const { DiscordAPIError } = require('@discordjs/rest');
const { RESTJSONErrorCodes, Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const { Role } = require('../structures/Role');
const { resolveImage } = require('../util/DataResolver');
const PermissionsBitField = require('../util/PermissionsBitField');
const { setPosition, resolveColor } = require('../util/Util');
let cacheWarningEmitted = false;
/**
* Manages API methods for roles and stores their cache.
* @extends {CachedManager}
*/
class RoleManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, Role, iterable);
if (!cacheWarningEmitted && this._cache.constructor.name !== 'Collection') {
cacheWarningEmitted = true;
process.emitWarning(
`Overriding the cache handling for ${this.constructor.name} is unsupported and breaks functionality.`,
'UnsupportedCacheOverwriteWarning',
);
}
/**
* The guild belonging to this manager
* @type {Guild}
*/
this.guild = guild;
}
/**
* The role cache of this manager
* @type {Collection<Snowflake, Role>}
* @name RoleManager#cache
*/
_add(data, cache) {
return super._add(data, cache, { extras: [this.guild] });
}
/**
* Obtains a role from Discord, or the role cache if they're already available.
* @param {Snowflake} [id] The role's id
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<?Role|Collection<Snowflake, Role>>}
* @example
* // Fetch all roles from the guild
* message.guild.roles.fetch()
* .then(roles => console.log(`There are ${roles.size} roles.`))
* .catch(console.error);
* @example
* // Fetch a single role
* message.guild.roles.fetch('222078108977594368')
* .then(role => console.log(`The role color is: ${role.color}`))
* .catch(console.error);
*/
async fetch(id, { cache = true, force = false } = {}) {
if (!id) {
const data = await this.client.rest.get(Routes.guildRoles(this.guild.id));
const roles = new Collection();
for (const role of data) roles.set(role.id, this._add(role, cache));
return roles;
}
if (!force) {
const existing = this.cache.get(id);
if (existing) return existing;
}
try {
const data = await this.client.rest.get(Routes.guildRole(this.guild.id, id));
return this._add(data, cache);
} catch (error) {
// TODO: Remove this catch in the next major version
if (error instanceof DiscordAPIError && error.code === RESTJSONErrorCodes.UnknownRole) {
return null;
}
throw error;
}
}
/**
* Data that can be resolved to a Role object. This can be:
* * A Role
* * A Snowflake
* @typedef {Role|Snowflake} RoleResolvable
*/
/**
* Resolves a {@link RoleResolvable} to a {@link Role} object.
* @method resolve
* @memberof RoleManager
* @instance
* @param {RoleResolvable} role The role resolvable to resolve
* @returns {?Role}
*/
/**
* Resolves a {@link RoleResolvable} to a {@link Role} id.
* @method resolveId
* @memberof RoleManager
* @instance
* @param {RoleResolvable} role The role resolvable to resolve
* @returns {?Snowflake}
*/
/**
* Options used to create a new role.
* @typedef {Object} RoleCreateOptions
* @property {string} [name] The name of the new role
* @property {ColorResolvable} [color] The data to create the role with
* @property {boolean} [hoist] Whether or not the new role should be hoisted
* @property {PermissionResolvable} [permissions] The permissions for the new role
* @property {number} [position] The position of the new role
* @property {boolean} [mentionable] Whether or not the new role should be mentionable
* @property {?(BufferResolvable|Base64Resolvable|EmojiResolvable)} [icon] The icon for the role
* <warn>The `EmojiResolvable` should belong to the same guild as the role.
* If not, pass the emoji's URL directly</warn>
* @property {?string} [unicodeEmoji] The unicode emoji for the role
* @property {string} [reason] The reason for creating this role
*/
/**
* Creates a new role in the guild with given information.
* <warn>The position will silently reset to 1 if an invalid one is provided, or none.</warn>
* @param {RoleCreateOptions} [options] Options for creating the new role
* @returns {Promise<Role>}
* @example
* // Create a new role
* guild.roles.create()
* .then(console.log)
* .catch(console.error);
* @example
* // Create a new role with data and a reason
* guild.roles.create({
* name: 'Super Cool Blue People',
* color: Colors.Blue,
* reason: 'we needed a role for Super Cool People',
* })
* .then(console.log)
* .catch(console.error);
*/
async create(options = {}) {
let { name, color, hoist, permissions, position, mentionable, reason, icon, unicodeEmoji } = options;
color &&= resolveColor(color);
if (permissions !== undefined) permissions = new PermissionsBitField(permissions);
if (icon) {
const guildEmojiURL = this.guild.emojis.resolve(icon)?.imageURL();
icon = guildEmojiURL ? await resolveImage(guildEmojiURL) : await resolveImage(icon);
if (typeof icon !== 'string') icon = undefined;
}
const data = await this.client.rest.post(Routes.guildRoles(this.guild.id), {
body: {
name,
color,
hoist,
permissions,
mentionable,
icon,
unicode_emoji: unicodeEmoji,
},
reason,
});
const { role } = this.client.actions.GuildRoleCreate.handle({
guild_id: this.guild.id,
role: data,
});
if (position) return this.setPosition(role, position, { reason });
return role;
}
/**
* Options for editing a role
* @typedef {RoleData} RoleEditOptions
* @property {string} [reason] The reason for editing this role
*/
/**
* Edits a role of the guild.
* @param {RoleResolvable} role The role to edit
* @param {RoleEditOptions} options The options to provide
* @returns {Promise<Role>}
* @example
* // Edit a role
* guild.roles.edit('222079219327434752', { name: 'buddies' })
* .then(updated => console.log(`Edited role name to ${updated.name}`))
* .catch(console.error);
*/
async edit(role, options) {
role = this.resolve(role);
if (!role) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'role', 'RoleResolvable');
if (typeof options.position === 'number') {
await this.setPosition(role, options.position, { reason: options.reason });
}
let icon = options.icon;
if (icon) {
const guildEmojiURL = this.guild.emojis.resolve(icon)?.imageURL();
icon = guildEmojiURL ? await resolveImage(guildEmojiURL) : await resolveImage(icon);
if (typeof icon !== 'string') icon = undefined;
}
const body = {
name: options.name,
color: options.color === undefined ? undefined : resolveColor(options.color),
hoist: options.hoist,
permissions: options.permissions === undefined ? undefined : new PermissionsBitField(options.permissions),
mentionable: options.mentionable,
icon,
unicode_emoji: options.unicodeEmoji,
};
const d = await this.client.rest.patch(Routes.guildRole(this.guild.id, role.id), { body, reason: options.reason });
const clone = role._clone();
clone._patch(d);
return clone;
}
/**
* Deletes a role.
* @param {RoleResolvable} role The role to delete
* @param {string} [reason] Reason for deleting the role
* @returns {Promise<void>}
* @example
* // Delete a role
* guild.roles.delete('222079219327434752', 'The role needed to go')
* .then(() => console.log('Deleted the role'))
* .catch(console.error);
*/
async delete(role, reason) {
const id = this.resolveId(role);
await this.client.rest.delete(Routes.guildRole(this.guild.id, id), { reason });
this.client.actions.GuildRoleDelete.handle({ guild_id: this.guild.id, role_id: id });
}
/**
* Sets the new position of the role.
* @param {RoleResolvable} role The role to change the position of
* @param {number} position The new position for the role
* @param {SetRolePositionOptions} [options] Options for setting the position
* @returns {Promise<Role>}
* @example
* // Set the position of the role
* guild.roles.setPosition('222197033908436994', 1)
* .then(updated => console.log(`Role position: ${updated.position}`))
* .catch(console.error);
*/
async setPosition(role, position, { relative, reason } = {}) {
role = this.resolve(role);
if (!role) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'role', 'RoleResolvable');
const updatedRoles = await setPosition(
role,
position,
relative,
this.guild._sortedRoles(),
this.client,
Routes.guildRoles(this.guild.id),
reason,
);
this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: this.guild.id,
roles: updatedRoles,
});
return role;
}
/**
* The data needed for updating a guild role's position
* @typedef {Object} GuildRolePosition
* @property {RoleResolvable} role The role's id
* @property {number} position The position to update
*/
/**
* Batch-updates the guild's role positions
* @param {GuildRolePosition[]} rolePositions Role positions to update
* @returns {Promise<Guild>}
* @example
* guild.roles.setPositions([{ role: roleId, position: updatedRoleIndex }])
* .then(guild => console.log(`Role positions updated for ${guild}`))
* .catch(console.error);
*/
async setPositions(rolePositions) {
// Make sure rolePositions are prepared for API
rolePositions = rolePositions.map(rolePosition => ({
id: this.resolveId(rolePosition.role),
position: rolePosition.position,
}));
// Call the API to update role positions
await this.client.rest.patch(Routes.guildRoles(this.guild.id), { body: rolePositions });
return this.client.actions.GuildRolesPositionUpdate.handle({
guild_id: this.guild.id,
roles: rolePositions,
}).guild;
}
/**
* Compares the positions of two roles.
* @param {RoleResolvable} role1 First role to compare
* @param {RoleResolvable} role2 Second role to compare
* @returns {number} Negative number if the first role's position is lower (second role's is higher),
* positive number if the first's is higher (second's is lower), 0 if equal
*/
comparePositions(role1, role2) {
const resolvedRole1 = this.resolve(role1);
const resolvedRole2 = this.resolve(role2);
if (!resolvedRole1 || !resolvedRole2) {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'role', 'Role nor a Snowflake');
}
const role1Position = resolvedRole1.position;
const role2Position = resolvedRole2.position;
if (role1Position === role2Position) {
return Number(BigInt(resolvedRole2.id) - BigInt(resolvedRole1.id));
}
return role1Position - role2Position;
}
/**
* Gets the managed role a user created when joining the guild, if any
* <info>Only ever available for bots</info>
* @param {UserResolvable} user The user to access the bot role for
* @returns {?Role}
*/
botRoleFor(user) {
const userId = this.client.users.resolveId(user);
if (!userId) return null;
return this.cache.find(role => role.tags?.botId === userId) ?? null;
}
/**
* The `@everyone` role of the guild
* @type {Role}
* @readonly
*/
get everyone() {
return this.cache.get(this.guild.id);
}
/**
* The premium subscriber role of the guild, if any
* @type {?Role}
* @readonly
*/
get premiumSubscriberRole() {
return this.cache.find(role => role.tags?.premiumSubscriberRole) ?? null;
}
/**
* The role with the highest position in the cache
* @type {Role}
* @readonly
*/
get highest() {
return this.cache.reduce((prev, role) => (role.comparePositionTo(prev) > 0 ? role : prev), this.cache.first());
}
}
module.exports = RoleManager;

View File

@@ -0,0 +1,159 @@
'use strict';
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors');
const { StageInstance } = require('../structures/StageInstance');
/**
* Manages API methods for {@link StageInstance} objects and holds their cache.
* @extends {CachedManager}
*/
class StageInstanceManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, StageInstance, iterable);
/**
* The guild this manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of this Manager
* @type {Collection<Snowflake, StageInstance>}
* @name StageInstanceManager#cache
*/
/**
* Options used to create a stage instance.
* @typedef {Object} StageInstanceCreateOptions
* @property {string} topic The topic of the stage instance
* @property {StageInstancePrivacyLevel} [privacyLevel] The privacy level of the stage instance
* @property {boolean} [sendStartNotification] Whether to notify `@everyone` that the stage instance has started
* @property {GuildScheduledEventResolvable} [guildScheduledEvent]
* The guild scheduled event associated with the stage instance
*/
/**
* Data that can be resolved to a Stage Channel object. This can be:
* * A StageChannel
* * A Snowflake
* @typedef {StageChannel|Snowflake} StageChannelResolvable
*/
/**
* Creates a new stage instance.
* @param {StageChannelResolvable} channel The stage channel to associate the created stage instance to
* @param {StageInstanceCreateOptions} options The options to create the stage instance
* @returns {Promise<StageInstance>}
* @example
* // Create a stage instance
* guild.stageInstances.create('1234567890123456789', {
* topic: 'A very creative topic',
* privacyLevel: GuildPrivacyLevel.GuildOnly
* })
* .then(stageInstance => console.log(stageInstance))
* .catch(console.error);
*/
async create(channel, options) {
const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve);
if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
const { guildScheduledEvent, topic, privacyLevel, sendStartNotification } = options;
const guildScheduledEventId = guildScheduledEvent && this.resolveId(guildScheduledEvent);
const data = await this.client.rest.post(Routes.stageInstances(), {
body: {
channel_id: channelId,
topic,
privacy_level: privacyLevel,
send_start_notification: sendStartNotification,
guild_scheduled_event_id: guildScheduledEventId,
},
});
return this._add(data);
}
/**
* Fetches the stage instance associated with a stage channel, if it exists.
* @param {StageChannelResolvable} channel The stage channel whose associated stage instance is to be fetched
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<StageInstance>}
* @example
* // Fetch a stage instance
* guild.stageInstances.fetch('1234567890123456789')
* .then(stageInstance => console.log(stageInstance))
* .catch(console.error);
*/
async fetch(channel, { cache = true, force = false } = {}) {
const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve);
if (!force) {
const existing = this.cache.find(stageInstance => stageInstance.channelId === channelId);
if (existing) return existing;
}
const data = await this.client.rest.get(Routes.stageInstance(channelId));
return this._add(data, cache);
}
/**
* Options used to edit an existing stage instance.
* @typedef {Object} StageInstanceEditOptions
* @property {string} [topic] The new topic of the stage instance
* @property {StageInstancePrivacyLevel} [privacyLevel] The new privacy level of the stage instance
*/
/**
* Edits an existing stage instance.
* @param {StageChannelResolvable} channel The stage channel whose associated stage instance is to be edited
* @param {StageInstanceEditOptions} options The options to edit the stage instance
* @returns {Promise<StageInstance>}
* @example
* // Edit a stage instance
* guild.stageInstances.edit('1234567890123456789', { topic: 'new topic' })
* .then(stageInstance => console.log(stageInstance))
* .catch(console.error);
*/
async edit(channel, options) {
if (typeof options !== 'object') throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true);
const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve);
let { topic, privacyLevel } = options;
const data = await this.client.rest.patch(Routes.stageInstance(channelId), {
body: {
topic,
privacy_level: privacyLevel,
},
});
if (this.cache.has(data.id)) {
const clone = this.cache.get(data.id)._clone();
clone._patch(data);
return clone;
}
return this._add(data);
}
/**
* Deletes an existing stage instance.
* @param {StageChannelResolvable} channel The stage channel whose associated stage instance is to be deleted
* @returns {Promise<void>}
*/
async delete(channel) {
const channelId = this.guild.channels.resolveId(channel);
if (!channelId) throw new DiscordjsError(ErrorCodes.StageChannelResolve);
await this.client.rest.delete(Routes.stageInstance(channelId));
}
}
module.exports = StageInstanceManager;

193
node_modules/discord.js/src/managers/ThreadManager.js generated vendored Normal file
View File

@@ -0,0 +1,193 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const ThreadChannel = require('../structures/ThreadChannel');
const { MakeCacheOverrideSymbol } = require('../util/Symbols');
/**
* Manages API methods for thread-based channels and stores their cache.
* @extends {CachedManager}
*/
class ThreadManager extends CachedManager {
static [MakeCacheOverrideSymbol] = ThreadManager;
constructor(channel, iterable) {
super(channel.client, ThreadChannel, iterable);
/**
* The channel this Manager belongs to
* @type {TextChannel|NewsChannel|ForumChannel|MediaChannel}
*/
this.channel = channel;
}
/**
* Data that can be resolved to a Thread Channel object. This can be:
* * A ThreadChannel object
* * A Snowflake
* @typedef {ThreadChannel|Snowflake} ThreadChannelResolvable
*/
/**
* The cache of this Manager
* @type {Collection<Snowflake, ThreadChannel>}
* @name ThreadManager#cache
*/
_add(thread) {
const existing = this.cache.get(thread.id);
if (existing) return existing;
this.cache.set(thread.id, thread);
return thread;
}
/**
* Resolves a {@link ThreadChannelResolvable} to a {@link ThreadChannel} object.
* @method resolve
* @memberof ThreadManager
* @instance
* @param {ThreadChannelResolvable} thread The ThreadChannel resolvable to resolve
* @returns {?ThreadChannel}
*/
/**
* Resolves a {@link ThreadChannelResolvable} to a {@link ThreadChannel} id.
* @method resolveId
* @memberof ThreadManager
* @instance
* @param {ThreadChannelResolvable} thread The ThreadChannel resolvable to resolve
* @returns {?Snowflake}
*/
/**
* Options for fetching multiple threads.
* @typedef {Object} FetchThreadsOptions
* @property {FetchArchivedThreadOptions} [archived] Options used to fetch archived threads
*/
/**
* Obtains a thread from Discord, or the channel cache if it's already available.
* @param {ThreadChannelResolvable|FetchThreadsOptions} [options] The options to fetch threads. If it is a
* ThreadChannelResolvable then the specified thread will be fetched. Fetches all active threads if `undefined`
* @param {BaseFetchOptions} [cacheOptions] Additional options for this fetch. <warn>The `force` field gets ignored
* if `options` is not a {@link ThreadChannelResolvable}</warn>
* @returns {Promise<?(ThreadChannel|FetchedThreads|FetchedThreadsMore)>}
* {@link FetchedThreads} if active & {@link FetchedThreadsMore} if archived.
* @example
* // Fetch a thread by its id
* channel.threads.fetch('831955138126104859')
* .then(channel => console.log(channel.name))
* .catch(console.error);
*/
fetch(options, { cache, force } = {}) {
if (!options) return this.fetchActive(cache);
const channel = this.client.channels.resolveId(options);
if (channel) return this.client.channels.fetch(channel, { cache, force });
if (options.archived) {
return this.fetchArchived(options.archived, cache);
}
return this.fetchActive(cache);
}
/**
* Data that can be resolved to a Date object. This can be:
* * A Date object
* * A number representing a timestamp
* * An [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) string
* @typedef {Date|number|string} DateResolvable
*/
/**
* The options used to fetch archived threads.
* @typedef {Object} FetchArchivedThreadOptions
* @property {string} [type='public'] The type of threads to fetch (`public` or `private`)
* @property {boolean} [fetchAll=false] Whether to fetch **all** archived threads when `type` is `private`
* <info>This property requires the {@link PermissionFlagsBits.ManageThreads} permission if `true`.</info>
* @property {DateResolvable|ThreadChannelResolvable} [before] Only return threads that were archived before this Date
* or Snowflake
* <warn>Must be a {@link ThreadChannelResolvable} when `type` is `private` and `fetchAll` is `false`.</warn>
* @property {number} [limit] Maximum number of threads to return
*/
/**
* Data returned from fetching multiple threads.
* @typedef {FetchedThreads} FetchedThreadsMore
* @property {?boolean} hasMore Whether there are potentially additional threads that require a subsequent call
*/
/**
* Obtains a set of archived threads from Discord.
* <info>This method requires the {@link PermissionFlagsBits.ReadMessageHistory} permission
* in the parent channel.</info>
* @param {FetchArchivedThreadOptions} [options] The options to fetch archived threads
* @param {boolean} [cache=true] Whether to cache the new thread objects if they aren't already
* @returns {Promise<FetchedThreadsMore>}
*/
async fetchArchived({ type = 'public', fetchAll = false, before, limit } = {}, cache = true) {
let path = Routes.channelThreads(this.channel.id, type);
if (type === 'private' && !fetchAll) {
path = Routes.channelJoinedArchivedThreads(this.channel.id);
}
let timestamp;
let id;
const query = makeURLSearchParams({ limit });
if (before !== undefined) {
if (before instanceof ThreadChannel || /^\d{17,19}$/.test(String(before))) {
id = this.resolveId(before);
timestamp = this.resolve(before)?.archivedAt?.toISOString();
const toUse = type === 'private' && !fetchAll ? id : timestamp;
if (toUse) {
query.set('before', toUse);
}
} else {
try {
timestamp = new Date(before).toISOString();
if (type === 'public' || fetchAll) {
query.set('before', timestamp);
}
} catch {
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'before', 'DateResolvable or ThreadChannelResolvable');
}
}
}
const raw = await this.client.rest.get(path, { query });
return this.constructor._mapThreads(raw, this.client, { parent: this.channel, cache });
}
/**
* Obtains all active threads in the channel.
* @param {boolean} [cache=true] Whether to cache the fetched data
* @returns {Promise<FetchedThreads>}
*/
async fetchActive(cache = true) {
const data = await this.channel.guild.channels.rawFetchGuildActiveThreads();
return this.constructor._mapThreads(data, this.client, { parent: this.channel, cache });
}
static _mapThreads(rawThreads, client, { parent, guild, cache }) {
const threads = rawThreads.threads.reduce((coll, raw) => {
const thread = client.channels._add(raw, guild ?? parent?.guild, { cache });
if (parent && thread.parentId !== parent.id) return coll;
return coll.set(thread.id, thread);
}, new Collection());
// Discord sends the thread id as id in this object
const threadMembers = rawThreads.members.reduce((coll, raw) => {
const thread = threads.get(raw.id);
return thread ? coll.set(raw.user_id, thread.members._add(raw)) : coll;
}, new Collection());
const response = { threads, members: threadMembers };
// The GET `/guilds/{guild.id}/threads/active` route does not return `has_more`.
if ('has_more' in rawThreads) response.hasMore = rawThreads.has_more;
return response;
}
}
module.exports = ThreadManager;

View File

@@ -0,0 +1,184 @@
'use strict';
const { Collection } = require('@discordjs/collection');
const { makeURLSearchParams } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsTypeError, ErrorCodes } = require('../errors');
const ThreadMember = require('../structures/ThreadMember');
/**
* Manages API methods for GuildMembers and stores their cache.
* @extends {CachedManager}
*/
class ThreadMemberManager extends CachedManager {
constructor(thread, iterable) {
super(thread.client, ThreadMember, iterable);
/**
* The thread this manager belongs to
* @type {ThreadChannel}
*/
this.thread = thread;
}
/**
* The cache of this Manager
* @type {Collection<Snowflake, ThreadMember>}
* @name ThreadMemberManager#cache
*/
_add(data, cache = true) {
const existing = this.cache.get(data.user_id);
if (cache) existing?._patch(data, { cache });
if (existing) return existing;
const member = new ThreadMember(this.thread, data, { cache });
if (cache) this.cache.set(data.user_id, member);
return member;
}
/**
* Fetches the client user as a ThreadMember of the thread.
* @param {BaseFetchOptions} [options] The options for fetching the member
* @returns {Promise<ThreadMember>}
*/
fetchMe(options) {
return this.fetch({ ...options, member: this.client.user.id });
}
/**
* The client user as a ThreadMember of this ThreadChannel
* @type {?ThreadMember}
* @readonly
*/
get me() {
return this.resolve(this.client.user.id);
}
/**
* Data that resolves to give a ThreadMember object. This can be:
* * A ThreadMember object
* * A User resolvable
* @typedef {ThreadMember|UserResolvable} ThreadMemberResolvable
*/
/**
* Resolves a {@link ThreadMemberResolvable} to a {@link ThreadMember} object.
* @param {ThreadMemberResolvable} member The user that is part of the thread
* @returns {?GuildMember}
*/
resolve(member) {
const memberResolvable = super.resolve(member);
if (memberResolvable) return memberResolvable;
const userResolvable = this.client.users.resolveId(member);
if (userResolvable) return super.resolve(userResolvable);
return null;
}
/**
* Resolves a {@link ThreadMemberResolvable} to a {@link ThreadMember} id string.
* @param {ThreadMemberResolvable} member The user that is part of the guild
* @returns {?Snowflake}
*/
resolveId(member) {
const memberResolvable = super.resolveId(member);
if (memberResolvable) return memberResolvable;
const userResolvable = this.client.users.resolveId(member);
return this.cache.has(userResolvable) ? userResolvable : null;
}
/**
* Adds a member to the thread.
* @param {UserResolvable|'@me'} member The member to add
* @param {string} [reason] The reason for adding this member
* @returns {Promise<Snowflake>}
*/
async add(member, reason) {
const id = member === '@me' ? member : this.client.users.resolveId(member);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'member', 'UserResolvable');
await this.client.rest.put(Routes.threadMembers(this.thread.id, id), { reason });
return id;
}
/**
* Remove a user from the thread.
* @param {UserResolvable|'@me'} member The member to remove
* @param {string} [reason] The reason for removing this member from the thread
* @returns {Promise<Snowflake>}
*/
async remove(member, reason) {
const id = member === '@me' ? member : this.client.users.resolveId(member);
if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'member', 'UserResolvable');
await this.client.rest.delete(Routes.threadMembers(this.thread.id, id), { reason });
return id;
}
/**
* Options used to fetch a thread member.
* @typedef {BaseFetchOptions} FetchThreadMemberOptions
* @property {ThreadMemberResolvable} member The thread member to fetch
* @property {boolean} [withMember] Whether to also return the guild member associated with this thread member
*/
/**
* Options used to fetch multiple thread members with guild member data.
* <info>With `withMember` set to `true`, pagination is enabled.</info>
* @typedef {Object} FetchThreadMembersWithGuildMemberDataOptions
* @property {true} withMember Whether to also return the guild member data
* @property {Snowflake} [after] Consider only thread members after this id
* @property {number} [limit] The maximum number of thread members to return
* @property {boolean} [cache] Whether to cache the fetched thread members and guild members
*/
/**
* Options used to fetch multiple thread members without guild member data.
* @typedef {Object} FetchThreadMembersWithoutGuildMemberDataOptions
* @property {false} [withMember] Whether to also return the guild member data
* @property {boolean} [cache] Whether to cache the fetched thread members
*/
/**
* Options used to fetch multiple thread members.
* @typedef {FetchThreadMembersWithGuildMemberDataOptions|
* FetchThreadMembersWithoutGuildMemberDataOptions} FetchThreadMembersOptions
*/
/**
* Fetches thread member(s) from Discord.
* <info>This method requires the {@link GatewayIntentBits.GuildMembers} privileged gateway intent.</info>
* @param {ThreadMemberResolvable|FetchThreadMemberOptions|FetchThreadMembersOptions} [options]
* Options for fetching thread member(s)
* @returns {Promise<ThreadMember|Collection<Snowflake, ThreadMember>>}
*/
fetch(options) {
if (!options) return this._fetchMany();
const { member, withMember, cache, force } = options;
const resolvedMember = this.resolveId(member ?? options);
if (resolvedMember) return this._fetchSingle({ member: resolvedMember, withMember, cache, force });
return this._fetchMany(options);
}
async _fetchSingle({ member, withMember, cache, force = false }) {
if (!force) {
const existing = this.cache.get(member);
if (existing) return existing;
}
const data = await this.client.rest.get(Routes.threadMembers(this.thread.id, member), {
query: makeURLSearchParams({ with_member: withMember }),
});
return this._add(data, cache);
}
async _fetchMany({ withMember, after, limit, cache } = {}) {
const data = await this.client.rest.get(Routes.threadMembers(this.thread.id), {
query: makeURLSearchParams({ with_member: withMember, after, limit }),
});
return data.reduce((col, member) => col.set(member.user_id, this._add(member, cache)), new Collection());
}
}
module.exports = ThreadMemberManager;

142
node_modules/discord.js/src/managers/UserManager.js generated vendored Normal file
View File

@@ -0,0 +1,142 @@
'use strict';
const { ChannelType, Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const { DiscordjsError, ErrorCodes } = require('../errors');
const { GuildMember } = require('../structures/GuildMember');
const { Message } = require('../structures/Message');
const ThreadMember = require('../structures/ThreadMember');
const User = require('../structures/User');
/**
* Manages API methods for users and stores their cache.
* @extends {CachedManager}
*/
class UserManager extends CachedManager {
constructor(client, iterable) {
super(client, User, iterable);
}
/**
* The cache of this manager
* @type {Collection<Snowflake, User>}
* @name UserManager#cache
*/
/**
* Data that resolves to give a User object. This can be:
* * A User object
* * A Snowflake
* * A Message object (resolves to the message author)
* * A GuildMember object
* * A ThreadMember object
* @typedef {User|Snowflake|Message|GuildMember|ThreadMember} UserResolvable
*/
/**
* The DM between the client's user and a user
* @param {Snowflake} userId The user id
* @returns {?DMChannel}
* @private
*/
dmChannel(userId) {
return (
this.client.channels.cache.find(channel => channel.type === ChannelType.DM && channel.recipientId === userId) ??
null
);
}
/**
* Creates a {@link DMChannel} between the client and a user.
* @param {UserResolvable} user The UserResolvable to identify
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<DMChannel>}
*/
async createDM(user, { cache = true, force = false } = {}) {
const id = this.resolveId(user);
if (!force) {
const dmChannel = this.dmChannel(id);
if (dmChannel && !dmChannel.partial) return dmChannel;
}
const data = await this.client.rest.post(Routes.userChannels(), { body: { recipient_id: id } });
return this.client.channels._add(data, null, { cache });
}
/**
* Deletes a {@link DMChannel} (if one exists) between the client and a user. Resolves with the channel if successful.
* @param {UserResolvable} user The UserResolvable to identify
* @returns {Promise<DMChannel>}
*/
async deleteDM(user) {
const id = this.resolveId(user);
const dmChannel = this.dmChannel(id);
if (!dmChannel) throw new DiscordjsError(ErrorCodes.UserNoDMChannel);
await this.client.rest.delete(Routes.channel(dmChannel.id));
this.client.channels._remove(dmChannel.id);
return dmChannel;
}
/**
* Obtains a user from Discord, or the user cache if it's already available.
* @param {UserResolvable} user The user to fetch
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<User>}
*/
async fetch(user, { cache = true, force = false } = {}) {
const id = this.resolveId(user);
if (!force) {
const existing = this.cache.get(id);
if (existing && !existing.partial) return existing;
}
const data = await this.client.rest.get(Routes.user(id));
return this._add(data, cache);
}
/**
* Fetches a user's flags.
* @param {UserResolvable} user The UserResolvable to identify
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<UserFlagsBitField>}
*/
async fetchFlags(user, options) {
return (await this.fetch(user, options)).flags;
}
/**
* Sends a message to a user.
* @param {UserResolvable} user The UserResolvable to identify
* @param {string|MessagePayload|MessageCreateOptions} options The options to provide
* @returns {Promise<Message>}
*/
async send(user, options) {
return (await this.createDM(user)).send(options);
}
/**
* Resolves a {@link UserResolvable} to a {@link User} object.
* @param {UserResolvable} user The UserResolvable to identify
* @returns {?User}
*/
resolve(user) {
if (user instanceof GuildMember || user instanceof ThreadMember) return user.user;
if (user instanceof Message) return user.author;
return super.resolve(user);
}
/**
* Resolves a {@link UserResolvable} to a {@link User} id.
* @param {UserResolvable} user The UserResolvable to identify
* @returns {?Snowflake}
*/
resolveId(user) {
if (user instanceof ThreadMember) return user.id;
if (user instanceof GuildMember) return user.user.id;
if (user instanceof Message) return user.author.id;
return super.resolveId(user);
}
}
module.exports = UserManager;

View File

@@ -0,0 +1,59 @@
'use strict';
const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager');
const VoiceState = require('../structures/VoiceState');
/**
* Manages API methods for VoiceStates and stores their cache.
* @extends {CachedManager}
*/
class VoiceStateManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, VoiceState, iterable);
/**
* The guild this manager belongs to
* @type {Guild}
*/
this.guild = guild;
}
/**
* The cache of this manager
* @type {Collection<Snowflake, VoiceState>}
* @name VoiceStateManager#cache
*/
_add(data, cache = true) {
const existing = this.cache.get(data.user_id);
if (existing) return existing._patch(data);
const entry = new this.holds(this.guild, data);
if (cache) this.cache.set(data.user_id, entry);
return entry;
}
/**
* Obtains a user's voice state from discord or from the cache if it's already available.
* @param {GuildMemberResolvable|'@me'} member The member whose voice state is to be fetched
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<VoiceState>}
* @example
* // Fetch a member's voice state
* guild.voiceStates.fetch("66564597481480192")
* .then(console.log)
* .catch(console.error);
*/
async fetch(member, { cache = true, force = false } = {}) {
const id = member === '@me' ? member : this.guild.members.resolveId(member);
if (!force) {
const existing = this.cache.get(id === '@me' ? this.client.user.id : id);
if (existing) return existing;
}
const data = await this.client.rest.get(Routes.guildVoiceState(this.guild.id, id));
return this._add(data, cache);
}
}
module.exports = VoiceStateManager;