Merge pull request #28 from Tropicallism/main
I hope it will work fine :))
This commit is contained in:
commit
5caadfe8da
@ -1,223 +1,223 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const ApplicationCommand = require('../structures/ApplicationCommand');
|
||||
const { ApplicationCommandTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Manages API methods for application commands and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class ApplicationCommandManager extends CachedManager {
|
||||
constructor(client, iterable, user) {
|
||||
super(client, ApplicationCommand, iterable);
|
||||
|
||||
/**
|
||||
* The manager for permissions of arbitrary commands on arbitrary guilds
|
||||
* @type {ApplicationCommandPermissionsManager}
|
||||
*/
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this, user);
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {Object}
|
||||
* @private
|
||||
*/
|
||||
commandPath({ id, guildId } = {}) {
|
||||
let path = this.client.api.applications(this.user.id);
|
||||
if (this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
|
||||
return id ? path.commands(id) : path.commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that resolves to give an ApplicationCommand object. This can be:
|
||||
* * An ApplicationCommand object
|
||||
* * A Snowflake
|
||||
* @typedef {ApplicationCommand|Snowflake} ApplicationCommandResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtains one or multiple application commands from Discord, or the cache if it's already available.
|
||||
* @param {Snowflake} [id] The application command's id
|
||||
* @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 } = {}) {
|
||||
await this.user.createDM().catch(() => {});
|
||||
if (typeof id === 'object') {
|
||||
({ guildId, cache = true } = id);
|
||||
} else if (id) {
|
||||
if (!force) {
|
||||
const existing = this.cache.get(id);
|
||||
if (existing) return existing;
|
||||
}
|
||||
const command = await this.commandPath({ id, guildId }).get();
|
||||
return this._add(command, cache);
|
||||
}
|
||||
|
||||
const data = await this.commandPath({ guildId }).get();
|
||||
return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an application command.
|
||||
* @param {ApplicationCommandData|APIApplicationCommand} 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const data = await this.commandPath({ guildId }).post({
|
||||
data: this.constructor.transformCommand(command),
|
||||
});
|
||||
return this._add(data, true, guildId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all the commands for this application or guild.
|
||||
* @param {ApplicationCommandData[]|APIApplicationCommand[]} 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const data = await this.commandPath({ guildId }).put({
|
||||
data: commands.map(c => this.constructor.transformCommand(c)),
|
||||
});
|
||||
return data.reduce((coll, command) => coll.set(command.id, this._add(command, true, guildId)), new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an application command.
|
||||
* @param {ApplicationCommandResolvable} command The command to edit
|
||||
* @param {ApplicationCommandData|APIApplicationCommand} 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
const patched = await this.commandPath({ id, guildId }).patch({
|
||||
data: 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
await this.commandPath({ id, guildId }).delete();
|
||||
|
||||
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 {ApplicationCommandData|APIApplicationCommand} command The command to transform
|
||||
* @returns {APIApplicationCommand}
|
||||
* @private
|
||||
*/
|
||||
static transformCommand(command) {
|
||||
return {
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
type: typeof command.type === 'number' ? command.type : ApplicationCommandTypes[command.type],
|
||||
options: command.options?.map(o => ApplicationCommand.transformOption(o)),
|
||||
default_permission: command.defaultPermission ?? command.default_permission,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApplicationCommandManager;
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { TypeError } = require('../errors');
|
||||
const ApplicationCommand = require('../structures/ApplicationCommand');
|
||||
const { ApplicationCommandTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Manages API methods for application commands and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class ApplicationCommandManager extends CachedManager {
|
||||
constructor(client, iterable, user) {
|
||||
super(client, ApplicationCommand, iterable);
|
||||
|
||||
/**
|
||||
* The manager for permissions of arbitrary commands on arbitrary guilds
|
||||
* @type {ApplicationCommandPermissionsManager}
|
||||
*/
|
||||
this.permissions = new ApplicationCommandPermissionsManager(this, user);
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {Object}
|
||||
* @private
|
||||
*/
|
||||
commandPath({ id, guildId } = {}) {
|
||||
let path = this.client.api.applications(this.user.id);
|
||||
if (this.guild ?? guildId) path = path.guilds(this.guild?.id ?? guildId);
|
||||
return id ? path.commands(id) : path.commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that resolves to give an ApplicationCommand object. This can be:
|
||||
* * An ApplicationCommand object
|
||||
* * A Snowflake
|
||||
* @typedef {ApplicationCommand|Snowflake} ApplicationCommandResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtains one or multiple application commands from Discord, or the cache if it's already available.
|
||||
* @param {Snowflake} [id] The application command's id
|
||||
* @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 } = {}) {
|
||||
await this.user.createDM().catch(() => {});
|
||||
if (typeof id === 'object') {
|
||||
({ guildId, cache = true } = id);
|
||||
} else if (id) {
|
||||
if (!force) {
|
||||
const existing = this.cache.get(id);
|
||||
if (existing) return existing;
|
||||
}
|
||||
const command = await this.commandPath({ id, guildId }).get();
|
||||
return this._add(command, cache);
|
||||
}
|
||||
|
||||
const data = await this.commandPath({ guildId }).get();
|
||||
return data.reduce((coll, command) => coll.set(command.id, this._add(command, cache, guildId)), new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an application command.
|
||||
* @param {ApplicationCommandData|APIApplicationCommand} 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const data = await this.commandPath({ guildId }).post({
|
||||
data: this.constructor.transformCommand(command),
|
||||
});
|
||||
return this._add(data, true, guildId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all the commands for this application or guild.
|
||||
* @param {ApplicationCommandData[]|APIApplicationCommand[]} 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const data = await this.commandPath({ guildId }).put({
|
||||
data: commands.map(c => this.constructor.transformCommand(c)),
|
||||
});
|
||||
return data.reduce((coll, command) => coll.set(command.id, this._add(command, true, guildId)), new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits an application command.
|
||||
* @param {ApplicationCommandResolvable} command The command to edit
|
||||
* @param {ApplicationCommandData|APIApplicationCommand} 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
const patched = await this.commandPath({ id, guildId }).patch({
|
||||
data: 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) {
|
||||
if(!this.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const id = this.resolveId(command);
|
||||
if (!id) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
await this.commandPath({ id, guildId }).delete();
|
||||
|
||||
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 {ApplicationCommandData|APIApplicationCommand} command The command to transform
|
||||
* @returns {APIApplicationCommand}
|
||||
* @private
|
||||
*/
|
||||
static transformCommand(command) {
|
||||
return {
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
type: typeof command.type === 'number' ? command.type : ApplicationCommandTypes[command.type],
|
||||
options: command.options?.map(o => ApplicationCommand.transformOption(o)),
|
||||
default_permission: command.defaultPermission ?? command.default_permission,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApplicationCommandManager;
|
||||
|
@ -1,422 +1,422 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const BaseManager = require('./BaseManager');
|
||||
const { Error, TypeError } = require('../errors');
|
||||
const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Manages API methods for permissions of Application Commands.
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
constructor(manager, user) {
|
||||
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;
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {Object}
|
||||
* @private
|
||||
*/
|
||||
permissionsPath(guildId, commandId) {
|
||||
return this.client.api.applications(typeof this.user == 'string' ? this.user : this.user.id).guilds(guildId).commands(commandId).permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for setting the permissions of an application command.
|
||||
* @typedef {Object} ApplicationCommandPermissionData
|
||||
* @property {Snowflake} id The role or user's id
|
||||
* @property {ApplicationCommandPermissionType|number} 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* The object returned when fetching permissions for an application command.
|
||||
* @typedef {Object} ApplicationCommandPermissions
|
||||
* @property {Snowflake} id The role or user's id
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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 permissions for ${perms.length} users`))
|
||||
* .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);
|
||||
*/
|
||||
async fetch({ guild, command } = {}) {
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (commandId) {
|
||||
const data = await this.permissionsPath(guildId, commandId).get();
|
||||
return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
|
||||
}
|
||||
|
||||
const data = await this.permissionsPath(guildId).get();
|
||||
return data.reduce(
|
||||
(coll, perm) =>
|
||||
coll.set(
|
||||
perm.id,
|
||||
perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
|
||||
),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data used for overwriting the permissions for all application commands in a guild.
|
||||
* @typedef {Object} GuildApplicationCommandPermissionData
|
||||
* @property {Snowflake} id The command's id
|
||||
* @property {ApplicationCommandPermissionData[]} permissions The permissions for this command
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options used to set permissions for one or more Application Commands in a guild
|
||||
* <warn>One of `command` AND `permissions`, OR `fullPermissions` is required.
|
||||
* `fullPermissions` is not a valid option when passing to a manager where `commandId` is non-null</warn>
|
||||
* @typedef {BaseApplicationCommandPermissionsOptions} SetApplicationCommandPermissionsOptions
|
||||
* @property {ApplicationCommandPermissionData[]} [permissions] The new permissions for the command
|
||||
* @property {GuildApplicationCommandPermissionData[]} [fullPermissions] The new permissions for all commands
|
||||
* in a guild <warn>When this parameter is set, `permissions` and `command` are ignored</warn>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the permissions for one or more commands.
|
||||
* @param {SetApplicationCommandPermissionsOptions} options Options used to set permissions
|
||||
* @returns {Promise<ApplicationCommandPermissions[]|Collection<Snowflake, ApplicationCommandPermissions[]>>}
|
||||
* @example
|
||||
* // Set the permissions for one command
|
||||
* client.application.commands.permissions.set({ guild: '892455839386304532', command: '123456789012345678',
|
||||
* permissions: [
|
||||
* {
|
||||
* id: '876543210987654321',
|
||||
* type: 'USER',
|
||||
* permission: false,
|
||||
* },
|
||||
* ]})
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Set the permissions for all commands
|
||||
* guild.commands.permissions.set({ fullPermissions: [
|
||||
* {
|
||||
* id: '123456789012345678',
|
||||
* permissions: [{
|
||||
* id: '876543210987654321',
|
||||
* type: 'USER',
|
||||
* permission: false,
|
||||
* }],
|
||||
* },
|
||||
* ]})
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async set({ guild, command, permissions, fullPermissions } = {}) {
|
||||
if(!this.manager.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
|
||||
if (commandId) {
|
||||
if (!Array.isArray(permissions)) {
|
||||
throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true);
|
||||
}
|
||||
const data = await this.permissionsPath(guildId, commandId).put({
|
||||
data: { permissions: permissions.map(perm => this.constructor.transformPermissions(perm)) },
|
||||
});
|
||||
return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
|
||||
}
|
||||
|
||||
if (!Array.isArray(fullPermissions)) {
|
||||
throw new TypeError('INVALID_TYPE', 'fullPermissions', 'Array of GuildApplicationCommandPermissionData', true);
|
||||
}
|
||||
|
||||
const APIPermissions = [];
|
||||
for (const perm of fullPermissions) {
|
||||
if (!Array.isArray(perm.permissions)) throw new TypeError('INVALID_ELEMENT', 'Array', 'fullPermissions', perm);
|
||||
APIPermissions.push({
|
||||
id: perm.id,
|
||||
permissions: perm.permissions.map(p => this.constructor.transformPermissions(p)),
|
||||
});
|
||||
}
|
||||
const data = await this.permissionsPath(guildId).put({
|
||||
data: APIPermissions,
|
||||
});
|
||||
return data.reduce(
|
||||
(coll, perm) =>
|
||||
coll.set(
|
||||
perm.id,
|
||||
perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
|
||||
),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to add permissions to a command
|
||||
* <warn>The `command` parameter is not optional when the managers `commandId` is `null`</warn>
|
||||
* @typedef {BaseApplicationCommandPermissionsOptions} AddApplicationCommandPermissionsOptions
|
||||
* @property {ApplicationCommandPermissionData[]} permissions The permissions to add to the command
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add permissions to a command.
|
||||
* @param {AddApplicationCommandPermissionsOptions} options Options used to add permissions
|
||||
* @returns {Promise<ApplicationCommandPermissions[]>}
|
||||
* @example
|
||||
* // Block a role from the command permissions
|
||||
* guild.commands.permissions.add({ command: '123456789012345678', permissions: [
|
||||
* {
|
||||
* id: '876543211234567890',
|
||||
* type: 'ROLE',
|
||||
* permission: false
|
||||
* },
|
||||
* ]})
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async add({ guild, command, permissions }) {
|
||||
if(!this.manager.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
if (!Array.isArray(permissions)) {
|
||||
throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true);
|
||||
}
|
||||
|
||||
let existing = [];
|
||||
try {
|
||||
existing = await this.fetch({ guild: guildId, command: commandId });
|
||||
} catch (error) {
|
||||
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
|
||||
}
|
||||
|
||||
const newPermissions = permissions.slice();
|
||||
for (const perm of existing) {
|
||||
if (!newPermissions.some(x => x.id === perm.id)) {
|
||||
newPermissions.push(perm);
|
||||
}
|
||||
}
|
||||
|
||||
return this.set({ guild: guildId, command: commandId, permissions: newPermissions });
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to remove permissions from a command
|
||||
* <warn>The `command` parameter is not optional when the managers `commandId` is `null`</warn>
|
||||
* @typedef {BaseApplicationCommandPermissionsOptions} RemoveApplicationCommandPermissionsOptions
|
||||
* @property {UserResolvable|UserResolvable[]} [users] The user(s) to remove from the command permissions
|
||||
* <warn>One of `users` or `roles` is required</warn>
|
||||
* @property {RoleResolvable|RoleResolvable[]} [roles] The role(s) to remove from the command permissions
|
||||
* <warn>One of `users` or `roles` is required</warn>
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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' })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Remove multiple roles from this command
|
||||
* guild.commands.permissions.remove({
|
||||
* command: '123456789012345678', roles: ['876543210123456789', '765432101234567890']
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async remove({ guild, command, users, roles }) {
|
||||
if(!this.manager.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
|
||||
|
||||
let resolvedIds = [];
|
||||
if (Array.isArray(users)) {
|
||||
users.forEach(user => {
|
||||
const userId = this.client.users.resolveId(user);
|
||||
if (!userId) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', user);
|
||||
resolvedIds.push(userId);
|
||||
});
|
||||
} else if (users) {
|
||||
const userId = this.client.users.resolveId(users);
|
||||
if (!userId) {
|
||||
throw new TypeError('INVALID_TYPE', 'users', 'Array or UserResolvable');
|
||||
}
|
||||
resolvedIds.push(userId);
|
||||
}
|
||||
|
||||
if (Array.isArray(roles)) {
|
||||
roles.forEach(role => {
|
||||
if (typeof role === 'string') {
|
||||
resolvedIds.push(role);
|
||||
return;
|
||||
}
|
||||
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
|
||||
const roleId = this.guild.roles.resolveId(role);
|
||||
if (!roleId) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', role);
|
||||
resolvedIds.push(roleId);
|
||||
});
|
||||
} else if (roles) {
|
||||
if (typeof roles === 'string') {
|
||||
resolvedIds.push(roles);
|
||||
} else {
|
||||
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
|
||||
const roleId = this.guild.roles.resolveId(roles);
|
||||
if (!roleId) {
|
||||
throw new TypeError('INVALID_TYPE', 'users', 'Array or RoleResolvable');
|
||||
}
|
||||
resolvedIds.push(roleId);
|
||||
}
|
||||
}
|
||||
|
||||
let existing = [];
|
||||
try {
|
||||
existing = await this.fetch({ guild: guildId, command: commandId });
|
||||
} catch (error) {
|
||||
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
|
||||
}
|
||||
|
||||
const permissions = existing.filter(perm => !resolvedIds.includes(perm.id));
|
||||
|
||||
return this.set({ guild: guildId, command: commandId, permissions });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {UserResolvable|RoleResolvable} permissionId The user or role to check if a permission exists for
|
||||
* on this command.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether a permission exists for a user or role
|
||||
* @param {AddApplicationCommandPermissionsOptions} options Options used to check permissions
|
||||
* @returns {Promise<boolean>}
|
||||
* @example
|
||||
* // Check whether a user has permission to use a command
|
||||
* guild.commands.permissions.has({ command: '123456789012345678', permissionId: '876543210123456789' })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async has({ guild, command, permissionId }) {
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
if (!permissionId) throw new TypeError('INVALID_TYPE', 'permissionId', 'UserResolvable or RoleResolvable');
|
||||
let resolvedId = permissionId;
|
||||
if (typeof permissionId !== 'string') {
|
||||
resolvedId = this.client.users.resolveId(permissionId);
|
||||
if (!resolvedId) {
|
||||
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
|
||||
resolvedId = this.guild.roles.resolveId(permissionId);
|
||||
}
|
||||
if (!resolvedId) {
|
||||
throw new TypeError('INVALID_TYPE', 'permissionId', 'UserResolvable or RoleResolvable');
|
||||
}
|
||||
}
|
||||
|
||||
let existing = [];
|
||||
try {
|
||||
existing = await this.fetch({ guild: guildId, command: commandId });
|
||||
} catch (error) {
|
||||
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
|
||||
}
|
||||
|
||||
return existing.some(perm => perm.id === resolvedId);
|
||||
}
|
||||
|
||||
_validateOptions(guild, command) {
|
||||
const guildId = this.guildId ?? this.client.guilds.resolveId(guild);
|
||||
if (!guildId) throw new Error('GLOBAL_COMMAND_PERMISSIONS');
|
||||
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 TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable', true);
|
||||
}
|
||||
}
|
||||
return { guildId, commandId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an {@link ApplicationCommandPermissionData} object into something that can be used with the API.
|
||||
* @param {ApplicationCommandPermissionData} permissions The permissions to transform
|
||||
* @param {boolean} [received] Whether these permissions have been received from Discord
|
||||
* @returns {APIApplicationCommandPermissions}
|
||||
* @private
|
||||
*/
|
||||
static transformPermissions(permissions, received) {
|
||||
return {
|
||||
id: permissions.id,
|
||||
permission: permissions.permission,
|
||||
type:
|
||||
typeof permissions.type === 'number' && !received
|
||||
? permissions.type
|
||||
: ApplicationCommandPermissionTypes[permissions.type],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApplicationCommandPermissionsManager;
|
||||
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* @external APIApplicationCommandPermissions
|
||||
* @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure}
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const BaseManager = require('./BaseManager');
|
||||
const { Error, TypeError } = require('../errors');
|
||||
const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Manages API methods for permissions of Application Commands.
|
||||
* @extends {BaseManager}
|
||||
*/
|
||||
class ApplicationCommandPermissionsManager extends BaseManager {
|
||||
constructor(manager, user) {
|
||||
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;
|
||||
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {Object}
|
||||
* @private
|
||||
*/
|
||||
permissionsPath(guildId, commandId) {
|
||||
return this.client.api.applications(typeof this.user == 'string' ? this.user : this.user.id).guilds(guildId).commands(commandId).permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for setting the permissions of an application command.
|
||||
* @typedef {Object} ApplicationCommandPermissionData
|
||||
* @property {Snowflake} id The role or user's id
|
||||
* @property {ApplicationCommandPermissionType|number} 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* The object returned when fetching permissions for an application command.
|
||||
* @typedef {Object} ApplicationCommandPermissions
|
||||
* @property {Snowflake} id The role or user's id
|
||||
* @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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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 permissions for ${perms.length} users`))
|
||||
* .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);
|
||||
*/
|
||||
async fetch({ guild, command } = {}) {
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (commandId) {
|
||||
const data = await this.permissionsPath(guildId, commandId).get();
|
||||
return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
|
||||
}
|
||||
|
||||
const data = await this.permissionsPath(guildId).get();
|
||||
return data.reduce(
|
||||
(coll, perm) =>
|
||||
coll.set(
|
||||
perm.id,
|
||||
perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
|
||||
),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data used for overwriting the permissions for all application commands in a guild.
|
||||
* @typedef {Object} GuildApplicationCommandPermissionData
|
||||
* @property {Snowflake} id The command's id
|
||||
* @property {ApplicationCommandPermissionData[]} permissions The permissions for this command
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options used to set permissions for one or more Application Commands in a guild
|
||||
* <warn>One of `command` AND `permissions`, OR `fullPermissions` is required.
|
||||
* `fullPermissions` is not a valid option when passing to a manager where `commandId` is non-null</warn>
|
||||
* @typedef {BaseApplicationCommandPermissionsOptions} SetApplicationCommandPermissionsOptions
|
||||
* @property {ApplicationCommandPermissionData[]} [permissions] The new permissions for the command
|
||||
* @property {GuildApplicationCommandPermissionData[]} [fullPermissions] The new permissions for all commands
|
||||
* in a guild <warn>When this parameter is set, `permissions` and `command` are ignored</warn>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the permissions for one or more commands.
|
||||
* @param {SetApplicationCommandPermissionsOptions} options Options used to set permissions
|
||||
* @returns {Promise<ApplicationCommandPermissions[]|Collection<Snowflake, ApplicationCommandPermissions[]>>}
|
||||
* @example
|
||||
* // Set the permissions for one command
|
||||
* client.application.commands.permissions.set({ guild: '892455839386304532', command: '123456789012345678',
|
||||
* permissions: [
|
||||
* {
|
||||
* id: '876543210987654321',
|
||||
* type: 'USER',
|
||||
* permission: false,
|
||||
* },
|
||||
* ]})
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Set the permissions for all commands
|
||||
* guild.commands.permissions.set({ fullPermissions: [
|
||||
* {
|
||||
* id: '123456789012345678',
|
||||
* permissions: [{
|
||||
* id: '876543210987654321',
|
||||
* type: 'USER',
|
||||
* permission: false,
|
||||
* }],
|
||||
* },
|
||||
* ]})
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async set({ guild, command, permissions, fullPermissions } = {}) {
|
||||
if(!this.manager.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
|
||||
if (commandId) {
|
||||
if (!Array.isArray(permissions)) {
|
||||
throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true);
|
||||
}
|
||||
const data = await this.permissionsPath(guildId, commandId).put({
|
||||
data: { permissions: permissions.map(perm => this.constructor.transformPermissions(perm)) },
|
||||
});
|
||||
return data.permissions.map(perm => this.constructor.transformPermissions(perm, true));
|
||||
}
|
||||
|
||||
if (!Array.isArray(fullPermissions)) {
|
||||
throw new TypeError('INVALID_TYPE', 'fullPermissions', 'Array of GuildApplicationCommandPermissionData', true);
|
||||
}
|
||||
|
||||
const APIPermissions = [];
|
||||
for (const perm of fullPermissions) {
|
||||
if (!Array.isArray(perm.permissions)) throw new TypeError('INVALID_ELEMENT', 'Array', 'fullPermissions', perm);
|
||||
APIPermissions.push({
|
||||
id: perm.id,
|
||||
permissions: perm.permissions.map(p => this.constructor.transformPermissions(p)),
|
||||
});
|
||||
}
|
||||
const data = await this.permissionsPath(guildId).put({
|
||||
data: APIPermissions,
|
||||
});
|
||||
return data.reduce(
|
||||
(coll, perm) =>
|
||||
coll.set(
|
||||
perm.id,
|
||||
perm.permissions.map(p => this.constructor.transformPermissions(p, true)),
|
||||
),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to add permissions to a command
|
||||
* <warn>The `command` parameter is not optional when the managers `commandId` is `null`</warn>
|
||||
* @typedef {BaseApplicationCommandPermissionsOptions} AddApplicationCommandPermissionsOptions
|
||||
* @property {ApplicationCommandPermissionData[]} permissions The permissions to add to the command
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add permissions to a command.
|
||||
* @param {AddApplicationCommandPermissionsOptions} options Options used to add permissions
|
||||
* @returns {Promise<ApplicationCommandPermissions[]>}
|
||||
* @example
|
||||
* // Block a role from the command permissions
|
||||
* guild.commands.permissions.add({ command: '123456789012345678', permissions: [
|
||||
* {
|
||||
* id: '876543211234567890',
|
||||
* type: 'ROLE',
|
||||
* permission: false
|
||||
* },
|
||||
* ]})
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async add({ guild, command, permissions }) {
|
||||
if(!this.manager.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
if (!Array.isArray(permissions)) {
|
||||
throw new TypeError('INVALID_TYPE', 'permissions', 'Array of ApplicationCommandPermissionData', true);
|
||||
}
|
||||
|
||||
let existing = [];
|
||||
try {
|
||||
existing = await this.fetch({ guild: guildId, command: commandId });
|
||||
} catch (error) {
|
||||
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
|
||||
}
|
||||
|
||||
const newPermissions = permissions.slice();
|
||||
for (const perm of existing) {
|
||||
if (!newPermissions.some(x => x.id === perm.id)) {
|
||||
newPermissions.push(perm);
|
||||
}
|
||||
}
|
||||
|
||||
return this.set({ guild: guildId, command: commandId, permissions: newPermissions });
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to remove permissions from a command
|
||||
* <warn>The `command` parameter is not optional when the managers `commandId` is `null`</warn>
|
||||
* @typedef {BaseApplicationCommandPermissionsOptions} RemoveApplicationCommandPermissionsOptions
|
||||
* @property {UserResolvable|UserResolvable[]} [users] The user(s) to remove from the command permissions
|
||||
* <warn>One of `users` or `roles` is required</warn>
|
||||
* @property {RoleResolvable|RoleResolvable[]} [roles] The role(s) to remove from the command permissions
|
||||
* <warn>One of `users` or `roles` is required</warn>
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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' })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Remove multiple roles from this command
|
||||
* guild.commands.permissions.remove({
|
||||
* command: '123456789012345678', roles: ['876543210123456789', '765432101234567890']
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async remove({ guild, command, users, roles }) {
|
||||
if(!this.manager.client.user.bot) throw new Error("INVALID_USER_METHOD");
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
if (!users && !roles) throw new TypeError('INVALID_TYPE', 'users OR roles', 'Array or Resolvable', true);
|
||||
|
||||
let resolvedIds = [];
|
||||
if (Array.isArray(users)) {
|
||||
users.forEach(user => {
|
||||
const userId = this.client.users.resolveId(user);
|
||||
if (!userId) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', user);
|
||||
resolvedIds.push(userId);
|
||||
});
|
||||
} else if (users) {
|
||||
const userId = this.client.users.resolveId(users);
|
||||
if (!userId) {
|
||||
throw new TypeError('INVALID_TYPE', 'users', 'Array or UserResolvable');
|
||||
}
|
||||
resolvedIds.push(userId);
|
||||
}
|
||||
|
||||
if (Array.isArray(roles)) {
|
||||
roles.forEach(role => {
|
||||
if (typeof role === 'string') {
|
||||
resolvedIds.push(role);
|
||||
return;
|
||||
}
|
||||
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
|
||||
const roleId = this.guild.roles.resolveId(role);
|
||||
if (!roleId) throw new TypeError('INVALID_ELEMENT', 'Array', 'users', role);
|
||||
resolvedIds.push(roleId);
|
||||
});
|
||||
} else if (roles) {
|
||||
if (typeof roles === 'string') {
|
||||
resolvedIds.push(roles);
|
||||
} else {
|
||||
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
|
||||
const roleId = this.guild.roles.resolveId(roles);
|
||||
if (!roleId) {
|
||||
throw new TypeError('INVALID_TYPE', 'users', 'Array or RoleResolvable');
|
||||
}
|
||||
resolvedIds.push(roleId);
|
||||
}
|
||||
}
|
||||
|
||||
let existing = [];
|
||||
try {
|
||||
existing = await this.fetch({ guild: guildId, command: commandId });
|
||||
} catch (error) {
|
||||
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
|
||||
}
|
||||
|
||||
const permissions = existing.filter(perm => !resolvedIds.includes(perm.id));
|
||||
|
||||
return this.set({ guild: guildId, command: commandId, permissions });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {UserResolvable|RoleResolvable} permissionId The user or role to check if a permission exists for
|
||||
* on this command.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether a permission exists for a user or role
|
||||
* @param {AddApplicationCommandPermissionsOptions} options Options used to check permissions
|
||||
* @returns {Promise<boolean>}
|
||||
* @example
|
||||
* // Check whether a user has permission to use a command
|
||||
* guild.commands.permissions.has({ command: '123456789012345678', permissionId: '876543210123456789' })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async has({ guild, command, permissionId }) {
|
||||
const { guildId, commandId } = this._validateOptions(guild, command);
|
||||
if (!commandId) throw new TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable');
|
||||
|
||||
if (!permissionId) throw new TypeError('INVALID_TYPE', 'permissionId', 'UserResolvable or RoleResolvable');
|
||||
let resolvedId = permissionId;
|
||||
if (typeof permissionId !== 'string') {
|
||||
resolvedId = this.client.users.resolveId(permissionId);
|
||||
if (!resolvedId) {
|
||||
if (!this.guild) throw new Error('GUILD_UNCACHED_ROLE_RESOLVE');
|
||||
resolvedId = this.guild.roles.resolveId(permissionId);
|
||||
}
|
||||
if (!resolvedId) {
|
||||
throw new TypeError('INVALID_TYPE', 'permissionId', 'UserResolvable or RoleResolvable');
|
||||
}
|
||||
}
|
||||
|
||||
let existing = [];
|
||||
try {
|
||||
existing = await this.fetch({ guild: guildId, command: commandId });
|
||||
} catch (error) {
|
||||
if (error.code !== APIErrors.UNKNOWN_APPLICATION_COMMAND_PERMISSIONS) throw error;
|
||||
}
|
||||
|
||||
return existing.some(perm => perm.id === resolvedId);
|
||||
}
|
||||
|
||||
_validateOptions(guild, command) {
|
||||
const guildId = this.guildId ?? this.client.guilds.resolveId(guild);
|
||||
if (!guildId) throw new Error('GLOBAL_COMMAND_PERMISSIONS');
|
||||
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 TypeError('INVALID_TYPE', 'command', 'ApplicationCommandResolvable', true);
|
||||
}
|
||||
}
|
||||
return { guildId, commandId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms an {@link ApplicationCommandPermissionData} object into something that can be used with the API.
|
||||
* @param {ApplicationCommandPermissionData} permissions The permissions to transform
|
||||
* @param {boolean} [received] Whether these permissions have been received from Discord
|
||||
* @returns {APIApplicationCommandPermissions}
|
||||
* @private
|
||||
*/
|
||||
static transformPermissions(permissions, received) {
|
||||
return {
|
||||
id: permissions.id,
|
||||
permission: permissions.permission,
|
||||
type:
|
||||
typeof permissions.type === 'number' && !received
|
||||
? permissions.type
|
||||
: ApplicationCommandPermissionTypes[permissions.type],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ApplicationCommandPermissionsManager;
|
||||
|
||||
/* eslint-disable max-len */
|
||||
/**
|
||||
* @external APIApplicationCommandPermissions
|
||||
* @see {@link https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure}
|
||||
*/
|
||||
|
@ -1,75 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
const CachedManager = require('./CachedManager');
|
||||
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 BlockedManager extends CachedManager {
|
||||
constructor(client, iterable) {
|
||||
super(client, User, iterable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, User>}
|
||||
* @name BlockedManager#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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.api.users(id).get();
|
||||
return this._add(data, cache);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlockedManager;
|
||||
'use strict';
|
||||
|
||||
const CachedManager = require('./CachedManager');
|
||||
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 BlockedManager extends CachedManager {
|
||||
constructor(client, iterable) {
|
||||
super(client, User, iterable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, User>}
|
||||
* @name BlockedManager#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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.api.users(id).get();
|
||||
return this._add(data, cache);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlockedManager;
|
||||
|
@ -1,364 +1,367 @@
|
||||
'use strict';
|
||||
const { default: Collection } = require('@discordjs/collection');
|
||||
const { Error, TypeError } = require('../errors/DJSError');
|
||||
const { remove } = require('lodash');
|
||||
const { localeObject, DMScanLevel, stickerAnimationMode } = require('../util/Constants')
|
||||
/**
|
||||
* Manages API methods for users.
|
||||
*/
|
||||
class ClientUserSettingManager {
|
||||
constructor(client, iterable) {
|
||||
this.client = client;
|
||||
// Raw data
|
||||
this.rawSetting = {};
|
||||
// Language
|
||||
this.locale = null;
|
||||
// Setting => ACTIVITY SETTINGS => Activity Status => Display current activity as a status message
|
||||
this.activityDisplay = null;
|
||||
//
|
||||
this.disableDMfromServer = new Collection();
|
||||
// Allow direct messages from server members
|
||||
this.DMfromServerMode = null;
|
||||
//
|
||||
this.displayImage = null;
|
||||
//
|
||||
this.linkedImageDisplay = null;
|
||||
// Setting => APP SETTINGS => Accessibility => Automatically play GIFs when Discord is focused.
|
||||
this.autoplayGIF = null;
|
||||
// Show embeds and preview website links pasted into chat
|
||||
this.previewLink = null;
|
||||
// Setting => APP SETTINGS => Accessibility => Play Animated Emojis
|
||||
this.animatedEmojis = null;
|
||||
// Setting => APP SETTINGS => Accessibility => Text-to-speech => Allow playback
|
||||
this.allowTTS = null;
|
||||
// Setting => APP SETTINGS => Appearance => Message Display => Compact Mode [OK]
|
||||
this.compactMode = null;
|
||||
// Setting => APP SETTINGS => Text & Images => Emoji => Convert Emoticons
|
||||
this.convertEmoticons = null;
|
||||
// SAFE DIRECT MESSAGING
|
||||
this.DMScanLevel = null;
|
||||
// Setting => APP SETTINGS => Appearance => Theme [OK]
|
||||
this.theme = '';
|
||||
//
|
||||
this.developerMode = null;
|
||||
//
|
||||
this.afkTimeout = null;
|
||||
//
|
||||
this.stickerAnimationMode = null;
|
||||
// WHO CAN ADD YOU AS A FRIEND ?
|
||||
this.addFriendFrom = {
|
||||
all: null,
|
||||
mutual_friends: null,
|
||||
mutual_guilds: null,
|
||||
};
|
||||
// Setting => APP SETTINGS => Text & Images => Emoji => Show emoji reactions
|
||||
this.showEmojiReactions = null;
|
||||
// Custom Stauts [It's not working now]
|
||||
this.customStatus = null;
|
||||
// Guild folder and position
|
||||
this.guildMetadata = new Collection();
|
||||
// Todo: add new method from Discum
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Object} data Raw Data to patch
|
||||
* @extends https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/discum/user/user.py
|
||||
* @private
|
||||
*/
|
||||
_patch(data) {
|
||||
this.rawSetting = data;
|
||||
if ('locale' in data) {
|
||||
this.locale = localeObject[data.locale];
|
||||
}
|
||||
if ('show_current_game' in data) {
|
||||
this.activityDisplay = data.show_current_game;
|
||||
}
|
||||
if ('default_guilds_restricted' in data) {
|
||||
this.DMfromServerMode = data.default_guilds_restricted;
|
||||
}
|
||||
if ('inline_attachment_media' in data) {
|
||||
this.displayImage = data.inline_attachment_media;
|
||||
}
|
||||
if ('inline_embed_media' in data) {
|
||||
this.linkedImageDisplay = data.inline_embed_media;
|
||||
}
|
||||
if ('gif_auto_play' in data) {
|
||||
this.autoplayGIF = data.gif_auto_play;
|
||||
}
|
||||
if ('render_embeds' in data) {
|
||||
this.previewLink = data.render_embeds;
|
||||
}
|
||||
if ('animate_emoji' in data) {
|
||||
this.animatedEmojis = data.animate_emoji;
|
||||
}
|
||||
if ('enable_tts_command' in data) {
|
||||
this.allowTTS = data.enable_tts_command;
|
||||
}
|
||||
if ('message_display_compact' in data) {
|
||||
this.compactMode = data.message_display_compact;
|
||||
}
|
||||
if ('convert_emoticons' in data) {
|
||||
this.convertEmoticons = data.convert_emoticons;
|
||||
}
|
||||
if ('explicit_content_filter' in data) {
|
||||
this.DMScanLevel = DMScanLevel[data.explicit_content_filter];
|
||||
}
|
||||
if ('theme' in data) {
|
||||
this.theme = data.theme;
|
||||
}
|
||||
if ('developer_mode' in data) {
|
||||
this.developerMode = data.developer_mode;
|
||||
}
|
||||
if ('afk_timeout' in data) {
|
||||
this.afkTimeout = data.afk_timeout * 1000; // second => milisecond
|
||||
}
|
||||
if ('animate_stickers' in data) {
|
||||
this.stickerAnimationMode = stickerAnimationMode[data.animate_stickers];
|
||||
}
|
||||
if ('render_reactions' in data) {
|
||||
this.showEmojiReactions = data.render_reactions;
|
||||
}
|
||||
if ('custom_status' in data) {
|
||||
this.customStatus = data.custom_status || {}; // Thanks PinkDuwc._#3443 reported this issue
|
||||
this.customStatus.status = data.status;
|
||||
}
|
||||
if ('friend_source_flags' in data) {
|
||||
this.addFriendFrom = {
|
||||
all: data.friend_source_flags.all || false,
|
||||
mutual_friends:
|
||||
data.friend_source_flags.all ? true : data.friend_source_flags.mutual_friends,
|
||||
mutual_guilds:
|
||||
data.friend_source_flags.all ? true : data.friend_source_flags.mutual_guilds,
|
||||
};
|
||||
}
|
||||
if ('guild_folders' in data) {
|
||||
const data_ = data.guild_positions.map((guildId, i) => {
|
||||
// Find folder
|
||||
const folderIndex = data.guild_folders.findIndex((obj) =>
|
||||
obj.guild_ids.includes(guildId),
|
||||
);
|
||||
const metadata = {
|
||||
guildId: guildId,
|
||||
guildIndex: i,
|
||||
folderId: data.guild_folders[folderIndex]?.id,
|
||||
folderIndex,
|
||||
folderName: data.guild_folders[folderIndex]?.name,
|
||||
folderColor: data.guild_folders[folderIndex]?.color,
|
||||
folderGuilds: data.guild_folders[folderIndex]?.guild_ids,
|
||||
};
|
||||
return [guildId, metadata];
|
||||
});
|
||||
this.guildMetadata = new Collection(data_);
|
||||
}
|
||||
if ('restricted_guilds' in data) {
|
||||
data.restricted_guilds.map((guildId) => {
|
||||
const guild = this.client.guilds.cache.get(guildId);
|
||||
if (!guild) return;
|
||||
guild.disableDM = true;
|
||||
this.disableDMfromServer.set(guildId, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
async fetch() {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
try {
|
||||
const data = await this.client.api.users('@me').settings.get();
|
||||
this._patch(data);
|
||||
return this;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Edit data
|
||||
* @param {Object} data Data to edit
|
||||
* @private
|
||||
*/
|
||||
async edit(data) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
try {
|
||||
const res = await this.client.api.users('@me').settings.patch({ data });
|
||||
this._patch(res);
|
||||
return this;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set compact mode
|
||||
* @param {Boolean | null} value Compact mode enable or disable
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
async setDisplayCompactMode(value) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
if (
|
||||
typeof value !== 'boolean' &&
|
||||
typeof value !== 'null' &&
|
||||
typeof value !== 'undefined'
|
||||
)
|
||||
throw new TypeError(
|
||||
'INVALID_TYPE',
|
||||
'value',
|
||||
'boolean | null | undefined',
|
||||
true,
|
||||
);
|
||||
if (!value) value = !this.compactMode;
|
||||
if (value !== this.compactMode) {
|
||||
await this.edit({ message_display_compact: value });
|
||||
}
|
||||
return this.compactMode;
|
||||
}
|
||||
/**
|
||||
* Discord Theme
|
||||
* @param {null |dark |light} value Theme to set
|
||||
* @returns {theme}
|
||||
*/
|
||||
async setTheme(value) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
const validValues = ['dark', 'light'];
|
||||
if (
|
||||
typeof value !== 'string' &&
|
||||
typeof value !== 'null' &&
|
||||
typeof value !== 'undefined'
|
||||
)
|
||||
throw new TypeError(
|
||||
'INVALID_TYPE',
|
||||
'value',
|
||||
'string | null | undefined',
|
||||
true,
|
||||
);
|
||||
if (!validValues.includes(value)) {
|
||||
value == validValues[0]
|
||||
? (value = validValues[1])
|
||||
: (value = validValues[0]);
|
||||
}
|
||||
if (value !== this.theme) {
|
||||
await this.edit({ theme: value });
|
||||
}
|
||||
return this.theme;
|
||||
}
|
||||
/**
|
||||
* * Locale Setting, must be one of:
|
||||
* * `DANISH`
|
||||
* * `GERMAN`
|
||||
* * `ENGLISH_UK`
|
||||
* * `ENGLISH_US`
|
||||
* * `SPANISH`
|
||||
* * `FRENCH`
|
||||
* * `CROATIAN`
|
||||
* * `ITALIAN`
|
||||
* * `LITHUANIAN`
|
||||
* * `HUNGARIAN`
|
||||
* * `DUTCH`
|
||||
* * `NORWEGIAN`
|
||||
* * `POLISH`
|
||||
* * `BRAZILIAN_PORTUGUESE`
|
||||
* * `ROMANIA_ROMANIAN`
|
||||
* * `FINNISH`
|
||||
* * `SWEDISH`
|
||||
* * `VIETNAMESE`
|
||||
* * `TURKISH`
|
||||
* * `CZECH`
|
||||
* * `GREEK`
|
||||
* * `BULGARIAN`
|
||||
* * `RUSSIAN`
|
||||
* * `UKRAINIAN`
|
||||
* * `HINDI`
|
||||
* * `THAI`
|
||||
* * `CHINA_CHINESE`
|
||||
* * `JAPANESE`
|
||||
* * `TAIWAN_CHINESE`
|
||||
* * `KOREAN`
|
||||
* @param {string} value
|
||||
* @returns {locale}
|
||||
*/
|
||||
async setLocale(value) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
if (typeof value !== 'string')
|
||||
throw new TypeError('INVALID_TYPE', 'value', 'string', true);
|
||||
if (!localeObject[value]) throw new Error('INVALID_LOCALE');
|
||||
if (localeObject[value] !== this.locale) {
|
||||
await this.edit({ locale: localeObject[value] });
|
||||
}
|
||||
return this.locale;
|
||||
}
|
||||
// TODO: Guild positions & folders
|
||||
// Change Index in Array [Hidden]
|
||||
/**
|
||||
*
|
||||
* @param {Array} array Array
|
||||
* @param {Number} from Index1
|
||||
* @param {Number} to Index2
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
_move(array, from, to) {
|
||||
array.splice(to, 0, array.splice(from, 1)[0]);
|
||||
return array;
|
||||
}
|
||||
// TODO: Move Guild
|
||||
// folder to folder
|
||||
// folder to home
|
||||
// home to home
|
||||
// home to folder
|
||||
/**
|
||||
* Change Guild Position (from * to Folder or Home)
|
||||
* @param {GuildIDResolve} guildId guild.id
|
||||
* @param {Number} newPosition Guild Position
|
||||
* * **WARNING**: Type = `FOLDER`, newPosition is the guild's index in the Folder.
|
||||
* @param {number} type Move to folder or home
|
||||
* * `FOLDER`: 1
|
||||
* * `HOME`: 2
|
||||
* @param {FolderID} folderId If you want to move to folder
|
||||
* @private
|
||||
*/
|
||||
async guildChangePosition(guildId, newPosition, type, folderId) {
|
||||
// get Guild default position
|
||||
// Escape
|
||||
const oldGuildFolderPosition = this.rawSetting.guild_folders.findIndex(
|
||||
(value) => value.guild_ids.includes(guildId),
|
||||
);
|
||||
const newGuildFolderPosition = this.rawSetting.guild_folders.findIndex(
|
||||
(value) =>
|
||||
value.guild_ids.includes(this.rawSetting.guild_positions[newPosition]),
|
||||
);
|
||||
if (type == 2 || `${type}`.toUpperCase() == 'HOME') {
|
||||
// Delete GuildID from Folder and create new Folder
|
||||
// Check it is folder
|
||||
const folder = this.rawSetting.guild_folders[oldGuildFolderPosition];
|
||||
if (folder.id) {
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids =
|
||||
this.rawSetting.guild_folders[
|
||||
oldGuildFolderPosition
|
||||
].guild_ids.filter((v) => v !== guildId);
|
||||
}
|
||||
this.rawSetting.guild_folders = this._move(
|
||||
this.rawSetting.guild_folders,
|
||||
oldGuildFolderPosition,
|
||||
newGuildFolderPosition,
|
||||
);
|
||||
this.rawSetting.guild_folders[newGuildFolderPosition].id = null;
|
||||
} else if (type == 1 || `${type}`.toUpperCase() == 'FOLDER') {
|
||||
// Delete GuildID from oldFolder
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids =
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids.filter(
|
||||
(v) => v !== guildId,
|
||||
);
|
||||
// Index new Folder
|
||||
const folderIndex = this.rawSetting.guild_folders.findIndex(
|
||||
(value) => value.id == folderId,
|
||||
);
|
||||
const folder = this.rawSetting.guild_folders[folderIndex];
|
||||
folder.guild_ids.push(guildId);
|
||||
folder.guild_ids = [...new Set(folder.guild_ids)];
|
||||
folder.guild_ids = this._move(
|
||||
folder.guild_ids,
|
||||
folder.guild_ids.findIndex((v) => v == guildId),
|
||||
newPosition,
|
||||
);
|
||||
}
|
||||
this.edit({ guild_folders: this.rawSetting.guild_folders });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUserSettingManager;
|
||||
'use strict';
|
||||
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { default: Collection } = require('@discordjs/collection');
|
||||
const { Error, TypeError } = require('../errors/DJSError');
|
||||
const { remove } = require('lodash');
|
||||
const { localeObject, DMScanLevel, stickerAnimationMode } = require('../util/Constants')
|
||||
/**
|
||||
* Manages API methods for users and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class ClientUserSettingManager extends CachedManager {
|
||||
constructor(client, iterable) {
|
||||
super(client);
|
||||
// Raw data
|
||||
this.rawSetting = {};
|
||||
// Language
|
||||
this.locale = null;
|
||||
// Setting => ACTIVITY SETTINGS => Activity Status => Display current activity as a status message
|
||||
this.activityDisplay = null;
|
||||
//
|
||||
this.disableDMfromServer = new Collection();
|
||||
// Allow direct messages from server members
|
||||
this.DMfromServerMode = null;
|
||||
//
|
||||
this.displayImage = null;
|
||||
//
|
||||
this.linkedImageDisplay = null;
|
||||
// Setting => APP SETTINGS => Accessibility => Automatically play GIFs when Discord is focused.
|
||||
this.autoplayGIF = null;
|
||||
// Show embeds and preview website links pasted into chat
|
||||
this.previewLink = null;
|
||||
// Setting => APP SETTINGS => Accessibility => Play Animated Emojis
|
||||
this.animatedEmojis = null;
|
||||
// Setting => APP SETTINGS => Accessibility => Text-to-speech => Allow playback
|
||||
this.allowTTS = null;
|
||||
// Setting => APP SETTINGS => Appearance => Message Display => Compact Mode [OK]
|
||||
this.compactMode = null;
|
||||
// Setting => APP SETTINGS => Text & Images => Emoji => Convert Emoticons
|
||||
this.convertEmoticons = null;
|
||||
// SAFE DIRECT MESSAGING
|
||||
this.DMScanLevel = null;
|
||||
// Setting => APP SETTINGS => Appearance => Theme [OK]
|
||||
this.theme = '';
|
||||
//
|
||||
this.developerMode = null;
|
||||
//
|
||||
this.afkTimeout = null;
|
||||
//
|
||||
this.stickerAnimationMode = null;
|
||||
// WHO CAN ADD YOU AS A FRIEND ?
|
||||
this.addFriendFrom = {
|
||||
all: null,
|
||||
mutual_friends: null,
|
||||
mutual_guilds: null,
|
||||
};
|
||||
// Setting => APP SETTINGS => Text & Images => Emoji => Show emoji reactions
|
||||
this.showEmojiReactions = null;
|
||||
// Custom Stauts [It's not working now]
|
||||
this.customStatus = null;
|
||||
// Guild folder and position
|
||||
this.guildMetadata = new Collection();
|
||||
// Todo: add new method from Discum
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {Object} data Raw Data to patch
|
||||
* @extends https://github.com/Merubokkusu/Discord-S.C.U.M/blob/master/discum/user/user.py
|
||||
* @private
|
||||
*/
|
||||
_patch(data) {
|
||||
this.rawSetting = data;
|
||||
if ('locale' in data) {
|
||||
this.locale = localeObject[data.locale];
|
||||
}
|
||||
if ('show_current_game' in data) {
|
||||
this.activityDisplay = data.show_current_game;
|
||||
}
|
||||
if ('default_guilds_restricted' in data) {
|
||||
this.DMfromServerMode = data.default_guilds_restricted;
|
||||
}
|
||||
if ('inline_attachment_media' in data) {
|
||||
this.displayImage = data.inline_attachment_media;
|
||||
}
|
||||
if ('inline_embed_media' in data) {
|
||||
this.linkedImageDisplay = data.inline_embed_media;
|
||||
}
|
||||
if ('gif_auto_play' in data) {
|
||||
this.autoplayGIF = data.gif_auto_play;
|
||||
}
|
||||
if ('render_embeds' in data) {
|
||||
this.previewLink = data.render_embeds;
|
||||
}
|
||||
if ('animate_emoji' in data) {
|
||||
this.animatedEmojis = data.animate_emoji;
|
||||
}
|
||||
if ('enable_tts_command' in data) {
|
||||
this.allowTTS = data.enable_tts_command;
|
||||
}
|
||||
if ('message_display_compact' in data) {
|
||||
this.compactMode = data.message_display_compact;
|
||||
}
|
||||
if ('convert_emoticons' in data) {
|
||||
this.convertEmoticons = data.convert_emoticons;
|
||||
}
|
||||
if ('explicit_content_filter' in data) {
|
||||
this.DMScanLevel = DMScanLevel[data.explicit_content_filter];
|
||||
}
|
||||
if ('theme' in data) {
|
||||
this.theme = data.theme;
|
||||
}
|
||||
if ('developer_mode' in data) {
|
||||
this.developerMode = data.developer_mode;
|
||||
}
|
||||
if ('afk_timeout' in data) {
|
||||
this.afkTimeout = data.afk_timeout * 1000; // second => milisecond
|
||||
}
|
||||
if ('animate_stickers' in data) {
|
||||
this.stickerAnimationMode = stickerAnimationMode[data.animate_stickers];
|
||||
}
|
||||
if ('render_reactions' in data) {
|
||||
this.showEmojiReactions = data.render_reactions;
|
||||
}
|
||||
if ('custom_status' in data) {
|
||||
this.customStatus = data.custom_status || {}; // Thanks PinkDuwc._#3443 reported this issue
|
||||
this.customStatus.status = data.status;
|
||||
}
|
||||
if ('friend_source_flags' in data) {
|
||||
this.addFriendFrom = {
|
||||
all: data.friend_source_flags.all || false,
|
||||
mutual_friends:
|
||||
data.friend_source_flags.all ? true : data.friend_source_flags.mutual_friends,
|
||||
mutual_guilds:
|
||||
data.friend_source_flags.all ? true : data.friend_source_flags.mutual_guilds,
|
||||
};
|
||||
}
|
||||
if ('guild_folders' in data) {
|
||||
const data_ = data.guild_positions.map((guildId, i) => {
|
||||
// Find folder
|
||||
const folderIndex = data.guild_folders.findIndex((obj) =>
|
||||
obj.guild_ids.includes(guildId),
|
||||
);
|
||||
const metadata = {
|
||||
guildId: guildId,
|
||||
guildIndex: i,
|
||||
folderId: data.guild_folders[folderIndex]?.id,
|
||||
folderIndex,
|
||||
folderName: data.guild_folders[folderIndex]?.name,
|
||||
folderColor: data.guild_folders[folderIndex]?.color,
|
||||
folderGuilds: data.guild_folders[folderIndex]?.guild_ids,
|
||||
};
|
||||
return [guildId, metadata];
|
||||
});
|
||||
this.guildMetadata = new Collection(data_);
|
||||
}
|
||||
if ('restricted_guilds' in data) {
|
||||
data.restricted_guilds.map((guildId) => {
|
||||
const guild = this.client.guilds.cache.get(guildId);
|
||||
if (!guild) return;
|
||||
guild.disableDM = true;
|
||||
this.disableDMfromServer.set(guildId, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
async fetch() {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
try {
|
||||
const data = await this.client.api.users('@me').settings.get();
|
||||
this._patch(data);
|
||||
return this;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Edit data
|
||||
* @param {Object} data Data to edit
|
||||
* @private
|
||||
*/
|
||||
async edit(data) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
try {
|
||||
const res = await this.client.api.users('@me').settings.patch({ data });
|
||||
this._patch(res);
|
||||
return this;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set compact mode
|
||||
* @param {Boolean | null} value Compact mode enable or disable
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
async setDisplayCompactMode(value) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
if (
|
||||
typeof value !== 'boolean' &&
|
||||
typeof value !== 'null' &&
|
||||
typeof value !== 'undefined'
|
||||
)
|
||||
throw new TypeError(
|
||||
'INVALID_TYPE',
|
||||
'value',
|
||||
'boolean | null | undefined',
|
||||
true,
|
||||
);
|
||||
if (!value) value = !this.compactMode;
|
||||
if (value !== this.compactMode) {
|
||||
await this.edit({ message_display_compact: value });
|
||||
}
|
||||
return this.compactMode;
|
||||
}
|
||||
/**
|
||||
* Discord Theme
|
||||
* @param {null |dark |light} value Theme to set
|
||||
* @returns {theme}
|
||||
*/
|
||||
async setTheme(value) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
const validValues = ['dark', 'light'];
|
||||
if (
|
||||
typeof value !== 'string' &&
|
||||
typeof value !== 'null' &&
|
||||
typeof value !== 'undefined'
|
||||
)
|
||||
throw new TypeError(
|
||||
'INVALID_TYPE',
|
||||
'value',
|
||||
'string | null | undefined',
|
||||
true,
|
||||
);
|
||||
if (!validValues.includes(value)) {
|
||||
value == validValues[0]
|
||||
? (value = validValues[1])
|
||||
: (value = validValues[0]);
|
||||
}
|
||||
if (value !== this.theme) {
|
||||
await this.edit({ theme: value });
|
||||
}
|
||||
return this.theme;
|
||||
}
|
||||
/**
|
||||
* * Locale Setting, must be one of:
|
||||
* * `DANISH`
|
||||
* * `GERMAN`
|
||||
* * `ENGLISH_UK`
|
||||
* * `ENGLISH_US`
|
||||
* * `SPANISH`
|
||||
* * `FRENCH`
|
||||
* * `CROATIAN`
|
||||
* * `ITALIAN`
|
||||
* * `LITHUANIAN`
|
||||
* * `HUNGARIAN`
|
||||
* * `DUTCH`
|
||||
* * `NORWEGIAN`
|
||||
* * `POLISH`
|
||||
* * `BRAZILIAN_PORTUGUESE`
|
||||
* * `ROMANIA_ROMANIAN`
|
||||
* * `FINNISH`
|
||||
* * `SWEDISH`
|
||||
* * `VIETNAMESE`
|
||||
* * `TURKISH`
|
||||
* * `CZECH`
|
||||
* * `GREEK`
|
||||
* * `BULGARIAN`
|
||||
* * `RUSSIAN`
|
||||
* * `UKRAINIAN`
|
||||
* * `HINDI`
|
||||
* * `THAI`
|
||||
* * `CHINA_CHINESE`
|
||||
* * `JAPANESE`
|
||||
* * `TAIWAN_CHINESE`
|
||||
* * `KOREAN`
|
||||
* @param {string} value
|
||||
* @returns {locale}
|
||||
*/
|
||||
async setLocale(value) {
|
||||
if (this.client.bot) throw new Error('INVALID_BOT_METHOD');
|
||||
if (typeof value !== 'string')
|
||||
throw new TypeError('INVALID_TYPE', 'value', 'string', true);
|
||||
if (!localeObject[value]) throw new Error('INVALID_LOCALE');
|
||||
if (localeObject[value] !== this.locale) {
|
||||
await this.edit({ locale: localeObject[value] });
|
||||
}
|
||||
return this.locale;
|
||||
}
|
||||
// TODO: Guild positions & folders
|
||||
// Change Index in Array [Hidden]
|
||||
/**
|
||||
*
|
||||
* @param {Array} array Array
|
||||
* @param {Number} from Index1
|
||||
* @param {Number} to Index2
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
_move(array, from, to) {
|
||||
array.splice(to, 0, array.splice(from, 1)[0]);
|
||||
return array;
|
||||
}
|
||||
// TODO: Move Guild
|
||||
// folder to folder
|
||||
// folder to home
|
||||
// home to home
|
||||
// home to folder
|
||||
/**
|
||||
* Change Guild Position (from * to Folder or Home)
|
||||
* @param {GuildIDResolve} guildId guild.id
|
||||
* @param {Number} newPosition Guild Position
|
||||
* * **WARNING**: Type = `FOLDER`, newPosition is the guild's index in the Folder.
|
||||
* @param {number} type Move to folder or home
|
||||
* * `FOLDER`: 1
|
||||
* * `HOME`: 2
|
||||
* @param {FolderID} folderId If you want to move to folder
|
||||
* @private
|
||||
*/
|
||||
async guildChangePosition(guildId, newPosition, type, folderId) {
|
||||
// get Guild default position
|
||||
// Escape
|
||||
const oldGuildFolderPosition = this.rawSetting.guild_folders.findIndex(
|
||||
(value) => value.guild_ids.includes(guildId),
|
||||
);
|
||||
const newGuildFolderPosition = this.rawSetting.guild_folders.findIndex(
|
||||
(value) =>
|
||||
value.guild_ids.includes(this.rawSetting.guild_positions[newPosition]),
|
||||
);
|
||||
if (type == 2 || `${type}`.toUpperCase() == 'HOME') {
|
||||
// Delete GuildID from Folder and create new Folder
|
||||
// Check it is folder
|
||||
const folder = this.rawSetting.guild_folders[oldGuildFolderPosition];
|
||||
if (folder.id) {
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids =
|
||||
this.rawSetting.guild_folders[
|
||||
oldGuildFolderPosition
|
||||
].guild_ids.filter((v) => v !== guildId);
|
||||
}
|
||||
this.rawSetting.guild_folders = this._move(
|
||||
this.rawSetting.guild_folders,
|
||||
oldGuildFolderPosition,
|
||||
newGuildFolderPosition,
|
||||
);
|
||||
this.rawSetting.guild_folders[newGuildFolderPosition].id = null;
|
||||
} else if (type == 1 || `${type}`.toUpperCase() == 'FOLDER') {
|
||||
// Delete GuildID from oldFolder
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids =
|
||||
this.rawSetting.guild_folders[oldGuildFolderPosition].guild_ids.filter(
|
||||
(v) => v !== guildId,
|
||||
);
|
||||
// Index new Folder
|
||||
const folderIndex = this.rawSetting.guild_folders.findIndex(
|
||||
(value) => value.id == folderId,
|
||||
);
|
||||
const folder = this.rawSetting.guild_folders[folderIndex];
|
||||
folder.guild_ids.push(guildId);
|
||||
folder.guild_ids = [...new Set(folder.guild_ids)];
|
||||
folder.guild_ids = this._move(
|
||||
folder.guild_ids,
|
||||
folder.guild_ids.findIndex((v) => v == guildId),
|
||||
newPosition,
|
||||
);
|
||||
}
|
||||
this.edit({ guild_folders: this.rawSetting.guild_folders });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUserSettingManager;
|
||||
|
@ -1,75 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
const CachedManager = require('./CachedManager');
|
||||
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 FriendsManager extends CachedManager {
|
||||
constructor(client, iterable) {
|
||||
super(client, User, iterable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, User>}
|
||||
* @name FriendsManager#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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.api.users(id).get();
|
||||
return this._add(data, cache);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FriendsManager;
|
||||
'use strict';
|
||||
|
||||
const CachedManager = require('./CachedManager');
|
||||
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 FriendsManager extends CachedManager {
|
||||
constructor(client, iterable) {
|
||||
super(client, User, iterable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, User>}
|
||||
* @name FriendsManager#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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.api.users(id).get();
|
||||
return this._add(data, cache);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FriendsManager;
|
||||
|
@ -1,247 +1,247 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { TypeError, Error } = require('../errors');
|
||||
const { Message } = require('../structures/Message');
|
||||
const MessagePayload = require('../structures/MessagePayload');
|
||||
const Util = require('../util/Util');
|
||||
const BigNumber = require('bignumber.js');
|
||||
|
||||
/**
|
||||
* Manages API methods for Messages and holds their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class MessageManager extends CachedManager {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters to pass in when requesting previous messages from a channel. `around`, `before` and
|
||||
* `after` are mutually exclusive. All the parameters are optional.
|
||||
* @typedef {Object} ChannelLogsQueryOptions
|
||||
* @property {number} [limit=50] Number of messages to acquire
|
||||
* @property {Snowflake} [before] The message's id to get the messages that were posted before it
|
||||
* @property {Snowflake} [after] The message's id to get the messages that were posted after it
|
||||
* @property {Snowflake} [around] The message's id to get the messages that were posted around it
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets a message, or messages, from this 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 {Snowflake|ChannelLogsQueryOptions} [message] The id of the message to fetch, or query parameters.
|
||||
* @param {BaseFetchOptions} [options] Additional options for this fetch
|
||||
* @returns {Promise<Message|Collection<Snowflake, Message>>}
|
||||
* @example
|
||||
* // Get message
|
||||
* channel.messages.fetch('99539446449315840')
|
||||
* .then(message => console.log(message.content))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Get messages
|
||||
* channel.messages.fetch({ limit: 10 })
|
||||
* .then(messages => console.log(`Received ${messages.size} messages`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Get messages and filter by user id
|
||||
* channel.messages.fetch()
|
||||
* .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
fetch(message, { cache = true, force = false } = {}) {
|
||||
return typeof message === 'string' ? this._fetchId(message, cache, force) : this._fetchMany(message, cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.api.channels[this.channel.id].pins.get();
|
||||
const messages = new Collection();
|
||||
for (const message of data) messages.set(message.id, this._add(message, cache));
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a Message object. This can be:
|
||||
* * A Message
|
||||
* * A Snowflake
|
||||
* @typedef {Message|Snowflake} MessageResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
let messagePayload;
|
||||
if (options instanceof MessagePayload) {
|
||||
messagePayload = await options.resolveData();
|
||||
} else {
|
||||
messagePayload = await MessagePayload.create(
|
||||
message instanceof Message ? message : this,
|
||||
options,
|
||||
).resolveData();
|
||||
}
|
||||
const { data, files } = await messagePayload.resolveFiles();
|
||||
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
const data = await this.client.api.channels(this.channel.id).messages(message).crosspost.post();
|
||||
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
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async pin(message) {
|
||||
message = this.resolveId(message);
|
||||
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
await this.client.api.channels(this.channel.id).pins(message).put();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins a message from the channel's pinned messages, even if it's not cached.
|
||||
* @param {MessageResolvable} message The message to unpin
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async unpin(message) {
|
||||
message = this.resolveId(message);
|
||||
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
await this.client.api.channels(this.channel.id).pins(message).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
emoji = Util.resolvePartialEmoji(emoji);
|
||||
if (!emoji) throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable');
|
||||
|
||||
const emojiId = emoji.id
|
||||
? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}`
|
||||
: encodeURIComponent(emoji.name);
|
||||
|
||||
// eslint-disable-next-line newline-per-chained-call
|
||||
await this.client.api.channels(this.channel.id).messages(message).reactions(emojiId, '@me').put();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
await this.client.api.channels(this.channel.id).messages(message).delete();
|
||||
}
|
||||
|
||||
async _fetchId(messageId, cache, force) {
|
||||
if (!force) {
|
||||
const existing = this.cache.get(messageId);
|
||||
if (existing && !existing.partial) return existing;
|
||||
}
|
||||
|
||||
// const data = await this.client.api.channels[this.channel.id].messages[messageId].get(); // Discord Block
|
||||
// https://canary.discord.com/api/v9/guilds/809133733591384155/messages/search?channel_id=840225732902518825&max_id=957254525360697375&min_id=957254525360697373
|
||||
const data = (
|
||||
await this.client.api.guilds[this.channel.guild.id].messages.search.get({
|
||||
query: {
|
||||
channel_id: this.channel.id,
|
||||
max_id: new BigNumber.BigNumber(messageId).plus(1).toString(),
|
||||
min_id: new BigNumber.BigNumber(messageId).minus(1).toString(),
|
||||
},
|
||||
})
|
||||
).messages[0]
|
||||
if (data) return this._add(data[0], cache);
|
||||
else throw new Error('MESSAGE_ID_NOT_FOUND');
|
||||
}
|
||||
|
||||
async _fetchMany(options = {}, cache) {
|
||||
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
|
||||
const messages = new Collection();
|
||||
for (const message of data) messages.set(message.id, this._add(message, cache));
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageManager;
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { TypeError, Error } = require('../errors');
|
||||
const { Message } = require('../structures/Message');
|
||||
const MessagePayload = require('../structures/MessagePayload');
|
||||
const Util = require('../util/Util');
|
||||
const BigNumber = require('bignumber.js');
|
||||
|
||||
/**
|
||||
* Manages API methods for Messages and holds their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class MessageManager extends CachedManager {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters to pass in when requesting previous messages from a channel. `around`, `before` and
|
||||
* `after` are mutually exclusive. All the parameters are optional.
|
||||
* @typedef {Object} ChannelLogsQueryOptions
|
||||
* @property {number} [limit=50] Number of messages to acquire
|
||||
* @property {Snowflake} [before] The message's id to get the messages that were posted before it
|
||||
* @property {Snowflake} [after] The message's id to get the messages that were posted after it
|
||||
* @property {Snowflake} [around] The message's id to get the messages that were posted around it
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets a message, or messages, from this 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 {Snowflake|ChannelLogsQueryOptions} [message] The id of the message to fetch, or query parameters.
|
||||
* @param {BaseFetchOptions} [options] Additional options for this fetch
|
||||
* @returns {Promise<Message|Collection<Snowflake, Message>>}
|
||||
* @example
|
||||
* // Get message
|
||||
* channel.messages.fetch('99539446449315840')
|
||||
* .then(message => console.log(message.content))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Get messages
|
||||
* channel.messages.fetch({ limit: 10 })
|
||||
* .then(messages => console.log(`Received ${messages.size} messages`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Get messages and filter by user id
|
||||
* channel.messages.fetch()
|
||||
* .then(messages => console.log(`${messages.filter(m => m.author.id === '84484653687267328').size} messages`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
fetch(message, { cache = true, force = false } = {}) {
|
||||
return typeof message === 'string' ? this._fetchId(message, cache, force) : this._fetchMany(message, cache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.api.channels[this.channel.id].pins.get();
|
||||
const messages = new Collection();
|
||||
for (const message of data) messages.set(message.id, this._add(message, cache));
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a Message object. This can be:
|
||||
* * A Message
|
||||
* * A Snowflake
|
||||
* @typedef {Message|Snowflake} MessageResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
let messagePayload;
|
||||
if (options instanceof MessagePayload) {
|
||||
messagePayload = await options.resolveData();
|
||||
} else {
|
||||
messagePayload = await MessagePayload.create(
|
||||
message instanceof Message ? message : this,
|
||||
options,
|
||||
).resolveData();
|
||||
}
|
||||
const { data, files } = await messagePayload.resolveFiles();
|
||||
const d = await this.client.api.channels[this.channel.id].messages[messageId].patch({ data, 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
const data = await this.client.api.channels(this.channel.id).messages(message).crosspost.post();
|
||||
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
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async pin(message) {
|
||||
message = this.resolveId(message);
|
||||
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
await this.client.api.channels(this.channel.id).pins(message).put();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins a message from the channel's pinned messages, even if it's not cached.
|
||||
* @param {MessageResolvable} message The message to unpin
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async unpin(message) {
|
||||
message = this.resolveId(message);
|
||||
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
await this.client.api.channels(this.channel.id).pins(message).delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
emoji = Util.resolvePartialEmoji(emoji);
|
||||
if (!emoji) throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable');
|
||||
|
||||
const emojiId = emoji.id
|
||||
? `${emoji.animated ? 'a:' : ''}${emoji.name}:${emoji.id}`
|
||||
: encodeURIComponent(emoji.name);
|
||||
|
||||
// eslint-disable-next-line newline-per-chained-call
|
||||
await this.client.api.channels(this.channel.id).messages(message).reactions(emojiId, '@me').put();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
|
||||
|
||||
await this.client.api.channels(this.channel.id).messages(message).delete();
|
||||
}
|
||||
|
||||
async _fetchId(messageId, cache, force) {
|
||||
if (!force) {
|
||||
const existing = this.cache.get(messageId);
|
||||
if (existing && !existing.partial) return existing;
|
||||
}
|
||||
|
||||
// const data = await this.client.api.channels[this.channel.id].messages[messageId].get(); // Discord Block
|
||||
// https://canary.discord.com/api/v9/guilds/809133733591384155/messages/search?channel_id=840225732902518825&max_id=957254525360697375&min_id=957254525360697373
|
||||
const data = (
|
||||
await this.client.api.guilds[this.channel.guild.id].messages.search.get({
|
||||
query: {
|
||||
channel_id: this.channel.id,
|
||||
max_id: new BigNumber.BigNumber(messageId).plus(1).toString(),
|
||||
min_id: new BigNumber.BigNumber(messageId).minus(1).toString(),
|
||||
},
|
||||
})
|
||||
).messages[0]
|
||||
if (data) return this._add(data[0], cache);
|
||||
else throw new Error('MESSAGE_ID_NOT_FOUND');
|
||||
}
|
||||
|
||||
async _fetchMany(options = {}, cache) {
|
||||
const data = await this.client.api.channels[this.channel.id].messages.get({ query: options });
|
||||
const messages = new Collection();
|
||||
for (const message of data) messages.set(message.id, this._add(message, cache));
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageManager;
|
||||
|
@ -3,14 +3,14 @@
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { Error } = require('../errors');
|
||||
const { User } = require('discord.js-selfbot-v13');
|
||||
const Discord = require("discord.js-selfbot-v13")
|
||||
/**
|
||||
* 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);
|
||||
super(reaction.client, Discord.User, iterable);
|
||||
|
||||
/**
|
||||
* The reaction that this manager belongs to
|
||||
@ -21,7 +21,7 @@ class ReactionUserManager extends CachedManager {
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, User>}
|
||||
* @type {Collection<Snowflake, Discord.User>}
|
||||
* @name ReactionUserManager#cache
|
||||
*/
|
||||
|
||||
@ -35,7 +35,7 @@ class ReactionUserManager extends CachedManager {
|
||||
/**
|
||||
* 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>>}
|
||||
* @returns {Promise<Collection<Snowflake, Discord.User>>}
|
||||
*/
|
||||
async fetch({ limit = 100, after } = {}) {
|
||||
const message = this.reaction.message;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,103 +1,103 @@
|
||||
'use strict';
|
||||
|
||||
const { TypeError } = require('../errors');
|
||||
const { MessageComponentTypes, Events } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents an interactive component of a Message. It should not be necessary to construct this directly.
|
||||
* See {@link MessageComponent}
|
||||
*/
|
||||
class BaseMessageComponent {
|
||||
/**
|
||||
* Options for a BaseMessageComponent
|
||||
* @typedef {Object} BaseMessageComponentOptions
|
||||
* @property {MessageComponentTypeResolvable} type The type of this component
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved into options for a MessageComponent. This can be:
|
||||
* * MessageActionRowOptions
|
||||
* * MessageButtonOptions
|
||||
* * MessageSelectMenuOptions
|
||||
* @typedef {MessageActionRowOptions|MessageButtonOptions|MessageSelectMenuOptions} MessageComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Components that can be sent in a message. These can be:
|
||||
* * MessageActionRow
|
||||
* * MessageButton
|
||||
* * MessageSelectMenu
|
||||
* @typedef {MessageActionRow|MessageButton|MessageSelectMenu} MessageComponent
|
||||
* @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a MessageComponentType. This can be:
|
||||
* * MessageComponentType
|
||||
* * string
|
||||
* * number
|
||||
* @typedef {string|number|MessageComponentType} MessageComponentTypeResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {BaseMessageComponent|BaseMessageComponentOptions} [data={}] The options for this component
|
||||
*/
|
||||
constructor(data) {
|
||||
/**
|
||||
* The type of this component
|
||||
* @type {?MessageComponentType}
|
||||
*/
|
||||
this.type = 'type' in data ? BaseMessageComponent.resolveType(data.type) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessageComponent based on the type of the incoming data
|
||||
* @param {MessageComponentOptions} data Data for a MessageComponent
|
||||
* @param {Client|WebhookClient} [client] Client constructing this component
|
||||
* @returns {?MessageComponent}
|
||||
* @private
|
||||
*/
|
||||
static create(data, client) {
|
||||
let component;
|
||||
let type = data.type;
|
||||
|
||||
if (typeof type === 'string') type = MessageComponentTypes[type];
|
||||
|
||||
switch (type) {
|
||||
case MessageComponentTypes.ACTION_ROW: {
|
||||
const MessageActionRow = require('./MessageActionRow');
|
||||
component = data instanceof MessageActionRow ? data : new MessageActionRow(data, client);
|
||||
break;
|
||||
}
|
||||
case MessageComponentTypes.BUTTON: {
|
||||
const MessageButton = require('./MessageButton');
|
||||
component = data instanceof MessageButton ? data : new MessageButton(data);
|
||||
break;
|
||||
}
|
||||
case MessageComponentTypes.SELECT_MENU: {
|
||||
const MessageSelectMenu = require('./MessageSelectMenu');
|
||||
component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (client) {
|
||||
client.emit(Events.DEBUG, `[BaseMessageComponent] Received component with unknown type: ${data.type}`);
|
||||
} else {
|
||||
throw new TypeError('INVALID_TYPE', 'data.type', 'valid MessageComponentType');
|
||||
}
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the type of a MessageComponent
|
||||
* @param {MessageComponentTypeResolvable} type The type to resolve
|
||||
* @returns {MessageComponentType}
|
||||
* @private
|
||||
*/
|
||||
static resolveType(type) {
|
||||
return typeof type === 'string' ? type : MessageComponentTypes[type];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseMessageComponent;
|
||||
'use strict';
|
||||
|
||||
const { TypeError } = require('../errors');
|
||||
const { MessageComponentTypes, Events } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents an interactive component of a Message. It should not be necessary to construct this directly.
|
||||
* See {@link MessageComponent}
|
||||
*/
|
||||
class BaseMessageComponent {
|
||||
/**
|
||||
* Options for a BaseMessageComponent
|
||||
* @typedef {Object} BaseMessageComponentOptions
|
||||
* @property {MessageComponentTypeResolvable} type The type of this component
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved into options for a MessageComponent. This can be:
|
||||
* * MessageActionRowOptions
|
||||
* * MessageButtonOptions
|
||||
* * MessageSelectMenuOptions
|
||||
* @typedef {MessageActionRowOptions|MessageButtonOptions|MessageSelectMenuOptions} MessageComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Components that can be sent in a message. These can be:
|
||||
* * MessageActionRow
|
||||
* * MessageButton
|
||||
* * MessageSelectMenu
|
||||
* @typedef {MessageActionRow|MessageButton|MessageSelectMenu} MessageComponent
|
||||
* @see {@link https://discord.com/developers/docs/interactions/message-components#component-object-component-types}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a MessageComponentType. This can be:
|
||||
* * MessageComponentType
|
||||
* * string
|
||||
* * number
|
||||
* @typedef {string|number|MessageComponentType} MessageComponentTypeResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {BaseMessageComponent|BaseMessageComponentOptions} [data={}] The options for this component
|
||||
*/
|
||||
constructor(data) {
|
||||
/**
|
||||
* The type of this component
|
||||
* @type {?MessageComponentType}
|
||||
*/
|
||||
this.type = 'type' in data ? BaseMessageComponent.resolveType(data.type) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a MessageComponent based on the type of the incoming data
|
||||
* @param {MessageComponentOptions} data Data for a MessageComponent
|
||||
* @param {Client|WebhookClient} [client] Client constructing this component
|
||||
* @returns {?MessageComponent}
|
||||
* @private
|
||||
*/
|
||||
static create(data, client) {
|
||||
let component;
|
||||
let type = data.type;
|
||||
|
||||
if (typeof type === 'string') type = MessageComponentTypes[type];
|
||||
|
||||
switch (type) {
|
||||
case MessageComponentTypes.ACTION_ROW: {
|
||||
const MessageActionRow = require('./MessageActionRow');
|
||||
component = data instanceof MessageActionRow ? data : new MessageActionRow(data, client);
|
||||
break;
|
||||
}
|
||||
case MessageComponentTypes.BUTTON: {
|
||||
const MessageButton = require('./MessageButton');
|
||||
component = data instanceof MessageButton ? data : new MessageButton(data);
|
||||
break;
|
||||
}
|
||||
case MessageComponentTypes.SELECT_MENU: {
|
||||
const MessageSelectMenu = require('./MessageSelectMenu');
|
||||
component = data instanceof MessageSelectMenu ? data : new MessageSelectMenu(data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (client) {
|
||||
client.emit(Events.DEBUG, `[BaseMessageComponent] Received component with unknown type: ${data.type}`);
|
||||
} else {
|
||||
throw new TypeError('INVALID_TYPE', 'data.type', 'valid MessageComponentType');
|
||||
}
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the type of a MessageComponent
|
||||
* @param {MessageComponentTypeResolvable} type The type to resolve
|
||||
* @returns {MessageComponentType}
|
||||
* @private
|
||||
*/
|
||||
static resolveType(type) {
|
||||
return typeof type === 'string' ? type : MessageComponentTypes[type];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseMessageComponent;
|
||||
|
@ -1,275 +1,275 @@
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const Base = require('./Base');
|
||||
let CategoryChannel;
|
||||
let DMChannel;
|
||||
let NewsChannel;
|
||||
let StageChannel;
|
||||
let StoreChannel;
|
||||
let TextChannel;
|
||||
let ThreadChannel;
|
||||
let VoiceChannel;
|
||||
const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const { Message } = require('discord.js');
|
||||
const { ApplicationCommand } = require('discord.js-selfbot-v13');
|
||||
|
||||
/**
|
||||
* @type {WeakSet<Channel>}
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
const deletedChannels = new WeakSet();
|
||||
let deprecationEmittedForDeleted = false;
|
||||
|
||||
/**
|
||||
* Represents any channel on Discord.
|
||||
* @extends {Base}
|
||||
* @abstract
|
||||
*/
|
||||
class Channel extends Base {
|
||||
constructor(client, data, immediatePatch = true) {
|
||||
super(client);
|
||||
|
||||
const type = ChannelTypes[data?.type];
|
||||
/**
|
||||
* The type of the channel
|
||||
* @type {ChannelType}
|
||||
*/
|
||||
this.type = type ?? 'UNKNOWN';
|
||||
|
||||
if (data && immediatePatch) this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
/**
|
||||
* The channel's id
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp the channel was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return SnowflakeUtil.timestampFrom(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the channel was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the structure has been deleted
|
||||
* @type {boolean}
|
||||
* @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
|
||||
*/
|
||||
get deleted() {
|
||||
if (!deprecationEmittedForDeleted) {
|
||||
deprecationEmittedForDeleted = true;
|
||||
process.emitWarning(
|
||||
'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
}
|
||||
|
||||
return deletedChannels.has(this);
|
||||
}
|
||||
|
||||
set deleted(value) {
|
||||
if (!deprecationEmittedForDeleted) {
|
||||
deprecationEmittedForDeleted = true;
|
||||
process.emitWarning(
|
||||
'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
}
|
||||
|
||||
if (value) deletedChannels.add(this);
|
||||
else deletedChannels.delete(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this Channel is a partial
|
||||
* <info>This is always false outside of DM channels.</info>
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get partial() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // Logs: Hello from <#123456789012345678>!
|
||||
* console.log(`Hello from ${channel}!`);
|
||||
*/
|
||||
toString() {
|
||||
return `<#${this.id}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this channel.
|
||||
* @returns {Promise<Channel>}
|
||||
* @example
|
||||
* // Delete the channel
|
||||
* channel.delete()
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete() {
|
||||
await this.client.api.channels(this.id).delete();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches this channel.
|
||||
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
||||
* @returns {Promise<Channel>}
|
||||
*/
|
||||
fetch(force = true) {
|
||||
return this.client.channels.fetch(this.id, { force });
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this channel is {@link TextBasedChannels text-based}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isText() {
|
||||
return 'messages' in this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this channel is {@link BaseGuildVoiceChannel voice-based}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isVoice() {
|
||||
return VoiceBasedChannelTypes.includes(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this channel is a {@link ThreadChannel}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isThread() {
|
||||
return ThreadChannelTypes.includes(this.type);
|
||||
}
|
||||
|
||||
static create(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) {
|
||||
CategoryChannel ??= require('./CategoryChannel');
|
||||
DMChannel ??= require('./DMChannel');
|
||||
NewsChannel ??= require('./NewsChannel');
|
||||
StageChannel ??= require('./StageChannel');
|
||||
StoreChannel ??= require('./StoreChannel');
|
||||
TextChannel ??= require('./TextChannel');
|
||||
ThreadChannel ??= require('./ThreadChannel');
|
||||
VoiceChannel ??= require('./VoiceChannel');
|
||||
|
||||
let channel;
|
||||
if (!data.guild_id && !guild) {
|
||||
if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) {
|
||||
channel = new DMChannel(client, data);
|
||||
} else if (data.type === ChannelTypes.GROUP_DM) {
|
||||
const PartialGroupDMChannel = require('./PartialGroupDMChannel');
|
||||
channel = new PartialGroupDMChannel(client, data);
|
||||
}
|
||||
} else {
|
||||
guild ??= client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (guild || allowUnknownGuild) {
|
||||
switch (data.type) {
|
||||
case ChannelTypes.GUILD_TEXT: {
|
||||
channel = new TextChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_VOICE: {
|
||||
channel = new VoiceChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_CATEGORY: {
|
||||
channel = new CategoryChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_NEWS: {
|
||||
channel = new NewsChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_STORE: {
|
||||
channel = new StoreChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_STAGE_VOICE: {
|
||||
channel = new StageChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_NEWS_THREAD:
|
||||
case ChannelTypes.GUILD_PUBLIC_THREAD:
|
||||
case ChannelTypes.GUILD_PRIVATE_THREAD: {
|
||||
channel = new ThreadChannel(guild, data, client, fromInteraction);
|
||||
if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
toJSON(...props) {
|
||||
return super.toJSON({ createdTimestamp: true }, ...props);
|
||||
}
|
||||
|
||||
// Send Slash
|
||||
/**
|
||||
* Send Slash to this channel
|
||||
* @param {DiscordBot} botID Bot ID
|
||||
* @param {String<ApplicationCommand.name>} commandName Command name
|
||||
* @param {Array<ApplicationCommand.options>} args Command arguments
|
||||
* @returns {Promise<pending>}
|
||||
*/
|
||||
async sendSlash(botID, commandName, args = []) {
|
||||
if (!this.isText()) throw new Error('This channel is not text-based.');
|
||||
if(!botID) throw new Error('Bot ID is required');
|
||||
const user = await this.client.users.fetch(botID).catch(() => {});
|
||||
if (!user || !user.bot || !user.applications) throw new Error('BotID is not a bot or does not have an application slash command');
|
||||
if (!commandName || typeof commandName !== 'string') throw new Error('Command name is required');
|
||||
const listApplication = user.applications.cache.size == 0 ? await user.applications.fetch() : user.applications.cache;
|
||||
let slashCommand;
|
||||
await Promise.all(listApplication.map(async application => {
|
||||
if (commandName == application.name && application.type == 'CHAT_INPUT') slashCommand = application;
|
||||
}));
|
||||
if (!slashCommand) throw new Error(
|
||||
`Command ${commandName} is not found\nList command avalible: ${listApplication.filter(a => a.type == 'CHAT_INPUT').map(a => a.name).join(', ')}`,
|
||||
);
|
||||
return slashCommand.sendSlashCommand(
|
||||
new Message(this.client, {
|
||||
channel_id: this.id,
|
||||
guild_id: this.guild?.id || null,
|
||||
author: this.client.user,
|
||||
content: '',
|
||||
id: this.client.user.id
|
||||
}),
|
||||
args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports.Channel = Channel;
|
||||
exports.deletedChannels = deletedChannels;
|
||||
|
||||
/**
|
||||
* @external APIChannel
|
||||
* @see {@link https://discord.com/developers/docs/resources/channel#channel-object}
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const process = require('node:process');
|
||||
const Base = require('./Base');
|
||||
let CategoryChannel;
|
||||
let DMChannel;
|
||||
let NewsChannel;
|
||||
let StageChannel;
|
||||
let StoreChannel;
|
||||
let TextChannel;
|
||||
let ThreadChannel;
|
||||
let VoiceChannel;
|
||||
const { ChannelTypes, ThreadChannelTypes, VoiceBasedChannelTypes } = require('../util/Constants');
|
||||
const SnowflakeUtil = require('../util/SnowflakeUtil');
|
||||
const { Message } = require('discord.js');
|
||||
//const { ApplicationCommand } = require('discord.js-selfbot-v13'); - Not being used in this file, not necessary.
|
||||
|
||||
/**
|
||||
* @type {WeakSet<Channel>}
|
||||
* @private
|
||||
* @internal
|
||||
*/
|
||||
const deletedChannels = new WeakSet();
|
||||
let deprecationEmittedForDeleted = false;
|
||||
|
||||
/**
|
||||
* Represents any channel on Discord.
|
||||
* @extends {Base}
|
||||
* @abstract
|
||||
*/
|
||||
class Channel extends Base {
|
||||
constructor(client, data, immediatePatch = true) {
|
||||
super(client);
|
||||
|
||||
const type = ChannelTypes[data?.type];
|
||||
/**
|
||||
* The type of the channel
|
||||
* @type {ChannelType}
|
||||
*/
|
||||
this.type = type ?? 'UNKNOWN';
|
||||
|
||||
if (data && immediatePatch) this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
/**
|
||||
* The channel's id
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp the channel was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return SnowflakeUtil.timestampFrom(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the channel was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the structure has been deleted
|
||||
* @type {boolean}
|
||||
* @deprecated This will be removed in the next major version, see https://github.com/discordjs/discord.js/issues/7091
|
||||
*/
|
||||
get deleted() {
|
||||
if (!deprecationEmittedForDeleted) {
|
||||
deprecationEmittedForDeleted = true;
|
||||
process.emitWarning(
|
||||
'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
}
|
||||
|
||||
return deletedChannels.has(this);
|
||||
}
|
||||
|
||||
set deleted(value) {
|
||||
if (!deprecationEmittedForDeleted) {
|
||||
deprecationEmittedForDeleted = true;
|
||||
process.emitWarning(
|
||||
'Channel#deleted is deprecated, see https://github.com/discordjs/discord.js/issues/7091.',
|
||||
'DeprecationWarning',
|
||||
);
|
||||
}
|
||||
|
||||
if (value) deletedChannels.add(this);
|
||||
else deletedChannels.delete(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this Channel is a partial
|
||||
* <info>This is always false outside of DM channels.</info>
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get partial() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the channel's mention instead of the Channel object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // Logs: Hello from <#123456789012345678>!
|
||||
* console.log(`Hello from ${channel}!`);
|
||||
*/
|
||||
toString() {
|
||||
return `<#${this.id}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this channel.
|
||||
* @returns {Promise<Channel>}
|
||||
* @example
|
||||
* // Delete the channel
|
||||
* channel.delete()
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async delete() {
|
||||
await this.client.api.channels(this.id).delete();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches this channel.
|
||||
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
||||
* @returns {Promise<Channel>}
|
||||
*/
|
||||
fetch(force = true) {
|
||||
return this.client.channels.fetch(this.id, { force });
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this channel is {@link TextBasedChannels text-based}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isText() {
|
||||
return 'messages' in this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this channel is {@link BaseGuildVoiceChannel voice-based}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isVoice() {
|
||||
return VoiceBasedChannelTypes.includes(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this channel is a {@link ThreadChannel}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isThread() {
|
||||
return ThreadChannelTypes.includes(this.type);
|
||||
}
|
||||
|
||||
static create(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) {
|
||||
CategoryChannel ??= require('./CategoryChannel');
|
||||
DMChannel ??= require('./DMChannel');
|
||||
NewsChannel ??= require('./NewsChannel');
|
||||
StageChannel ??= require('./StageChannel');
|
||||
StoreChannel ??= require('./StoreChannel');
|
||||
TextChannel ??= require('./TextChannel');
|
||||
ThreadChannel ??= require('./ThreadChannel');
|
||||
VoiceChannel ??= require('./VoiceChannel');
|
||||
|
||||
let channel;
|
||||
if (!data.guild_id && !guild) {
|
||||
if ((data.recipients && data.type !== ChannelTypes.GROUP_DM) || data.type === ChannelTypes.DM) {
|
||||
channel = new DMChannel(client, data);
|
||||
} else if (data.type === ChannelTypes.GROUP_DM) {
|
||||
const PartialGroupDMChannel = require('./PartialGroupDMChannel');
|
||||
channel = new PartialGroupDMChannel(client, data);
|
||||
}
|
||||
} else {
|
||||
guild ??= client.guilds.cache.get(data.guild_id);
|
||||
|
||||
if (guild || allowUnknownGuild) {
|
||||
switch (data.type) {
|
||||
case ChannelTypes.GUILD_TEXT: {
|
||||
channel = new TextChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_VOICE: {
|
||||
channel = new VoiceChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_CATEGORY: {
|
||||
channel = new CategoryChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_NEWS: {
|
||||
channel = new NewsChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_STORE: {
|
||||
channel = new StoreChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_STAGE_VOICE: {
|
||||
channel = new StageChannel(guild, data, client);
|
||||
break;
|
||||
}
|
||||
case ChannelTypes.GUILD_NEWS_THREAD:
|
||||
case ChannelTypes.GUILD_PUBLIC_THREAD:
|
||||
case ChannelTypes.GUILD_PRIVATE_THREAD: {
|
||||
channel = new ThreadChannel(guild, data, client, fromInteraction);
|
||||
if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
toJSON(...props) {
|
||||
return super.toJSON({ createdTimestamp: true }, ...props);
|
||||
}
|
||||
|
||||
// Send Slash
|
||||
/**
|
||||
* Send Slash to this channel
|
||||
* @param {DiscordBot} botID Bot ID
|
||||
* @param {String<ApplicationCommand.name>} commandName Command name
|
||||
* @param {Array<ApplicationCommand.options>} args Command arguments
|
||||
* @returns {Promise<pending>}
|
||||
*/
|
||||
async sendSlash(botID, commandName, args = []) {
|
||||
if (!this.isText()) throw new Error('This channel is not text-based.');
|
||||
if(!botID) throw new Error('Bot ID is required');
|
||||
const user = await this.client.users.fetch(botID).catch(() => {});
|
||||
if (!user || !user.bot || !user.applications) throw new Error('BotID is not a bot or does not have an application slash command');
|
||||
if (!commandName || typeof commandName !== 'string') throw new Error('Command name is required');
|
||||
const listApplication = user.applications.cache.size == 0 ? await user.applications.fetch() : user.applications.cache;
|
||||
let slashCommand;
|
||||
await Promise.all(listApplication.map(async application => {
|
||||
if (commandName == application.name && application.type == 'CHAT_INPUT') slashCommand = application;
|
||||
}));
|
||||
if (!slashCommand) throw new Error(
|
||||
`Command ${commandName} is not found\nList command avalible: ${listApplication.filter(a => a.type == 'CHAT_INPUT').map(a => a.name).join(', ')}`,
|
||||
);
|
||||
return slashCommand.sendSlashCommand(
|
||||
new Message(this.client, {
|
||||
channel_id: this.id,
|
||||
guild_id: this.guild?.id || null,
|
||||
author: this.client.user,
|
||||
content: '',
|
||||
id: this.client.user.id
|
||||
}),
|
||||
args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports.Channel = Channel;
|
||||
exports.deletedChannels = deletedChannels;
|
||||
|
||||
/**
|
||||
* @external APIChannel
|
||||
* @see {@link https://discord.com/developers/docs/resources/channel#channel-object}
|
||||
*/
|
||||
|
@ -1,375 +1,357 @@
|
||||
'use strict';
|
||||
|
||||
const User = require('./User');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const { HypeSquadOptions } = require('../util/Constants');
|
||||
const { Util } = require('..');
|
||||
|
||||
/**
|
||||
* Represents the logged in client's Discord user.
|
||||
* @extends {User}
|
||||
*/
|
||||
class ClientUser extends User {
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
if ('verified' in data) {
|
||||
/**
|
||||
* Whether or not this account has been verified
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.verified = data.verified;
|
||||
}
|
||||
|
||||
if ('mfa_enabled' in data) {
|
||||
/**
|
||||
* If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.mfaEnabled =
|
||||
typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null;
|
||||
} else {
|
||||
this.mfaEnabled ??= null;
|
||||
}
|
||||
|
||||
if ('token' in data) this.client.token = data.token;
|
||||
|
||||
// Add (Selfbot)
|
||||
if ('premium' in data) this.nitro = data.premium;
|
||||
/**
|
||||
* Nitro Status
|
||||
* `0`: None
|
||||
* `1`: Classic
|
||||
* `2`: Boost
|
||||
* @external
|
||||
* https://discord.com/developers/docs/resources/user#user-object-premium-types
|
||||
* @type {Number}
|
||||
*/
|
||||
if ('purchased_flags' in data) this.nitroType = data.purchased_flags;
|
||||
if ('phone' in data) this.phoneNumber = data.phone;
|
||||
if ('nsfw_allowed' in data) this.nsfwAllowed = data.nsfw_allowed;
|
||||
if ('email' in data) this.emailAddress = data.email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the client user's presence
|
||||
* @type {ClientPresence}
|
||||
* @readonly
|
||||
*/
|
||||
get presence() {
|
||||
return this.client.presence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data used to edit the logged in client
|
||||
* @typedef {Object} ClientUserEditData
|
||||
* @property {string} [username] The new username
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [avatar] The new avatar
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edits the logged in client.
|
||||
* @param {ClientUserEditData} data The new data
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
async edit(data) {
|
||||
if (typeof data.avatar !== 'undefined')
|
||||
data.avatar = await DataResolver.resolveImage(data.avatar);
|
||||
if (typeof data.banner !== 'undefined')
|
||||
data.banner = await DataResolver.resolveImage(data.banner);
|
||||
const newData = await this.client.api.users('@me').patch({ data });
|
||||
this.client.token = newData.token;
|
||||
this.client.password = data?.password
|
||||
? data?.password
|
||||
: this.client.password;
|
||||
const { updated } = this.client.actions.UserUpdate.handle(newData);
|
||||
return updated ?? this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username of the logged in client.
|
||||
* <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
|
||||
* every hour. Use this sparingly!</info>
|
||||
* @param {string} username The new username
|
||||
* @param {string} password The password of the account
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set username
|
||||
* client.user.setUsername('discordjs')
|
||||
* .then(user => console.log(`My new username is ${user.username}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUsername(username, password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to change a username.');
|
||||
return this.edit({
|
||||
username,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the avatar of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set avatar
|
||||
* client.user.setAvatar('./avatar.png')
|
||||
* .then(user => console.log(`New avatar set!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setAvatar(avatar) {
|
||||
return this.edit({ avatar });
|
||||
}
|
||||
/**
|
||||
* Sets the banner of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set banner
|
||||
* client.user.setBanner('./banner.png')
|
||||
* .then(user => console.log(`New banner set!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setBanner(banner) {
|
||||
if (this.nitroType !== 2)
|
||||
throw new Error(
|
||||
'You must be a Nitro Boosted User to change your banner.',
|
||||
);
|
||||
return this.edit({ banner });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HyperSquad House
|
||||
* @param {HypeSquadOptions<Number|String>} type
|
||||
* `LEAVE`: 0
|
||||
* `HOUSE_BRAVERY`: 1
|
||||
* `HOUSE_BRILLIANCE`: 2
|
||||
* `HOUSE_BALANCE`: 3
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Set HyperSquad HOUSE_BRAVERY
|
||||
* client.user.setHypeSquad(1); || client.user.setHypeSquad('HOUSE_BRAVERY');
|
||||
* // Leave
|
||||
* client.user.setHypeSquad(0);
|
||||
*/
|
||||
async setHypeSquad(type) {
|
||||
const id = typeof type === 'string' ? HypeSquadOptions[type] : type;
|
||||
if (!id && id !== 0) throw new Error('Invalid HypeSquad type.');
|
||||
if (id !== 0)
|
||||
return await this.client.api.hypesquad.online.post({
|
||||
data: { house_id: id },
|
||||
});
|
||||
else return await this.client.api.hypesquad.online.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Accent color
|
||||
* @param {ColorResolvable} color Color to set
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setAccentColor(color = null) {
|
||||
return this.edit({ accent_color: color ? Util.resolveColor(color) : null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set discriminator
|
||||
* @param {User.discriminator} discriminator It is #1234
|
||||
* @param {string} password The password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setDiscriminator(discriminator, password) {
|
||||
if (!this.nitro)
|
||||
throw new Error('You must be a Nitro User to change your discriminator.');
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to change a discriminator.');
|
||||
return this.edit({
|
||||
discriminator,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set About me
|
||||
* @param {String} bio Bio to set
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setAboutMe(bio = null) {
|
||||
return this.edit({
|
||||
bio,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the email
|
||||
* @param {Email<string>} email Email to change
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setEmail(email, password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to change a email.');
|
||||
return this.edit({
|
||||
email,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new password
|
||||
* @param {string} oldPassword Old password
|
||||
* @param {string} newPassword New password to set
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setPassword(oldPassword, newPassword) {
|
||||
if (!oldPassword && !this.client.password)
|
||||
throw new Error('A password is required to change a password.');
|
||||
if (!newPassword) throw new Error('New password is required.');
|
||||
return this.edit({
|
||||
password: this.client.password ? this.client.password : oldPassword,
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable account
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async disableAccount(password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to disable an account.');
|
||||
return await this.client.api.users['@me'].disable.post({
|
||||
data: {
|
||||
password: this.client.password ? this.client.password : password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account. Warning: Cannot be changed once used!
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async deleteAccount(password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to delete an account.');
|
||||
return await this.client.api.users['@me'].delete.post({
|
||||
data: {
|
||||
password: this.client.password ? this.client.password : password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for setting activities
|
||||
* @typedef {Object} ActivitiesOptions
|
||||
* @property {string} [name] Name of the activity
|
||||
* @property {ActivityType|number} [type] Type of the activity
|
||||
* @property {string} [url] Twitch / YouTube stream URL
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data resembling a raw Discord presence.
|
||||
* @typedef {Object} PresenceData
|
||||
* @property {PresenceStatusData} [status] Status of the user
|
||||
* @property {boolean} [afk] Whether the user is AFK
|
||||
* @property {ActivitiesOptions[]} [activities] Activity the user is playing
|
||||
* @property {number|number[]} [shardId] Shard id(s) to have the activity set on
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the full presence of the client user.
|
||||
* @param {PresenceData} data Data for the presence
|
||||
* @returns {ClientPresence}
|
||||
* @example
|
||||
* // Set the client user's presence
|
||||
* client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' });
|
||||
*/
|
||||
setPresence(data) {
|
||||
return this.client.presence.set(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* A user's status. Must be one of:
|
||||
* * `online`
|
||||
* * `idle`
|
||||
* * `invisible`
|
||||
* * `dnd` (do not disturb)
|
||||
* @typedef {string} PresenceStatusData
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the status of the client user.
|
||||
* @param {PresenceStatusData} status Status to change to
|
||||
* @param {number|number[]} [shardId] Shard id(s) to have the activity set on
|
||||
* @returns {ClientPresence}
|
||||
* @example
|
||||
* // Set the client user's status
|
||||
* client.user.setStatus('idle');
|
||||
*/
|
||||
setStatus(status, shardId) {
|
||||
return this.setPresence({ status, shardId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for setting an activity.
|
||||
* @typedef {Object} ActivityOptions
|
||||
* @property {string} [name] Name of the activity
|
||||
* @property {string} [url] Twitch / YouTube stream URL
|
||||
* @property {ActivityType|number} [type] Type of the activity
|
||||
* @property {number|number[]} [shardId] Shard Id(s) to have the activity set on
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the activity the client user is playing.
|
||||
* @param {string|ActivityOptions} [name] Activity being played, or options for setting the activity
|
||||
* @param {ActivityOptions} [options] Options for setting the activity
|
||||
* @returns {ClientPresence}
|
||||
* @example
|
||||
* // Set the client user's activity
|
||||
* client.user.setActivity('discord.js', { type: 'WATCHING' });
|
||||
*/
|
||||
setActivity(name, options = {}) {
|
||||
if (!name)
|
||||
return this.setPresence({ activities: [], shardId: options.shardId });
|
||||
|
||||
const activity = Object.assign(
|
||||
{},
|
||||
options,
|
||||
typeof name === 'object' ? name : { name },
|
||||
);
|
||||
return this.setPresence({
|
||||
activities: [activity],
|
||||
shardId: activity.shardId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/removes the AFK flag for the client user.
|
||||
* @param {boolean} [afk=true] Whether or not the user is AFK
|
||||
* @param {number|number[]} [shardId] Shard Id(s) to have the AFK flag set on
|
||||
* @returns {ClientPresence}
|
||||
*/
|
||||
setAFK(afk = true, shardId) {
|
||||
return this.setPresence({ afk, shardId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Friend Request to the user
|
||||
* @returns {Promise<User>} the user object
|
||||
*/
|
||||
async findFriend(username, discriminator) {
|
||||
return await this.client.api
|
||||
.users('@me')
|
||||
.relationships.post({
|
||||
data: {
|
||||
username: username,
|
||||
discriminator: parseInt(discriminator),
|
||||
},
|
||||
})
|
||||
.then((_) => _);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUser;
|
||||
'use strict';
|
||||
|
||||
const User = require('./User');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const { HypeSquadOptions } = require('../util/Constants');
|
||||
const { Util } = require('..');
|
||||
|
||||
/**
|
||||
* Represents the logged in client's Discord user.
|
||||
* @extends {User}
|
||||
*/
|
||||
class ClientUser extends User {
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
if ('verified' in data) {
|
||||
/**
|
||||
* Whether or not this account has been verified
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.verified = data.verified;
|
||||
}
|
||||
|
||||
if ('mfa_enabled' in data) {
|
||||
/**
|
||||
* If the bot's {@link ClientApplication#owner Owner} has MFA enabled on their account
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.mfaEnabled =
|
||||
typeof data.mfa_enabled === 'boolean' ? data.mfa_enabled : null;
|
||||
} else {
|
||||
this.mfaEnabled ??= null;
|
||||
}
|
||||
|
||||
if ('token' in data) this.client.token = data.token;
|
||||
|
||||
// Add (Selfbot)
|
||||
if ('premium' in data) this.nitro = data.premium;
|
||||
/**
|
||||
* Nitro Status
|
||||
* `0`: None
|
||||
* `1`: Classic
|
||||
* `2`: Boost
|
||||
* @external
|
||||
* https://discord.com/developers/docs/resources/user#user-object-premium-types
|
||||
* @type {Number}
|
||||
*/
|
||||
if ('purchased_flags' in data) this.nitroType = data.purchased_flags;
|
||||
if ('phone' in data) this.phoneNumber = data.phone;
|
||||
if ('nsfw_allowed' in data) this.nsfwAllowed = data.nsfw_allowed;
|
||||
if ('email' in data) this.emailAddress = data.email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the client user's presence
|
||||
* @type {ClientPresence}
|
||||
* @readonly
|
||||
*/
|
||||
get presence() {
|
||||
return this.client.presence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data used to edit the logged in client
|
||||
* @typedef {Object} ClientUserEditData
|
||||
* @property {string} [username] The new username
|
||||
* @property {?(BufferResolvable|Base64Resolvable)} [avatar] The new avatar
|
||||
*/
|
||||
|
||||
/**
|
||||
* Edits the logged in client.
|
||||
* @param {ClientUserEditData} data The new data
|
||||
* @returns {Promise<ClientUser>}
|
||||
*/
|
||||
async edit(data) {
|
||||
if (typeof data.avatar !== 'undefined')
|
||||
data.avatar = await DataResolver.resolveImage(data.avatar);
|
||||
if (typeof data.banner !== 'undefined')
|
||||
data.banner = await DataResolver.resolveImage(data.banner);
|
||||
const newData = await this.client.api.users('@me').patch({ data });
|
||||
this.client.token = newData.token;
|
||||
this.client.password = data?.password
|
||||
? data?.password
|
||||
: this.client.password;
|
||||
const { updated } = this.client.actions.UserUpdate.handle(newData);
|
||||
return updated ?? this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username of the logged in client.
|
||||
* <info>Changing usernames in Discord is heavily rate limited, with only 2 requests
|
||||
* every hour. Use this sparingly!</info>
|
||||
* @param {string} username The new username
|
||||
* @param {string} password The password of the account
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set username
|
||||
* client.user.setUsername('discordjs')
|
||||
* .then(user => console.log(`My new username is ${user.username}`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setUsername(username, password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to change a username.');
|
||||
return this.edit({
|
||||
username,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the avatar of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} avatar The new avatar
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set avatar
|
||||
* client.user.setAvatar('./avatar.png')
|
||||
* .then(user => console.log(`New avatar set!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setAvatar(avatar) {
|
||||
return this.edit({ avatar });
|
||||
}
|
||||
/**
|
||||
* Sets the banner of the logged in client.
|
||||
* @param {?(BufferResolvable|Base64Resolvable)} banner The new banner
|
||||
* @returns {Promise<ClientUser>}
|
||||
* @example
|
||||
* // Set banner
|
||||
* client.user.setBanner('./banner.png')
|
||||
* .then(user => console.log(`New banner set!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
setBanner(banner) {
|
||||
if (this.nitroType !== 2)
|
||||
throw new Error(
|
||||
'You must be a Nitro Boosted User to change your banner.',
|
||||
);
|
||||
return this.edit({ banner });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set HyperSquad House
|
||||
* @param {HypeSquadOptions<Number|String>} type
|
||||
* `LEAVE`: 0
|
||||
* `HOUSE_BRAVERY`: 1
|
||||
* `HOUSE_BRILLIANCE`: 2
|
||||
* `HOUSE_BALANCE`: 3
|
||||
* @returns {Promise<void>}
|
||||
* @example
|
||||
* // Set HyperSquad HOUSE_BRAVERY
|
||||
* client.user.setHypeSquad(1); || client.user.setHypeSquad('HOUSE_BRAVERY');
|
||||
* // Leave
|
||||
* client.user.setHypeSquad(0);
|
||||
*/
|
||||
async setHypeSquad(type) {
|
||||
const id = typeof type === 'string' ? HypeSquadOptions[type] : type;
|
||||
if (!id && id !== 0) throw new Error('Invalid HypeSquad type.');
|
||||
if (id !== 0) return await this.client.api.hypesquad.online.post({
|
||||
data: { house_id: id },
|
||||
});
|
||||
else return await this.client.api.hypesquad.online.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Accent color
|
||||
* @param {ColorResolvable} color Color to set
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setAccentColor(color = null) {
|
||||
return this.edit({ accent_color: color ? Util.resolveColor(color) : null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set discriminator
|
||||
* @param {User.discriminator} discriminator It is #1234
|
||||
* @param {string} password The password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setDiscriminator(discriminator, password) {
|
||||
if (!this.nitro) throw new Error('You must be a Nitro User to change your discriminator.');
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to change a discriminator.');
|
||||
return this.edit({
|
||||
discriminator,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set About me
|
||||
* @param {String} bio Bio to set
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setAboutMe(bio = null) {
|
||||
return this.edit({
|
||||
bio,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the email
|
||||
* @param {Email<string>} email Email to change
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setEmail(email, password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to change a email.');
|
||||
return this.edit({
|
||||
email,
|
||||
password: this.client.password ? this.client.password : password,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new password
|
||||
* @param {string} oldPassword Old password
|
||||
* @param {string} newPassword New password to set
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setPassword(oldPassword, newPassword) {
|
||||
if (!oldPassword && !this.client.password)
|
||||
throw new Error('A password is required to change a password.');
|
||||
if (!newPassword) throw new Error('New password is required.');
|
||||
return this.edit({
|
||||
password: this.client.password ? this.client.password : oldPassword,
|
||||
new_password: newPassword,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable account
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async disableAccount(password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to disable an account.');
|
||||
return await this.client.api.users['@me'].disable.post({
|
||||
data: {
|
||||
password: this.client.password ? this.client.password : password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account. Warning: Cannot be changed once used!
|
||||
* @param {string} password Password of the account
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async deleteAccount(password) {
|
||||
if (!password && !this.client.password)
|
||||
throw new Error('A password is required to delete an account.');
|
||||
return await this.client.api.users['@me'].delete.post({
|
||||
data: {
|
||||
password: this.client.password ? this.client.password : password,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for setting activities
|
||||
* @typedef {Object} ActivitiesOptions
|
||||
* @property {string} [name] Name of the activity
|
||||
* @property {ActivityType|number} [type] Type of the activity
|
||||
* @property {string} [url] Twitch / YouTube stream URL
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data resembling a raw Discord presence.
|
||||
* @typedef {Object} PresenceData
|
||||
* @property {PresenceStatusData} [status] Status of the user
|
||||
* @property {boolean} [afk] Whether the user is AFK
|
||||
* @property {ActivitiesOptions[]} [activities] Activity the user is playing
|
||||
* @property {number|number[]} [shardId] Shard id(s) to have the activity set on
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the full presence of the client user.
|
||||
* @param {PresenceData} data Data for the presence
|
||||
* @returns {ClientPresence}
|
||||
* @example
|
||||
* // Set the client user's presence
|
||||
* client.user.setPresence({ activities: [{ name: 'with discord.js' }], status: 'idle' });
|
||||
*/
|
||||
setPresence(data) {
|
||||
return this.client.presence.set(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* A user's status. Must be one of:
|
||||
* * `online`
|
||||
* * `idle`
|
||||
* * `invisible`
|
||||
* * `dnd` (do not disturb)
|
||||
* @typedef {string} PresenceStatusData
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the status of the client user.
|
||||
* @param {PresenceStatusData} status Status to change to
|
||||
* @param {number|number[]} [shardId] Shard id(s) to have the activity set on
|
||||
* @returns {ClientPresence}
|
||||
* @example
|
||||
* // Set the client user's status
|
||||
* client.user.setStatus('idle');
|
||||
*/
|
||||
setStatus(status, shardId) {
|
||||
return this.setPresence({ status, shardId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for setting an activity.
|
||||
* @typedef {Object} ActivityOptions
|
||||
* @property {string} [name] Name of the activity
|
||||
* @property {string} [url] Twitch / YouTube stream URL
|
||||
* @property {ActivityType|number} [type] Type of the activity
|
||||
* @property {number|number[]} [shardId] Shard Id(s) to have the activity set on
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the activity the client user is playing.
|
||||
* @param {string|ActivityOptions} [name] Activity being played, or options for setting the activity
|
||||
* @param {ActivityOptions} [options] Options for setting the activity
|
||||
* @returns {ClientPresence}
|
||||
* @example
|
||||
* // Set the client user's activity
|
||||
* client.user.setActivity('discord.js', { type: 'WATCHING' });
|
||||
*/
|
||||
setActivity(name, options = {}) {
|
||||
if (!name)
|
||||
return this.setPresence({ activities: [], shardId: options.shardId });
|
||||
|
||||
const activity = Object.assign(
|
||||
{},
|
||||
options,
|
||||
typeof name === 'object' ? name : { name },
|
||||
);
|
||||
return this.setPresence({
|
||||
activities: [activity],
|
||||
shardId: activity.shardId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/removes the AFK flag for the client user.
|
||||
* @param {boolean} [afk=true] Whether or not the user is AFK
|
||||
* @param {number|number[]} [shardId] Shard Id(s) to have the AFK flag set on
|
||||
* @returns {ClientPresence}
|
||||
*/
|
||||
setAFK(afk = true, shardId) {
|
||||
return this.setPresence({ afk, shardId });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientUser;
|
||||
|
@ -1,101 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
const { Channel } = require('./Channel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
|
||||
/**
|
||||
* Represents a direct message channel between two users.
|
||||
* @extends {Channel}
|
||||
* @implements {TextBasedChannel}
|
||||
*/
|
||||
class DMChannel extends Channel {
|
||||
constructor(client, data) {
|
||||
super(client, data);
|
||||
|
||||
// Override the channel type so partials have a known type
|
||||
this.type = 'DM';
|
||||
|
||||
/**
|
||||
* A manager of the messages belonging to this channel
|
||||
* @type {MessageManager}
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
if (data.recipients) {
|
||||
/**
|
||||
* The recipient on the other end of the DM
|
||||
* @type {User}
|
||||
*/
|
||||
this.recipient = this.client.users._add(data.recipients[0]);
|
||||
}
|
||||
|
||||
if ('last_message_id' in data) {
|
||||
/**
|
||||
* The channel's last message id, if one was sent
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.lastMessageId = data.last_message_id;
|
||||
}
|
||||
|
||||
if ('last_pin_timestamp' in data) {
|
||||
/**
|
||||
* The timestamp when the last pinned message was pinned, if there was one
|
||||
* @type {?number}
|
||||
*/
|
||||
this.lastPinTimestamp = new Date(data.last_pin_timestamp).getTime();
|
||||
} else {
|
||||
this.lastPinTimestamp ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this DMChannel is a partial
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get partial() {
|
||||
return typeof this.lastMessageId === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch this DMChannel.
|
||||
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
||||
* @returns {Promise<DMChannel>}
|
||||
*/
|
||||
fetch(force = true) {
|
||||
return this.recipient.createDM(force);
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the recipient's mention instead of the
|
||||
* DMChannel object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // Logs: Hello from <@123456789012345678>!
|
||||
* console.log(`Hello from ${channel}!`);
|
||||
*/
|
||||
toString() {
|
||||
return this.recipient.toString();
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
/* eslint-disable no-empty-function */
|
||||
get lastMessage() {}
|
||||
get lastPinAt() {}
|
||||
send() {}
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentCollector() {}
|
||||
awaitMessageComponent() {}
|
||||
// Doesn't work on DM channels; bulkDelete() {}
|
||||
}
|
||||
|
||||
TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete']);
|
||||
|
||||
module.exports = DMChannel;
|
||||
'use strict';
|
||||
|
||||
const { Channel } = require('./Channel');
|
||||
const TextBasedChannel = require('./interfaces/TextBasedChannel');
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
|
||||
/**
|
||||
* Represents a direct message channel between two users.
|
||||
* @extends {Channel}
|
||||
* @implements {TextBasedChannel}
|
||||
*/
|
||||
class DMChannel extends Channel {
|
||||
constructor(client, data) {
|
||||
super(client, data);
|
||||
|
||||
// Override the channel type so partials have a known type
|
||||
this.type = 'DM';
|
||||
|
||||
/**
|
||||
* A manager of the messages belonging to this channel
|
||||
* @type {MessageManager}
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
super._patch(data);
|
||||
|
||||
if (data.recipients) {
|
||||
/**
|
||||
* The recipient on the other end of the DM
|
||||
* @type {User}
|
||||
*/
|
||||
this.recipient = this.client.users._add(data.recipients[0]);
|
||||
}
|
||||
|
||||
if ('last_message_id' in data) {
|
||||
/**
|
||||
* The channel's last message id, if one was sent
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.lastMessageId = data.last_message_id;
|
||||
}
|
||||
|
||||
if ('last_pin_timestamp' in data) {
|
||||
/**
|
||||
* The timestamp when the last pinned message was pinned, if there was one
|
||||
* @type {?number}
|
||||
*/
|
||||
this.lastPinTimestamp = new Date(data.last_pin_timestamp).getTime();
|
||||
} else {
|
||||
this.lastPinTimestamp ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this DMChannel is a partial
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get partial() {
|
||||
return typeof this.lastMessageId === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch this DMChannel.
|
||||
* @param {boolean} [force=true] Whether to skip the cache check and request the API
|
||||
* @returns {Promise<DMChannel>}
|
||||
*/
|
||||
fetch(force = true) {
|
||||
return this.recipient.createDM(force);
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the recipient's mention instead of the
|
||||
* DMChannel object.
|
||||
* @returns {string}
|
||||
* @example
|
||||
* // Logs: Hello from <@123456789012345678>!
|
||||
* console.log(`Hello from ${channel}!`);
|
||||
*/
|
||||
toString() {
|
||||
return this.recipient.toString();
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by TextBasedChannel
|
||||
/* eslint-disable no-empty-function */
|
||||
get lastMessage() {}
|
||||
get lastPinAt() {}
|
||||
send() {}
|
||||
sendTyping() {}
|
||||
createMessageCollector() {}
|
||||
awaitMessages() {}
|
||||
createMessageComponentCollector() {}
|
||||
awaitMessageComponent() {}
|
||||
// Doesn't work on DM channels; bulkDelete() {}
|
||||
}
|
||||
|
||||
TextBasedChannel.applyToClass(DMChannel, true, ['bulkDelete']);
|
||||
|
||||
module.exports = DMChannel;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,101 +1,101 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents an action row containing message components.
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageActionRow extends BaseMessageComponent {
|
||||
/**
|
||||
* Components that can be placed in an action row
|
||||
* * MessageButton
|
||||
* * MessageSelectMenu
|
||||
* @typedef {MessageButton|MessageSelectMenu} MessageActionRowComponent
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for components that can be placed in an action row
|
||||
* * MessageButtonOptions
|
||||
* * MessageSelectMenuOptions
|
||||
* @typedef {MessageButtonOptions|MessageSelectMenuOptions} MessageActionRowComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved into components that can be placed in an action row
|
||||
* * MessageActionRowComponent
|
||||
* * MessageActionRowComponentOptions
|
||||
* @typedef {MessageActionRowComponent|MessageActionRowComponentOptions} MessageActionRowComponentResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageActionRowOptions
|
||||
* @property {MessageActionRowComponentResolvable[]} [components]
|
||||
* The components to place in this action row
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageActionRow|MessageActionRowOptions} [data={}] MessageActionRow to clone or raw data
|
||||
* @param {Client} [client] The client constructing this MessageActionRow, if provided
|
||||
*/
|
||||
constructor(data = {}, client = null) {
|
||||
super({ type: 'ACTION_ROW' });
|
||||
|
||||
/**
|
||||
* The components in this action row
|
||||
* @type {MessageActionRowComponent[]}
|
||||
*/
|
||||
this.components = data.components?.map(c => BaseMessageComponent.create(c, client)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to the action row.
|
||||
* @param {...MessageActionRowComponentResolvable[]} components The components to add
|
||||
* @returns {MessageActionRow}
|
||||
*/
|
||||
addComponents(...components) {
|
||||
this.components.push(...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components of the action row.
|
||||
* @param {...MessageActionRowComponentResolvable[]} components The components to set
|
||||
* @returns {MessageActionRow}
|
||||
*/
|
||||
setComponents(...components) {
|
||||
this.spliceComponents(0, this.components.length, components);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, and inserts components in the action row.
|
||||
* @param {number} index The index to start at
|
||||
* @param {number} deleteCount The number of components to remove
|
||||
* @param {...MessageActionRowComponentResolvable[]} [components] The replacing components
|
||||
* @returns {MessageActionRow}
|
||||
*/
|
||||
spliceComponents(index, deleteCount, ...components) {
|
||||
this.components.splice(index, deleteCount, ...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the action row to a plain object.
|
||||
* @returns {APIMessageComponent} The raw data of this action row
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
components: this.components.map(c => c.toJSON()),
|
||||
type: MessageComponentTypes[this.type],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageActionRow;
|
||||
|
||||
/**
|
||||
* @external APIMessageComponent
|
||||
* @see {@link https://discord.com/developers/docs/interactions/message-components#component-object}
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
|
||||
/**
|
||||
* Represents an action row containing message components.
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageActionRow extends BaseMessageComponent {
|
||||
/**
|
||||
* Components that can be placed in an action row
|
||||
* * MessageButton
|
||||
* * MessageSelectMenu
|
||||
* @typedef {MessageButton|MessageSelectMenu} MessageActionRowComponent
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for components that can be placed in an action row
|
||||
* * MessageButtonOptions
|
||||
* * MessageSelectMenuOptions
|
||||
* @typedef {MessageButtonOptions|MessageSelectMenuOptions} MessageActionRowComponentOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that can be resolved into components that can be placed in an action row
|
||||
* * MessageActionRowComponent
|
||||
* * MessageActionRowComponentOptions
|
||||
* @typedef {MessageActionRowComponent|MessageActionRowComponentOptions} MessageActionRowComponentResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageActionRowOptions
|
||||
* @property {MessageActionRowComponentResolvable[]} [components]
|
||||
* The components to place in this action row
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageActionRow|MessageActionRowOptions} [data={}] MessageActionRow to clone or raw data
|
||||
* @param {Client} [client] The client constructing this MessageActionRow, if provided
|
||||
*/
|
||||
constructor(data = {}, client = null) {
|
||||
super({ type: 'ACTION_ROW' });
|
||||
|
||||
/**
|
||||
* The components in this action row
|
||||
* @type {MessageActionRowComponent[]}
|
||||
*/
|
||||
this.components = data.components?.map(c => BaseMessageComponent.create(c, client)) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to the action row.
|
||||
* @param {...MessageActionRowComponentResolvable[]} components The components to add
|
||||
* @returns {MessageActionRow}
|
||||
*/
|
||||
addComponents(...components) {
|
||||
this.components.push(...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components of the action row.
|
||||
* @param {...MessageActionRowComponentResolvable[]} components The components to set
|
||||
* @returns {MessageActionRow}
|
||||
*/
|
||||
setComponents(...components) {
|
||||
this.spliceComponents(0, this.components.length, components);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, and inserts components in the action row.
|
||||
* @param {number} index The index to start at
|
||||
* @param {number} deleteCount The number of components to remove
|
||||
* @param {...MessageActionRowComponentResolvable[]} [components] The replacing components
|
||||
* @returns {MessageActionRow}
|
||||
*/
|
||||
spliceComponents(index, deleteCount, ...components) {
|
||||
this.components.splice(index, deleteCount, ...components.flat(Infinity).map(c => BaseMessageComponent.create(c)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the action row to a plain object.
|
||||
* @returns {APIMessageComponent} The raw data of this action row
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
components: this.components.map(c => c.toJSON()),
|
||||
type: MessageComponentTypes[this.type],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageActionRow;
|
||||
|
||||
/**
|
||||
* @external APIMessageComponent
|
||||
* @see {@link https://discord.com/developers/docs/interactions/message-components#component-object}
|
||||
*/
|
||||
|
@ -1,194 +1,193 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { Message } = require('./Message');
|
||||
const { RangeError } = require('../errors');
|
||||
const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a button message component.
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageButton extends BaseMessageComponent {
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageButtonOptions
|
||||
* @property {string} [label] The text to be displayed on this button
|
||||
* @property {string} [customId] A unique string to be sent in the interaction when clicked
|
||||
* @property {MessageButtonStyleResolvable} [style] The style of this button
|
||||
* @property {EmojiIdentifierResolvable} [emoji] The emoji to be displayed to the left of the text
|
||||
* @property {string} [url] Optional URL for link-style buttons
|
||||
* @property {boolean} [disabled=false] Disables the button to prevent interactions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageButton|MessageButtonOptions} [data={}] MessageButton to clone or raw data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
super({ type: 'BUTTON' });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The text to be displayed on this button
|
||||
* @type {?string}
|
||||
*/
|
||||
this.label = data.label ?? null;
|
||||
|
||||
/**
|
||||
* A unique string to be sent in the interaction when clicked
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customId = data.custom_id ?? data.customId ?? null;
|
||||
|
||||
/**
|
||||
* The style of this button
|
||||
* @type {?MessageButtonStyle}
|
||||
*/
|
||||
this.style = data.style ? MessageButton.resolveStyle(data.style) : null;
|
||||
|
||||
/**
|
||||
* Emoji for this button
|
||||
* @type {?RawEmoji}
|
||||
*/
|
||||
this.emoji = data.emoji ? Util.resolvePartialEmoji(data.emoji) : null;
|
||||
|
||||
/**
|
||||
* The URL this button links to, if it is a Link style button
|
||||
* @type {?string}
|
||||
*/
|
||||
this.url = data.url ?? null;
|
||||
|
||||
/**
|
||||
* Whether this button is currently disabled
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.disabled = data.disabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id for this button
|
||||
* @param {string} customId A unique string to be sent in the interaction when clicked
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setCustomId(customId) {
|
||||
this.customId = Util.verifyString(customId, RangeError, 'BUTTON_CUSTOM_ID');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the interactive status of the button
|
||||
* @param {boolean} [disabled=true] Whether this button should be disabled
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setDisabled(disabled = true) {
|
||||
this.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the emoji of this button
|
||||
* @param {EmojiIdentifierResolvable} emoji The emoji to be displayed on this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setEmoji(emoji) {
|
||||
this.emoji = Util.resolvePartialEmoji(emoji);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label of this button
|
||||
* @param {string} label The text to be displayed on this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setLabel(label) {
|
||||
this.label = Util.verifyString(label, RangeError, 'BUTTON_LABEL');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style of this button
|
||||
* @param {MessageButtonStyleResolvable} style The style of this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setStyle(style) {
|
||||
this.style = MessageButton.resolveStyle(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this button.
|
||||
* <info>MessageButton#style must be LINK when setting a URL</info>
|
||||
* @param {string} url The URL of this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setURL(url) {
|
||||
this.url = Util.verifyString(url, RangeError, 'BUTTON_URL');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the button to a plain object.
|
||||
* @returns {APIMessageButton} The raw data of this button
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
custom_id: this.customId,
|
||||
disabled: this.disabled,
|
||||
emoji: this.emoji,
|
||||
label: this.label,
|
||||
style: MessageButtonStyles[this.style],
|
||||
type: MessageComponentTypes[this.type],
|
||||
url: this.url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a MessageButtonStyle. This can be
|
||||
* * MessageButtonStyle
|
||||
* * number
|
||||
* @typedef {number|MessageButtonStyle} MessageButtonStyleResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves the style of a button
|
||||
* @param {MessageButtonStyleResolvable} style The style to resolve
|
||||
* @returns {MessageButtonStyle}
|
||||
* @private
|
||||
*/
|
||||
static resolveStyle(style) {
|
||||
return typeof style === 'string' ? style : MessageButtonStyles[style];
|
||||
}
|
||||
// Patch Click
|
||||
/**
|
||||
* Click the button
|
||||
* @param {Message} message Discord Message
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async click(message) {
|
||||
if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message");
|
||||
if (!this.customId || this.style == 5 || this.disabled) return false; // Button URL, Disabled
|
||||
await message.client.api.interactions.post(
|
||||
{
|
||||
data: {
|
||||
type: 3, // ?
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
data: {
|
||||
component_type: 2, // Button
|
||||
custom_id: this.customId
|
||||
},
|
||||
message_flags: message.flags.bitfield,
|
||||
}
|
||||
}
|
||||
)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageButton;
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { Message } = require('./Message');
|
||||
const { RangeError } = require('../errors');
|
||||
const { MessageButtonStyles, MessageComponentTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a button message component.
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageButton extends BaseMessageComponent {
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageButtonOptions
|
||||
* @property {string} [label] The text to be displayed on this button
|
||||
* @property {string} [customId] A unique string to be sent in the interaction when clicked
|
||||
* @property {MessageButtonStyleResolvable} [style] The style of this button
|
||||
* @property {EmojiIdentifierResolvable} [emoji] The emoji to be displayed to the left of the text
|
||||
* @property {string} [url] Optional URL for link-style buttons
|
||||
* @property {boolean} [disabled=false] Disables the button to prevent interactions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageButton|MessageButtonOptions} [data={}] MessageButton to clone or raw data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
super({ type: 'BUTTON' });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* The text to be displayed on this button
|
||||
* @type {?string}
|
||||
*/
|
||||
this.label = data.label ?? null;
|
||||
|
||||
/**
|
||||
* A unique string to be sent in the interaction when clicked
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customId = data.custom_id ?? data.customId ?? null;
|
||||
|
||||
/**
|
||||
* The style of this button
|
||||
* @type {?MessageButtonStyle}
|
||||
*/
|
||||
this.style = data.style ? MessageButton.resolveStyle(data.style) : null;
|
||||
|
||||
/**
|
||||
* Emoji for this button
|
||||
* @type {?RawEmoji}
|
||||
*/
|
||||
this.emoji = data.emoji ? Util.resolvePartialEmoji(data.emoji) : null;
|
||||
|
||||
/**
|
||||
* The URL this button links to, if it is a Link style button
|
||||
* @type {?string}
|
||||
*/
|
||||
this.url = data.url ?? null;
|
||||
|
||||
/**
|
||||
* Whether this button is currently disabled
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.disabled = data.disabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id for this button
|
||||
* @param {string} customId A unique string to be sent in the interaction when clicked
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setCustomId(customId) {
|
||||
this.customId = Util.verifyString(customId, RangeError, 'BUTTON_CUSTOM_ID');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the interactive status of the button
|
||||
* @param {boolean} [disabled=true] Whether this button should be disabled
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setDisabled(disabled = true) {
|
||||
this.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the emoji of this button
|
||||
* @param {EmojiIdentifierResolvable} emoji The emoji to be displayed on this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setEmoji(emoji) {
|
||||
this.emoji = Util.resolvePartialEmoji(emoji);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label of this button
|
||||
* @param {string} label The text to be displayed on this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setLabel(label) {
|
||||
this.label = Util.verifyString(label, RangeError, 'BUTTON_LABEL');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style of this button
|
||||
* @param {MessageButtonStyleResolvable} style The style of this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setStyle(style) {
|
||||
this.style = MessageButton.resolveStyle(style);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this button.
|
||||
* <info>MessageButton#style must be LINK when setting a URL</info>
|
||||
* @param {string} url The URL of this button
|
||||
* @returns {MessageButton}
|
||||
*/
|
||||
setURL(url) {
|
||||
this.url = Util.verifyString(url, RangeError, 'BUTTON_URL');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the button to a plain object.
|
||||
* @returns {APIMessageButton} The raw data of this button
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
custom_id: this.customId,
|
||||
disabled: this.disabled,
|
||||
emoji: this.emoji,
|
||||
label: this.label,
|
||||
style: MessageButtonStyles[this.style],
|
||||
type: MessageComponentTypes[this.type],
|
||||
url: this.url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Data that can be resolved to a MessageButtonStyle. This can be
|
||||
* * MessageButtonStyle
|
||||
* * number
|
||||
* @typedef {number|MessageButtonStyle} MessageButtonStyleResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves the style of a button
|
||||
* @param {MessageButtonStyleResolvable} style The style to resolve
|
||||
* @returns {MessageButtonStyle}
|
||||
* @private
|
||||
*/
|
||||
static resolveStyle(style) {
|
||||
return typeof style === 'string' ? style : MessageButtonStyles[style];
|
||||
}
|
||||
// Patch Click
|
||||
/**
|
||||
* Click the button
|
||||
* @param {Message} message Discord Message
|
||||
* @returns {boolean}
|
||||
*/
|
||||
async click(message) {
|
||||
if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message");
|
||||
if (!this.customId || this.style == 5 || this.disabled) return false; // Button URL, Disabled
|
||||
await message.client.api.interactions.post(
|
||||
{
|
||||
data: {
|
||||
type: 3, // ?
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
data: {
|
||||
component_type: 2, // Button
|
||||
custom_id: this.customId
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageButton;
|
||||
|
@ -1,235 +1,235 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { ChannelTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Keeps track of mentions in a {@link Message}.
|
||||
*/
|
||||
class MessageMentions {
|
||||
constructor(message, users, roles, everyone, crosspostedChannels, repliedUser) {
|
||||
/**
|
||||
* The client the message is from
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'client', { value: message.client });
|
||||
|
||||
/**
|
||||
* The guild the message is in
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'guild', { value: message.guild });
|
||||
|
||||
/**
|
||||
* The initial message content
|
||||
* @type {string}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, '_content', { value: message.content });
|
||||
|
||||
/**
|
||||
* Whether `@everyone` or `@here` were mentioned
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.everyone = Boolean(everyone);
|
||||
|
||||
if (users) {
|
||||
if (users instanceof Collection) {
|
||||
/**
|
||||
* Any users that were mentioned
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {Collection<Snowflake, User>}
|
||||
*/
|
||||
this.users = new Collection(users);
|
||||
} else {
|
||||
this.users = new Collection();
|
||||
for (const mention of users) {
|
||||
if (mention.member && message.guild) {
|
||||
message.guild.members._add(Object.assign(mention.member, { user: mention }));
|
||||
}
|
||||
const user = message.client.users._add(mention);
|
||||
this.users.set(user.id, user);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.users = new Collection();
|
||||
}
|
||||
|
||||
if (roles instanceof Collection) {
|
||||
/**
|
||||
* Any roles that were mentioned
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {Collection<Snowflake, Role>}
|
||||
*/
|
||||
this.roles = new Collection(roles);
|
||||
} else if (roles) {
|
||||
this.roles = new Collection();
|
||||
const guild = message.guild;
|
||||
if (guild) {
|
||||
for (const mention of roles) {
|
||||
const role = guild.roles.cache.get(mention);
|
||||
if (role) this.roles.set(role.id, role);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.roles = new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached members for {@link MessageMentions#members}
|
||||
* @type {?Collection<Snowflake, GuildMember>}
|
||||
* @private
|
||||
*/
|
||||
this._members = null;
|
||||
|
||||
/**
|
||||
* Cached channels for {@link MessageMentions#channels}
|
||||
* @type {?Collection<Snowflake, Channel>}
|
||||
* @private
|
||||
*/
|
||||
this._channels = null;
|
||||
|
||||
/**
|
||||
* Crossposted channel data.
|
||||
* @typedef {Object} CrosspostedChannel
|
||||
* @property {Snowflake} channelId The mentioned channel's id
|
||||
* @property {Snowflake} guildId The id of the guild that has the channel
|
||||
* @property {ChannelType} type The channel's type
|
||||
* @property {string} name The channel's name
|
||||
*/
|
||||
|
||||
if (crosspostedChannels) {
|
||||
if (crosspostedChannels instanceof Collection) {
|
||||
/**
|
||||
* A collection of crossposted channels
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {Collection<Snowflake, CrosspostedChannel>}
|
||||
*/
|
||||
this.crosspostedChannels = new Collection(crosspostedChannels);
|
||||
} else {
|
||||
this.crosspostedChannels = new Collection();
|
||||
const channelTypes = Object.keys(ChannelTypes);
|
||||
for (const d of crosspostedChannels) {
|
||||
const type = channelTypes[d.type];
|
||||
this.crosspostedChannels.set(d.id, {
|
||||
channelId: d.id,
|
||||
guildId: d.guild_id,
|
||||
type: type ?? 'UNKNOWN',
|
||||
name: d.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.crosspostedChannels = new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* The author of the message that this message is a reply to
|
||||
* @type {?User}
|
||||
*/
|
||||
this.repliedUser = repliedUser ? this.client.users._add(repliedUser) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any members that were mentioned (only in {@link Guild}s)
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {?Collection<Snowflake, GuildMember>}
|
||||
* @readonly
|
||||
*/
|
||||
get members() {
|
||||
if (this._members) return this._members;
|
||||
if (!this.guild) return null;
|
||||
this._members = new Collection();
|
||||
this.users.forEach(user => {
|
||||
const member = this.guild.members.resolve(user);
|
||||
if (member) this._members.set(member.user.id, member);
|
||||
});
|
||||
return this._members;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any channels that were mentioned
|
||||
* <info>Order as they appear first in the message content</info>
|
||||
* @type {Collection<Snowflake, Channel>}
|
||||
* @readonly
|
||||
*/
|
||||
get channels() {
|
||||
if (this._channels) return this._channels;
|
||||
this._channels = new Collection();
|
||||
let matches;
|
||||
while ((matches = this.constructor.CHANNELS_PATTERN.exec(this._content)) !== null) {
|
||||
const chan = this.client.channels.cache.get(matches[1]);
|
||||
if (chan) this._channels.set(chan.id, chan);
|
||||
}
|
||||
return this._channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to check for a mention.
|
||||
* @typedef {Object} MessageMentionsHasOptions
|
||||
* @property {boolean} [ignoreDirect=false] Whether to ignore direct mentions to the item
|
||||
* @property {boolean} [ignoreRoles=false] Whether to ignore role mentions to a guild member
|
||||
* @property {boolean} [ignoreEveryone=false] Whether to ignore everyone/here mentions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a user, guild member, role, or channel is mentioned.
|
||||
* Takes into account user mentions, role mentions, and `@everyone`/`@here` mentions.
|
||||
* @param {UserResolvable|RoleResolvable|ChannelResolvable} data The User/Role/Channel to check for
|
||||
* @param {MessageMentionsHasOptions} [options] The options for the check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) {
|
||||
if (!ignoreEveryone && this.everyone) return true;
|
||||
const { GuildMember } = require('./GuildMember');
|
||||
if (!ignoreRoles && data instanceof GuildMember) {
|
||||
for (const role of this.roles.values()) if (data.roles.cache.has(role.id)) return true;
|
||||
}
|
||||
|
||||
if (!ignoreDirect) {
|
||||
const id =
|
||||
this.guild?.roles.resolveId(data) ?? this.client.channels.resolveId(data) ?? this.client.users.resolveId(data);
|
||||
|
||||
return typeof id === 'string' && (this.users.has(id) || this.channels.has(id) || this.roles.has(id));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Util.flatten(this, {
|
||||
members: true,
|
||||
channels: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches `@everyone` and `@here`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.EVERYONE_PATTERN = /@(everyone|here)/g;
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches user mentions like `<@81440962496172032>`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.USERS_PATTERN = /<@!?(\d{17,19})>/g;
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches role mentions like `<@&297577916114403338>`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.ROLES_PATTERN = /<@&(\d{17,19})>/g;
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches channel mentions like `<#222079895583457280>`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.CHANNELS_PATTERN = /<#(\d{17,19})>/g;
|
||||
|
||||
module.exports = MessageMentions;
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { ChannelTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Keeps track of mentions in a {@link Message}.
|
||||
*/
|
||||
class MessageMentions {
|
||||
constructor(message, users, roles, everyone, crosspostedChannels, repliedUser) {
|
||||
/**
|
||||
* The client the message is from
|
||||
* @type {Client}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'client', { value: message.client });
|
||||
|
||||
/**
|
||||
* The guild the message is in
|
||||
* @type {?Guild}
|
||||
* @readonly
|
||||
*/
|
||||
Object.defineProperty(this, 'guild', { value: message.guild });
|
||||
|
||||
/**
|
||||
* The initial message content
|
||||
* @type {string}
|
||||
* @readonly
|
||||
* @private
|
||||
*/
|
||||
Object.defineProperty(this, '_content', { value: message.content });
|
||||
|
||||
/**
|
||||
* Whether `@everyone` or `@here` were mentioned
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.everyone = Boolean(everyone);
|
||||
|
||||
if (users) {
|
||||
if (users instanceof Collection) {
|
||||
/**
|
||||
* Any users that were mentioned
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {Collection<Snowflake, User>}
|
||||
*/
|
||||
this.users = new Collection(users);
|
||||
} else {
|
||||
this.users = new Collection();
|
||||
for (const mention of users) {
|
||||
if (mention.member && message.guild) {
|
||||
message.guild.members._add(Object.assign(mention.member, { user: mention }));
|
||||
}
|
||||
const user = message.client.users._add(mention);
|
||||
this.users.set(user.id, user);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.users = new Collection();
|
||||
}
|
||||
|
||||
if (roles instanceof Collection) {
|
||||
/**
|
||||
* Any roles that were mentioned
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {Collection<Snowflake, Role>}
|
||||
*/
|
||||
this.roles = new Collection(roles);
|
||||
} else if (roles) {
|
||||
this.roles = new Collection();
|
||||
const guild = message.guild;
|
||||
if (guild) {
|
||||
for (const mention of roles) {
|
||||
const role = guild.roles.cache.get(mention);
|
||||
if (role) this.roles.set(role.id, role);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.roles = new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cached members for {@link MessageMentions#members}
|
||||
* @type {?Collection<Snowflake, GuildMember>}
|
||||
* @private
|
||||
*/
|
||||
this._members = null;
|
||||
|
||||
/**
|
||||
* Cached channels for {@link MessageMentions#channels}
|
||||
* @type {?Collection<Snowflake, Channel>}
|
||||
* @private
|
||||
*/
|
||||
this._channels = null;
|
||||
|
||||
/**
|
||||
* Crossposted channel data.
|
||||
* @typedef {Object} CrosspostedChannel
|
||||
* @property {Snowflake} channelId The mentioned channel's id
|
||||
* @property {Snowflake} guildId The id of the guild that has the channel
|
||||
* @property {ChannelType} type The channel's type
|
||||
* @property {string} name The channel's name
|
||||
*/
|
||||
|
||||
if (crosspostedChannels) {
|
||||
if (crosspostedChannels instanceof Collection) {
|
||||
/**
|
||||
* A collection of crossposted channels
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {Collection<Snowflake, CrosspostedChannel>}
|
||||
*/
|
||||
this.crosspostedChannels = new Collection(crosspostedChannels);
|
||||
} else {
|
||||
this.crosspostedChannels = new Collection();
|
||||
const channelTypes = Object.keys(ChannelTypes);
|
||||
for (const d of crosspostedChannels) {
|
||||
const type = channelTypes[d.type];
|
||||
this.crosspostedChannels.set(d.id, {
|
||||
channelId: d.id,
|
||||
guildId: d.guild_id,
|
||||
type: type ?? 'UNKNOWN',
|
||||
name: d.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.crosspostedChannels = new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* The author of the message that this message is a reply to
|
||||
* @type {?User}
|
||||
*/
|
||||
this.repliedUser = repliedUser ? this.client.users._add(repliedUser) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any members that were mentioned (only in {@link Guild}s)
|
||||
* <info>Order as received from the API, not as they appear in the message content</info>
|
||||
* @type {?Collection<Snowflake, GuildMember>}
|
||||
* @readonly
|
||||
*/
|
||||
get members() {
|
||||
if (this._members) return this._members;
|
||||
if (!this.guild) return null;
|
||||
this._members = new Collection();
|
||||
this.users.forEach(user => {
|
||||
const member = this.guild.members.resolve(user);
|
||||
if (member) this._members.set(member.user.id, member);
|
||||
});
|
||||
return this._members;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any channels that were mentioned
|
||||
* <info>Order as they appear first in the message content</info>
|
||||
* @type {Collection<Snowflake, Channel>}
|
||||
* @readonly
|
||||
*/
|
||||
get channels() {
|
||||
if (this._channels) return this._channels;
|
||||
this._channels = new Collection();
|
||||
let matches;
|
||||
while ((matches = this.constructor.CHANNELS_PATTERN.exec(this._content)) !== null) {
|
||||
const chan = this.client.channels.cache.get(matches[1]);
|
||||
if (chan) this._channels.set(chan.id, chan);
|
||||
}
|
||||
return this._channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to check for a mention.
|
||||
* @typedef {Object} MessageMentionsHasOptions
|
||||
* @property {boolean} [ignoreDirect=false] Whether to ignore direct mentions to the item
|
||||
* @property {boolean} [ignoreRoles=false] Whether to ignore role mentions to a guild member
|
||||
* @property {boolean} [ignoreEveryone=false] Whether to ignore everyone/here mentions
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a user, guild member, role, or channel is mentioned.
|
||||
* Takes into account user mentions, role mentions, and `@everyone`/`@here` mentions.
|
||||
* @param {UserResolvable|RoleResolvable|ChannelResolvable} data The User/Role/Channel to check for
|
||||
* @param {MessageMentionsHasOptions} [options] The options for the check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(data, { ignoreDirect = false, ignoreRoles = false, ignoreEveryone = false } = {}) {
|
||||
if (!ignoreEveryone && this.everyone) return true;
|
||||
const { GuildMember } = require('./GuildMember');
|
||||
if (!ignoreRoles && data instanceof GuildMember) {
|
||||
for (const role of this.roles.values()) if (data.roles.cache.has(role.id)) return true;
|
||||
}
|
||||
|
||||
if (!ignoreDirect) {
|
||||
const id =
|
||||
this.guild?.roles.resolveId(data) ?? this.client.channels.resolveId(data) ?? this.client.users.resolveId(data);
|
||||
|
||||
return typeof id === 'string' && (this.users.has(id) || this.channels.has(id) || this.roles.has(id));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return Util.flatten(this, {
|
||||
members: true,
|
||||
channels: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches `@everyone` and `@here`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.EVERYONE_PATTERN = /@(everyone|here)/g;
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches user mentions like `<@81440962496172032>`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.USERS_PATTERN = /<@!?(\d{17,19})>/g;
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches role mentions like `<@&297577916114403338>`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.ROLES_PATTERN = /<@&(\d{17,19})>/g;
|
||||
|
||||
/**
|
||||
* Regular expression that globally matches channel mentions like `<#222079895583457280>`
|
||||
* @type {RegExp}
|
||||
*/
|
||||
MessageMentions.CHANNELS_PATTERN = /<#(\d{17,19})>/g;
|
||||
|
||||
module.exports = MessageMentions;
|
||||
|
@ -1,313 +1,313 @@
|
||||
'use strict';
|
||||
|
||||
const { Buffer } = require('node:buffer');
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const MessageEmbed = require('./MessageEmbed');
|
||||
const WebEmbed = require('./WebEmbed');
|
||||
const { RangeError } = require('../errors');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const MessageFlags = require('../util/MessageFlags');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a message to be sent to the API.
|
||||
*/
|
||||
class MessagePayload {
|
||||
/**
|
||||
* @param {MessageTarget} target The target for this message to be sent to
|
||||
* @param {MessageOptions|WebhookMessageOptions} options Options passed in from send
|
||||
*/
|
||||
constructor(target, options) {
|
||||
/**
|
||||
* The target for this message to be sent to
|
||||
* @type {MessageTarget}
|
||||
*/
|
||||
this.target = target;
|
||||
|
||||
/**
|
||||
* Options passed in from send
|
||||
* @type {MessageOptions|WebhookMessageOptions}
|
||||
*/
|
||||
this.options = options;
|
||||
|
||||
/**
|
||||
* Data sendable to the API
|
||||
* @type {?APIMessage}
|
||||
*/
|
||||
this.data = null;
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageFile
|
||||
* @property {Buffer|string|Stream} attachment The original attachment that generated this file
|
||||
* @property {string} name The name of this file
|
||||
* @property {Buffer|Stream} file The file to be sent to the API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Files sendable to the API
|
||||
* @type {?MessageFile[]}
|
||||
*/
|
||||
this.files = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isWebhook() {
|
||||
const Webhook = require('./Webhook');
|
||||
const WebhookClient = require('../client/WebhookClient');
|
||||
return this.target instanceof Webhook || this.target instanceof WebhookClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link User}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isUser() {
|
||||
const User = require('./User');
|
||||
const { GuildMember } = require('./GuildMember');
|
||||
return this.target instanceof User || this.target instanceof GuildMember;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link Message}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isMessage() {
|
||||
const { Message } = require('./Message');
|
||||
return this.target instanceof Message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link MessageManager}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isMessageManager() {
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
return this.target instanceof MessageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is an {@link Interaction} or an {@link InteractionWebhook}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isInteraction() {
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionWebhook = require('./InteractionWebhook');
|
||||
return this.target instanceof Interaction || this.target instanceof InteractionWebhook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the content of this message.
|
||||
* @returns {?string}
|
||||
*/
|
||||
makeContent() {
|
||||
let content;
|
||||
if (this.options.content === null) {
|
||||
content = '';
|
||||
} else if (typeof this.options.content !== 'undefined') {
|
||||
content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', false);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves data.
|
||||
* @returns {MessagePayload}
|
||||
*/
|
||||
async resolveData() {
|
||||
if (this.data) return this;
|
||||
const isInteraction = this.isInteraction;
|
||||
const isWebhook = this.isWebhook;
|
||||
|
||||
let content = this.makeContent();
|
||||
const tts = Boolean(this.options.tts);
|
||||
|
||||
let nonce;
|
||||
if (typeof this.options.nonce !== 'undefined') {
|
||||
nonce = this.options.nonce;
|
||||
// eslint-disable-next-line max-len
|
||||
if (typeof nonce === 'number' ? !Number.isInteger(nonce) : typeof nonce !== 'string') {
|
||||
throw new RangeError('MESSAGE_NONCE_TYPE');
|
||||
}
|
||||
}
|
||||
|
||||
const components = this.options.components?.map(c => BaseMessageComponent.create(c).toJSON());
|
||||
|
||||
let username;
|
||||
let avatarURL;
|
||||
if (isWebhook) {
|
||||
username = this.options.username ?? this.target.name;
|
||||
if (this.options.avatarURL) avatarURL = this.options.avatarURL;
|
||||
}
|
||||
|
||||
let flags;
|
||||
if (this.isMessage || this.isMessageManager) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags?.bitfield;
|
||||
} else if (isInteraction && this.options.ephemeral) {
|
||||
flags = MessageFlags.FLAGS.EPHEMERAL;
|
||||
}
|
||||
|
||||
let allowedMentions =
|
||||
typeof this.options.allowedMentions === 'undefined'
|
||||
? this.target.client.options.allowedMentions
|
||||
: this.options.allowedMentions;
|
||||
|
||||
if (allowedMentions) {
|
||||
allowedMentions = Util.cloneObject(allowedMentions);
|
||||
allowedMentions.replied_user = allowedMentions.repliedUser;
|
||||
delete allowedMentions.repliedUser;
|
||||
}
|
||||
|
||||
let message_reference;
|
||||
if (typeof this.options.reply === 'object') {
|
||||
const reference = this.options.reply.messageReference;
|
||||
const message_id = this.isMessage ? reference.id ?? reference : this.target.messages.resolveId(reference);
|
||||
if (message_id) {
|
||||
message_reference = {
|
||||
message_id,
|
||||
fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const attachments = this.options.files?.map((file, index) => ({
|
||||
id: index.toString(),
|
||||
description: file.description,
|
||||
}));
|
||||
if (Array.isArray(this.options.attachments)) {
|
||||
this.options.attachments.push(...(attachments ?? []));
|
||||
} else {
|
||||
this.options.attachments = attachments;
|
||||
}
|
||||
|
||||
if (this.options.embeds) {
|
||||
if (!Array.isArray(this.options.embeds)) {
|
||||
this.options.embeds = [this.options.embeds];
|
||||
}
|
||||
|
||||
const webembeds = this.options.embeds.filter(
|
||||
(e) => !(e instanceof MessageEmbed),
|
||||
);
|
||||
this.options.embeds = this.options.embeds.filter(e => e instanceof MessageEmbed);
|
||||
|
||||
if (webembeds.length > 0) {
|
||||
// add hidden embed link
|
||||
content += `\n${WebEmbed.hiddenEmbed} \n`;
|
||||
if (webembeds.length > 1) {
|
||||
console.warn('Multiple webembeds are not supported, this will be ignored.');
|
||||
}
|
||||
// const embed = webembeds[0];
|
||||
for (const webE of webembeds) {
|
||||
const data = await webE.toMessage();
|
||||
content += `\n${data}`;
|
||||
}
|
||||
}
|
||||
// Check content
|
||||
if (content.length > 2000) {
|
||||
console.warn(`[WARN] Content is longer than 2000 characters.`);
|
||||
}
|
||||
if (content.length > 4000) { // Max length if user has nitro boost
|
||||
throw new RangeError('MESSAGE_EMBED_LINK_LENGTH');
|
||||
}
|
||||
}
|
||||
|
||||
this.data = {
|
||||
content,
|
||||
tts,
|
||||
nonce,
|
||||
embeds: this.options.embeds?.map(embed => new MessageEmbed(embed).toJSON()),
|
||||
components,
|
||||
username,
|
||||
avatar_url: avatarURL,
|
||||
allowed_mentions:
|
||||
typeof content === 'undefined' && typeof message_reference === 'undefined' ? undefined : allowedMentions,
|
||||
flags,
|
||||
message_reference,
|
||||
attachments: this.options.attachments,
|
||||
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves files.
|
||||
* @returns {Promise<MessagePayload>}
|
||||
*/
|
||||
async resolveFiles() {
|
||||
if (this.files) return this;
|
||||
|
||||
this.files = await Promise.all(this.options.files?.map(file => this.constructor.resolveFile(file)) ?? []);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a single file into an object sendable to the API.
|
||||
* @param {BufferResolvable|Stream|FileOptions|MessageAttachment} fileLike Something that could be resolved to a file
|
||||
* @returns {Promise<MessageFile>}
|
||||
*/
|
||||
static async resolveFile(fileLike) {
|
||||
let attachment;
|
||||
let name;
|
||||
|
||||
const findName = thing => {
|
||||
if (typeof thing === 'string') {
|
||||
return Util.basename(thing);
|
||||
}
|
||||
|
||||
if (thing.path) {
|
||||
return Util.basename(thing.path);
|
||||
}
|
||||
|
||||
return 'file.jpg';
|
||||
};
|
||||
|
||||
const ownAttachment =
|
||||
typeof fileLike === 'string' || fileLike instanceof Buffer || typeof fileLike.pipe === 'function';
|
||||
if (ownAttachment) {
|
||||
attachment = fileLike;
|
||||
name = findName(attachment);
|
||||
} else {
|
||||
attachment = fileLike.attachment;
|
||||
name = fileLike.name ?? findName(attachment);
|
||||
}
|
||||
|
||||
const resource = await DataResolver.resolveFile(attachment);
|
||||
return { attachment, name, file: resource };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MessagePayload} from user-level arguments.
|
||||
* @param {MessageTarget} target Target to send to
|
||||
* @param {string|MessageOptions|WebhookMessageOptions} options Options or content to use
|
||||
* @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto specified options
|
||||
* @returns {MessagePayload}
|
||||
*/
|
||||
static create(target, options, extra = {}) {
|
||||
return new this(
|
||||
target,
|
||||
typeof options !== 'object' || options === null ? { content: options, ...extra } : { ...options, ...extra },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessagePayload;
|
||||
|
||||
/**
|
||||
* A target for a message.
|
||||
* @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
|
||||
* Message|MessageManager} MessageTarget
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external APIMessage
|
||||
* @see {@link https://discord.com/developers/docs/resources/channel#message-object}
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const { Buffer } = require('node:buffer');
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const MessageEmbed = require('./MessageEmbed');
|
||||
const WebEmbed = require('./WebEmbed');
|
||||
const { RangeError } = require('../errors');
|
||||
const DataResolver = require('../util/DataResolver');
|
||||
const MessageFlags = require('../util/MessageFlags');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Represents a message to be sent to the API.
|
||||
*/
|
||||
class MessagePayload {
|
||||
/**
|
||||
* @param {MessageTarget} target The target for this message to be sent to
|
||||
* @param {MessageOptions|WebhookMessageOptions} options Options passed in from send
|
||||
*/
|
||||
constructor(target, options) {
|
||||
/**
|
||||
* The target for this message to be sent to
|
||||
* @type {MessageTarget}
|
||||
*/
|
||||
this.target = target;
|
||||
|
||||
/**
|
||||
* Options passed in from send
|
||||
* @type {MessageOptions|WebhookMessageOptions}
|
||||
*/
|
||||
this.options = options;
|
||||
|
||||
/**
|
||||
* Data sendable to the API
|
||||
* @type {?APIMessage}
|
||||
*/
|
||||
this.data = null;
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageFile
|
||||
* @property {Buffer|string|Stream} attachment The original attachment that generated this file
|
||||
* @property {string} name The name of this file
|
||||
* @property {Buffer|Stream} file The file to be sent to the API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Files sendable to the API
|
||||
* @type {?MessageFile[]}
|
||||
*/
|
||||
this.files = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link Webhook} or a {@link WebhookClient}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isWebhook() {
|
||||
const Webhook = require('./Webhook');
|
||||
const WebhookClient = require('../client/WebhookClient');
|
||||
return this.target instanceof Webhook || this.target instanceof WebhookClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link User}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isUser() {
|
||||
const User = require('./User');
|
||||
const { GuildMember } = require('./GuildMember');
|
||||
return this.target instanceof User || this.target instanceof GuildMember;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link Message}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isMessage() {
|
||||
const { Message } = require('./Message');
|
||||
return this.target instanceof Message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is a {@link MessageManager}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isMessageManager() {
|
||||
const MessageManager = require('../managers/MessageManager');
|
||||
return this.target instanceof MessageManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the target is an {@link Interaction} or an {@link InteractionWebhook}
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isInteraction() {
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionWebhook = require('./InteractionWebhook');
|
||||
return this.target instanceof Interaction || this.target instanceof InteractionWebhook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the content of this message.
|
||||
* @returns {?string}
|
||||
*/
|
||||
makeContent() {
|
||||
let content;
|
||||
if (this.options.content === null) {
|
||||
content = '';
|
||||
} else if (typeof this.options.content !== 'undefined') {
|
||||
content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', false);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves data.
|
||||
* @returns {MessagePayload}
|
||||
*/
|
||||
async resolveData() {
|
||||
if (this.data) return this;
|
||||
const isInteraction = this.isInteraction;
|
||||
const isWebhook = this.isWebhook;
|
||||
|
||||
let content = this.makeContent();
|
||||
const tts = Boolean(this.options.tts);
|
||||
|
||||
let nonce;
|
||||
if (typeof this.options.nonce !== 'undefined') {
|
||||
nonce = this.options.nonce;
|
||||
// eslint-disable-next-line max-len
|
||||
if (typeof nonce === 'number' ? !Number.isInteger(nonce) : typeof nonce !== 'string') {
|
||||
throw new RangeError('MESSAGE_NONCE_TYPE');
|
||||
}
|
||||
}
|
||||
|
||||
const components = this.options.components?.map(c => BaseMessageComponent.create(c).toJSON());
|
||||
|
||||
let username;
|
||||
let avatarURL;
|
||||
if (isWebhook) {
|
||||
username = this.options.username ?? this.target.name;
|
||||
if (this.options.avatarURL) avatarURL = this.options.avatarURL;
|
||||
}
|
||||
|
||||
let flags;
|
||||
if (this.isMessage || this.isMessageManager) {
|
||||
// eslint-disable-next-line eqeqeq
|
||||
flags = this.options.flags != null ? new MessageFlags(this.options.flags).bitfield : this.target.flags?.bitfield;
|
||||
} else if (isInteraction && this.options.ephemeral) {
|
||||
flags = MessageFlags.FLAGS.EPHEMERAL;
|
||||
}
|
||||
|
||||
let allowedMentions =
|
||||
typeof this.options.allowedMentions === 'undefined'
|
||||
? this.target.client.options.allowedMentions
|
||||
: this.options.allowedMentions;
|
||||
|
||||
if (allowedMentions) {
|
||||
allowedMentions = Util.cloneObject(allowedMentions);
|
||||
allowedMentions.replied_user = allowedMentions.repliedUser;
|
||||
delete allowedMentions.repliedUser;
|
||||
}
|
||||
|
||||
let message_reference;
|
||||
if (typeof this.options.reply === 'object') {
|
||||
const reference = this.options.reply.messageReference;
|
||||
const message_id = this.isMessage ? reference.id ?? reference : this.target.messages.resolveId(reference);
|
||||
if (message_id) {
|
||||
message_reference = {
|
||||
message_id,
|
||||
fail_if_not_exists: this.options.reply.failIfNotExists ?? this.target.client.options.failIfNotExists,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const attachments = this.options.files?.map((file, index) => ({
|
||||
id: index.toString(),
|
||||
description: file.description,
|
||||
}));
|
||||
if (Array.isArray(this.options.attachments)) {
|
||||
this.options.attachments.push(...(attachments ?? []));
|
||||
} else {
|
||||
this.options.attachments = attachments;
|
||||
}
|
||||
|
||||
if (this.options.embeds) {
|
||||
if (!Array.isArray(this.options.embeds)) {
|
||||
this.options.embeds = [this.options.embeds];
|
||||
}
|
||||
|
||||
const webembeds = this.options.embeds.filter(
|
||||
(e) => !(e instanceof MessageEmbed),
|
||||
);
|
||||
this.options.embeds = this.options.embeds.filter(e => e instanceof MessageEmbed);
|
||||
|
||||
if (webembeds.length > 0) {
|
||||
// add hidden embed link
|
||||
content += `\n${WebEmbed.hiddenEmbed} \n`;
|
||||
if (webembeds.length > 1) {
|
||||
console.warn('Multiple webembeds are not supported, this will be ignored.');
|
||||
}
|
||||
// const embed = webembeds[0];
|
||||
for (const webE of webembeds) {
|
||||
const data = await webE.toMessage();
|
||||
content += `\n${data}`;
|
||||
}
|
||||
}
|
||||
// Check content
|
||||
if (content.length > 2000) {
|
||||
console.warn(`[WARN] Content is longer than 2000 characters.`);
|
||||
}
|
||||
if (content.length > 4000) { // Max length if user has nitro boost
|
||||
throw new RangeError('MESSAGE_EMBED_LINK_LENGTH');
|
||||
}
|
||||
}
|
||||
|
||||
this.data = {
|
||||
content,
|
||||
tts,
|
||||
nonce,
|
||||
embeds: this.options.embeds?.map(embed => new MessageEmbed(embed).toJSON()),
|
||||
components,
|
||||
username,
|
||||
avatar_url: avatarURL,
|
||||
allowed_mentions:
|
||||
typeof content === 'undefined' && typeof message_reference === 'undefined' ? undefined : allowedMentions,
|
||||
flags,
|
||||
message_reference,
|
||||
attachments: this.options.attachments,
|
||||
sticker_ids: this.options.stickers?.map(sticker => sticker.id ?? sticker),
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves files.
|
||||
* @returns {Promise<MessagePayload>}
|
||||
*/
|
||||
async resolveFiles() {
|
||||
if (this.files) return this;
|
||||
|
||||
this.files = await Promise.all(this.options.files?.map(file => this.constructor.resolveFile(file)) ?? []);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a single file into an object sendable to the API.
|
||||
* @param {BufferResolvable|Stream|FileOptions|MessageAttachment} fileLike Something that could be resolved to a file
|
||||
* @returns {Promise<MessageFile>}
|
||||
*/
|
||||
static async resolveFile(fileLike) {
|
||||
let attachment;
|
||||
let name;
|
||||
|
||||
const findName = thing => {
|
||||
if (typeof thing === 'string') {
|
||||
return Util.basename(thing);
|
||||
}
|
||||
|
||||
if (thing.path) {
|
||||
return Util.basename(thing.path);
|
||||
}
|
||||
|
||||
return 'file.jpg';
|
||||
};
|
||||
|
||||
const ownAttachment =
|
||||
typeof fileLike === 'string' || fileLike instanceof Buffer || typeof fileLike.pipe === 'function';
|
||||
if (ownAttachment) {
|
||||
attachment = fileLike;
|
||||
name = findName(attachment);
|
||||
} else {
|
||||
attachment = fileLike.attachment;
|
||||
name = fileLike.name ?? findName(attachment);
|
||||
}
|
||||
|
||||
const resource = await DataResolver.resolveFile(attachment);
|
||||
return { attachment, name, file: resource };
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MessagePayload} from user-level arguments.
|
||||
* @param {MessageTarget} target Target to send to
|
||||
* @param {string|MessageOptions|WebhookMessageOptions} options Options or content to use
|
||||
* @param {MessageOptions|WebhookMessageOptions} [extra={}] Extra options to add onto specified options
|
||||
* @returns {MessagePayload}
|
||||
*/
|
||||
static create(target, options, extra = {}) {
|
||||
return new this(
|
||||
target,
|
||||
typeof options !== 'object' || options === null ? { content: options, ...extra } : { ...options, ...extra },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessagePayload;
|
||||
|
||||
/**
|
||||
* A target for a message.
|
||||
* @typedef {TextChannel|DMChannel|User|GuildMember|Webhook|WebhookClient|Interaction|InteractionWebhook|
|
||||
* Message|MessageManager} MessageTarget
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external APIMessage
|
||||
* @see {@link https://discord.com/developers/docs/resources/channel#message-object}
|
||||
*/
|
||||
|
@ -1,255 +1,256 @@
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
const { Message } = require('./Message');
|
||||
|
||||
/**
|
||||
* Represents a select menu message component
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageSelectMenu extends BaseMessageComponent {
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageSelectMenuOptions
|
||||
* @property {string} [customId] A unique string to be sent in the interaction when clicked
|
||||
* @property {string} [placeholder] Custom placeholder text to display when nothing is selected
|
||||
* @property {number} [minValues] The minimum number of selections required
|
||||
* @property {number} [maxValues] The maximum number of selections allowed
|
||||
* @property {MessageSelectOption[]} [options] Options for the select menu
|
||||
* @property {boolean} [disabled=false] Disables the select menu to prevent interactions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageSelectOption
|
||||
* @property {string} label The text to be displayed on this option
|
||||
* @property {string} value The value to be sent for this option
|
||||
* @property {?string} description Optional description to show for this option
|
||||
* @property {?RawEmoji} emoji Emoji to display for this option
|
||||
* @property {boolean} default Render this option as the default selection
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageSelectOptionData
|
||||
* @property {string} label The text to be displayed on this option
|
||||
* @property {string} value The value to be sent for this option
|
||||
* @property {string} [description] Optional description to show for this option
|
||||
* @property {EmojiIdentifierResolvable} [emoji] Emoji to display for this option
|
||||
* @property {boolean} [default] Render this option as the default selection
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageSelectMenu|MessageSelectMenuOptions} [data={}] MessageSelectMenu to clone or raw data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
super({ type: 'SELECT_MENU' });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* A unique string to be sent in the interaction when clicked
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customId = data.custom_id ?? data.customId ?? null;
|
||||
|
||||
/**
|
||||
* Custom placeholder text to display when nothing is selected
|
||||
* @type {?string}
|
||||
*/
|
||||
this.placeholder = data.placeholder ?? null;
|
||||
|
||||
/**
|
||||
* The minimum number of selections required
|
||||
* @type {?number}
|
||||
*/
|
||||
this.minValues = data.min_values ?? data.minValues ?? null;
|
||||
|
||||
/**
|
||||
* The maximum number of selections allowed
|
||||
* @type {?number}
|
||||
*/
|
||||
this.maxValues = data.max_values ?? data.maxValues ?? null;
|
||||
|
||||
/**
|
||||
* Options for the select menu
|
||||
* @type {MessageSelectOption[]}
|
||||
*/
|
||||
this.options = this.constructor.normalizeOptions(data.options ?? []);
|
||||
|
||||
/**
|
||||
* Whether this select menu is currently disabled
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.disabled = data.disabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id of this select menu
|
||||
* @param {string} customId A unique string to be sent in the interaction when clicked
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setCustomId(customId) {
|
||||
this.customId = Util.verifyString(customId, RangeError, 'SELECT_MENU_CUSTOM_ID');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the interactive status of the select menu
|
||||
* @param {boolean} [disabled=true] Whether this select menu should be disabled
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setDisabled(disabled = true) {
|
||||
this.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of selections allowed for this select menu
|
||||
* @param {number} maxValues Number of selections to be allowed
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setMaxValues(maxValues) {
|
||||
this.maxValues = maxValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of selections required for this select menu
|
||||
* <info>This will default the maxValues to the number of options, unless manually set</info>
|
||||
* @param {number} minValues Number of selections to be required
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setMinValues(minValues) {
|
||||
this.minValues = minValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder of this select menu
|
||||
* @param {string} placeholder Custom placeholder text to display when nothing is selected
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setPlaceholder(placeholder) {
|
||||
this.placeholder = Util.verifyString(placeholder, RangeError, 'SELECT_MENU_PLACEHOLDER');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to the select menu.
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to add
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
addOptions(...options) {
|
||||
this.options.push(...this.constructor.normalizeOptions(options));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options of the select menu.
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to set
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setOptions(...options) {
|
||||
this.spliceOptions(0, this.options.length, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, and inserts options in the select menu.
|
||||
* @param {number} index The index to start at
|
||||
* @param {number} deleteCount The number of options to remove
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} [options] The replacing option objects
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
spliceOptions(index, deleteCount, ...options) {
|
||||
this.options.splice(index, deleteCount, ...this.constructor.normalizeOptions(...options));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the select menu into a plain object
|
||||
* @returns {APIMessageSelectMenu} The raw data of this select menu
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
custom_id: this.customId,
|
||||
disabled: this.disabled,
|
||||
placeholder: this.placeholder,
|
||||
min_values: this.minValues,
|
||||
max_values: this.maxValues ?? (this.minValues ? this.options.length : undefined),
|
||||
options: this.options,
|
||||
type: typeof this.type === 'string' ? MessageComponentTypes[this.type] : this.type,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes option input and resolves strings and emojis.
|
||||
* @param {MessageSelectOptionData} option The select menu option to normalize
|
||||
* @returns {MessageSelectOption}
|
||||
*/
|
||||
static normalizeOption(option) {
|
||||
let { label, value, description, emoji } = option;
|
||||
|
||||
label = Util.verifyString(label, RangeError, 'SELECT_OPTION_LABEL');
|
||||
value = Util.verifyString(value, RangeError, 'SELECT_OPTION_VALUE');
|
||||
emoji = emoji ? Util.resolvePartialEmoji(emoji) : null;
|
||||
description = description ? Util.verifyString(description, RangeError, 'SELECT_OPTION_DESCRIPTION', true) : null;
|
||||
|
||||
return { label, value, description, emoji, default: option.default ?? false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes option input and resolves strings and emojis.
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The select menu options to normalize
|
||||
* @returns {MessageSelectOption[]}
|
||||
*/
|
||||
static normalizeOptions(...options) {
|
||||
return options.flat(Infinity).map(option => this.normalizeOption(option));
|
||||
}
|
||||
// Add
|
||||
/**
|
||||
* Select in menu
|
||||
* @param {Message} message Discord Message
|
||||
* @param {Array<String>} values Option values
|
||||
* @returns {Promise<boolean}
|
||||
*/
|
||||
async select(message, values = []) {
|
||||
// Github copilot is the best :))
|
||||
// POST data from https://github.com/phamleduy04
|
||||
if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message");
|
||||
if (!Array.isArray(values)) throw new TypeError("[INVALID_VALUES] Please pass an array of values");
|
||||
if (!this.customId || this.disabled || values.length == 0) return false; // Disabled or null customID or [] array
|
||||
// Check value is invalid [Max options is 20] => For loop
|
||||
if (values.length < this.minValues) throw new RangeError("[SELECT_MENU_MIN_VALUES] The minimum number of values is " + this.minValues);
|
||||
if (values.length > this.maxValues) throw new RangeError("[SELECT_MENU_MAX_VALUES] The maximum number of values is " + this.maxValues);
|
||||
const validValue = this.options.map(obj => obj.value);
|
||||
const check_ = await values.find(element => {
|
||||
if (typeof element !== 'string') return true;
|
||||
if (!validValue.includes(element)) return true;
|
||||
return false;
|
||||
})
|
||||
if (check_) throw new RangeError("[SELECT_MENU_INVALID_VALUE] The value " + check_ + " is invalid. Please use a valid value " + validValue.join(', '));
|
||||
await message.client.api.interactions.post({
|
||||
data: {
|
||||
type: 3, // ?
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
data: {
|
||||
component_type: 3, // Select Menu
|
||||
custom_id: this.customId,
|
||||
type: 3, // Select Menu
|
||||
values,
|
||||
},
|
||||
message_flags: message.flags.bitfield,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageSelectMenu;
|
||||
'use strict';
|
||||
|
||||
const BaseMessageComponent = require('./BaseMessageComponent');
|
||||
const { MessageComponentTypes } = require('../util/Constants');
|
||||
const Util = require('../util/Util');
|
||||
const { Message } = require('./Message');
|
||||
|
||||
/**
|
||||
* Represents a select menu message component
|
||||
* @extends {BaseMessageComponent}
|
||||
*/
|
||||
class MessageSelectMenu extends BaseMessageComponent {
|
||||
/**
|
||||
* @typedef {BaseMessageComponentOptions} MessageSelectMenuOptions
|
||||
* @property {string} [customId] A unique string to be sent in the interaction when clicked
|
||||
* @property {string} [placeholder] Custom placeholder text to display when nothing is selected
|
||||
* @property {number} [minValues] The minimum number of selections required
|
||||
* @property {number} [maxValues] The maximum number of selections allowed
|
||||
* @property {MessageSelectOption[]} [options] Options for the select menu
|
||||
* @property {boolean} [disabled=false] Disables the select menu to prevent interactions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageSelectOption
|
||||
* @property {string} label The text to be displayed on this option
|
||||
* @property {string} value The value to be sent for this option
|
||||
* @property {?string} description Optional description to show for this option
|
||||
* @property {?RawEmoji} emoji Emoji to display for this option
|
||||
* @property {boolean} default Render this option as the default selection
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MessageSelectOptionData
|
||||
* @property {string} label The text to be displayed on this option
|
||||
* @property {string} value The value to be sent for this option
|
||||
* @property {string} [description] Optional description to show for this option
|
||||
* @property {EmojiIdentifierResolvable} [emoji] Emoji to display for this option
|
||||
* @property {boolean} [default] Render this option as the default selection
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {MessageSelectMenu|MessageSelectMenuOptions} [data={}] MessageSelectMenu to clone or raw data
|
||||
*/
|
||||
constructor(data = {}) {
|
||||
super({ type: 'SELECT_MENU' });
|
||||
|
||||
this.setup(data);
|
||||
}
|
||||
|
||||
setup(data) {
|
||||
/**
|
||||
* A unique string to be sent in the interaction when clicked
|
||||
* @type {?string}
|
||||
*/
|
||||
this.customId = data.custom_id ?? data.customId ?? null;
|
||||
|
||||
/**
|
||||
* Custom placeholder text to display when nothing is selected
|
||||
* @type {?string}
|
||||
*/
|
||||
this.placeholder = data.placeholder ?? null;
|
||||
|
||||
/**
|
||||
* The minimum number of selections required
|
||||
* @type {?number}
|
||||
*/
|
||||
this.minValues = data.min_values ?? data.minValues ?? null;
|
||||
|
||||
/**
|
||||
* The maximum number of selections allowed
|
||||
* @type {?number}
|
||||
*/
|
||||
this.maxValues = data.max_values ?? data.maxValues ?? null;
|
||||
|
||||
/**
|
||||
* Options for the select menu
|
||||
* @type {MessageSelectOption[]}
|
||||
*/
|
||||
this.options = this.constructor.normalizeOptions(data.options ?? []);
|
||||
|
||||
/**
|
||||
* Whether this select menu is currently disabled
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.disabled = data.disabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id of this select menu
|
||||
* @param {string} customId A unique string to be sent in the interaction when clicked
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setCustomId(customId) {
|
||||
this.customId = Util.verifyString(customId, RangeError, 'SELECT_MENU_CUSTOM_ID');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the interactive status of the select menu
|
||||
* @param {boolean} [disabled=true] Whether this select menu should be disabled
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setDisabled(disabled = true) {
|
||||
this.disabled = disabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of selections allowed for this select menu
|
||||
* @param {number} maxValues Number of selections to be allowed
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setMaxValues(maxValues) {
|
||||
this.maxValues = maxValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of selections required for this select menu
|
||||
* <info>This will default the maxValues to the number of options, unless manually set</info>
|
||||
* @param {number} minValues Number of selections to be required
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setMinValues(minValues) {
|
||||
this.minValues = minValues;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder of this select menu
|
||||
* @param {string} placeholder Custom placeholder text to display when nothing is selected
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setPlaceholder(placeholder) {
|
||||
this.placeholder = Util.verifyString(placeholder, RangeError, 'SELECT_MENU_PLACEHOLDER');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds options to the select menu.
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to add
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
addOptions(...options) {
|
||||
this.options.push(...this.constructor.normalizeOptions(options));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options of the select menu.
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The options to set
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
setOptions(...options) {
|
||||
this.spliceOptions(0, this.options.length, options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes, replaces, and inserts options in the select menu.
|
||||
* @param {number} index The index to start at
|
||||
* @param {number} deleteCount The number of options to remove
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} [options] The replacing option objects
|
||||
* @returns {MessageSelectMenu}
|
||||
*/
|
||||
spliceOptions(index, deleteCount, ...options) {
|
||||
this.options.splice(index, deleteCount, ...this.constructor.normalizeOptions(...options));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the select menu into a plain object
|
||||
* @returns {APIMessageSelectMenu} The raw data of this select menu
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
custom_id: this.customId,
|
||||
disabled: this.disabled,
|
||||
placeholder: this.placeholder,
|
||||
min_values: this.minValues,
|
||||
max_values: this.maxValues ?? (this.minValues ? this.options.length : undefined),
|
||||
options: this.options,
|
||||
type: typeof this.type === 'string' ? MessageComponentTypes[this.type] : this.type,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes option input and resolves strings and emojis.
|
||||
* @param {MessageSelectOptionData} option The select menu option to normalize
|
||||
* @returns {MessageSelectOption}
|
||||
*/
|
||||
static normalizeOption(option) {
|
||||
let { label, value, description, emoji } = option;
|
||||
|
||||
label = Util.verifyString(label, RangeError, 'SELECT_OPTION_LABEL');
|
||||
value = Util.verifyString(value, RangeError, 'SELECT_OPTION_VALUE');
|
||||
emoji = emoji ? Util.resolvePartialEmoji(emoji) : null;
|
||||
description = description ? Util.verifyString(description, RangeError, 'SELECT_OPTION_DESCRIPTION', true) : null;
|
||||
|
||||
return { label, value, description, emoji, default: option.default ?? false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes option input and resolves strings and emojis.
|
||||
* @param {...MessageSelectOptionData|MessageSelectOptionData[]} options The select menu options to normalize
|
||||
* @returns {MessageSelectOption[]}
|
||||
*/
|
||||
static normalizeOptions(...options) {
|
||||
return options.flat(Infinity).map(option => this.normalizeOption(option));
|
||||
}
|
||||
// Add
|
||||
/**
|
||||
* Select in menu
|
||||
* @param {Message} message Discord Message
|
||||
* @param {Array<String>} values Option values
|
||||
* @returns {Promise<boolean}
|
||||
*/
|
||||
async select(message, values = []) {
|
||||
// Github copilot is the best :))
|
||||
// POST data from https://github.com/phamleduy04
|
||||
if (!message instanceof Message) throw new Error("[UNKNOWN_MESSAGE] Please pass a valid Message");
|
||||
if (!Array.isArray(values)) throw new TypeError("[INVALID_VALUES] Please pass an array of values");
|
||||
if (!this.customId || this.disabled || values.length == 0) return false; // Disabled or null customID or [] array
|
||||
// Check value is invalid [Max options is 20] => For loop
|
||||
if (values.length < this.minValues) throw new RangeError("[SELECT_MENU_MIN_VALUES] The minimum number of values is " + this.minValues);
|
||||
if (values.length > this.maxValues) throw new RangeError("[SELECT_MENU_MAX_VALUES] The maximum number of values is " + this.maxValues);
|
||||
const validValue = this.options.map(obj => obj.value);
|
||||
const check_ = await values.find(element => {
|
||||
if (typeof element !== 'string') return true;
|
||||
if (!validValue.includes(element)) return true;
|
||||
return false;
|
||||
})
|
||||
if (check_) throw new RangeError("[SELECT_MENU_INVALID_VALUE] The value " + check_ + " is invalid. Please use a valid value " + validValue.join(', '));
|
||||
await message.client.api.interactions.post(
|
||||
{
|
||||
data: {
|
||||
type: 3, // ?
|
||||
guild_id: message.guild?.id ?? null, // In DMs
|
||||
channel_id: message.channel.id,
|
||||
message_id: message.id,
|
||||
application_id: message.author.id,
|
||||
session_id: message.client.session_id,
|
||||
data: {
|
||||
component_type: 3, // Select Menu
|
||||
custom_id: this.customId,
|
||||
type: 3, // Select Menu
|
||||
values,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageSelectMenu;
|
||||
|
@ -1,328 +1,323 @@
|
||||
'use strict';
|
||||
const axios = require('axios');
|
||||
const baseURL = 'https://embed.benny.fun/?'; // error, not working .-. sad ...
|
||||
const hiddenCharter =
|
||||
'||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||';
|
||||
const { RangeError } = require('../errors');
|
||||
const Util = require('../util/Util');
|
||||
const process = require('node:process');
|
||||
const warn = true;
|
||||
|
||||
class WebEmbed {
|
||||
constructor(data = {}) {
|
||||
if (warn) process.emitWarning(
|
||||
'The WebEmbed constructor encountered a problem with the URL.',
|
||||
);
|
||||
this._setup(data);
|
||||
/**
|
||||
* Shorten the link
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.shorten = data.shorten ?? true;
|
||||
|
||||
/**
|
||||
* Hidden Embed link
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.hidden = data.hidden ?? false;
|
||||
}
|
||||
_setup(data) {
|
||||
/**
|
||||
* The title of this embed
|
||||
* @type {?string}
|
||||
*/
|
||||
this.title = data.title ?? null;
|
||||
|
||||
/**
|
||||
* The description of this embed
|
||||
* @type {?string}
|
||||
*/
|
||||
this.description = data.description ?? null;
|
||||
|
||||
/**
|
||||
* The URL of this embed
|
||||
* @type {?string}
|
||||
*/
|
||||
this.url = data.url ?? null;
|
||||
|
||||
/**
|
||||
* The color of this embed
|
||||
* @type {?number}
|
||||
*/
|
||||
this.color = 'color' in data ? Util.resolveColor(data.color) : null;
|
||||
|
||||
/**
|
||||
* Represents the image of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedImage
|
||||
* @property {string} url URL for this image
|
||||
* @property {string} proxyURL ProxyURL for this image
|
||||
* @property {number} height Height of this image
|
||||
* @property {number} width Width of this image
|
||||
*/
|
||||
|
||||
/**
|
||||
* The image of this embed, if there is one
|
||||
* @type {?MessageEmbedImage}
|
||||
*/
|
||||
this.image = data.image
|
||||
? {
|
||||
url: data.image.url,
|
||||
proxyURL: data.image.proxyURL ?? data.image.proxy_url,
|
||||
height: data.image.height,
|
||||
width: data.image.width,
|
||||
}
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Represents the video of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedVideo
|
||||
* @property {string} url URL of this video
|
||||
* @property {string} proxyURL ProxyURL for this video
|
||||
* @property {number} height Height of this video
|
||||
* @property {number} width Width of this video
|
||||
*/
|
||||
|
||||
/**
|
||||
* The video of this embed (if there is one)
|
||||
* @type {?MessageEmbedVideo}
|
||||
* @readonly
|
||||
*/
|
||||
this.video = data.video
|
||||
? {
|
||||
url: data.video.url,
|
||||
proxyURL: data.video.proxyURL ?? data.video.proxy_url,
|
||||
height: data.video.height,
|
||||
width: data.video.width,
|
||||
}
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Represents the author field of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedAuthor
|
||||
* @property {string} name The name of this author
|
||||
* @property {string} url URL of this author
|
||||
* @property {string} iconURL URL of the icon for this author
|
||||
* @property {string} proxyIconURL Proxied URL of the icon for this author
|
||||
*/
|
||||
|
||||
/**
|
||||
* The author of this embed (if there is one)
|
||||
* @type {?MessageEmbedAuthor}
|
||||
*/
|
||||
this.author = data.author
|
||||
? {
|
||||
name: data.author.name,
|
||||
url: data.author.url,
|
||||
}
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Represents the provider of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedProvider
|
||||
* @property {string} name The name of this provider
|
||||
* @property {string} url URL of this provider
|
||||
*/
|
||||
|
||||
/**
|
||||
* The provider of this embed (if there is one)
|
||||
* @type {?MessageEmbedProvider}
|
||||
*/
|
||||
this.provider = data.provider
|
||||
? {
|
||||
name: data.provider.name,
|
||||
url: data.provider.name,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
/**
|
||||
* The options to provide for setting an author for a {@link MessageEmbed}.
|
||||
* @typedef {Object} EmbedAuthorData
|
||||
* @property {string} name The name of this author.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the author of this embed.
|
||||
* @param {string|EmbedAuthorData|null} options The options to provide for the author.
|
||||
* Provide `null` to remove the author data.
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setAuthor(options) {
|
||||
if (options === null) {
|
||||
this.author = {};
|
||||
return this;
|
||||
}
|
||||
const { name, url } = options;
|
||||
this.author = {
|
||||
name: Util.verifyString(name, RangeError, 'EMBED_AUTHOR_NAME'),
|
||||
url,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options to provide for setting an provider for a {@link MessageEmbed}.
|
||||
* @typedef {Object} EmbedProviderData
|
||||
* @property {string} name The name of this provider.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the provider of this embed.
|
||||
* @param {string|EmbedProviderData|null} options The options to provide for the provider.
|
||||
* Provide `null` to remove the provider data.
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setProvider(options) {
|
||||
if (options === null) {
|
||||
this.provider = {};
|
||||
return this;
|
||||
}
|
||||
const { name, url } = options;
|
||||
this.provider = {
|
||||
name: Util.verifyString(name, RangeError, 'EMBED_PROVIDER_NAME'),
|
||||
url,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this embed.
|
||||
* @param {ColorResolvable} color The color of the embed
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setColor(color) {
|
||||
this.color = Util.resolveColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this embed.
|
||||
* @param {string} description The description (Limit 350 characters)
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setDescription(description) {
|
||||
this.description = Util.verifyString(
|
||||
description,
|
||||
RangeError,
|
||||
'EMBED_DESCRIPTION',
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image of this embed.
|
||||
* @param {string} url The URL of the image
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setImage(url) {
|
||||
this.image = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the video of this embed.
|
||||
* @param {string} url The URL of the video
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setVideo(url) {
|
||||
this.video = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this embed.
|
||||
* @param {string} title The title
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setTitle(title) {
|
||||
this.title = Util.verifyString(title, RangeError, 'EMBED_TITLE');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this embed.
|
||||
* @param {string} url The URL
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setURL(url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Message Content + Embed (if hidden, pls check content length because it has 1000+ length)
|
||||
* @returns {string} Message Content
|
||||
*/
|
||||
async toMessage() {
|
||||
const arrayQuery = [];
|
||||
if (this.title) {
|
||||
arrayQuery.push(`title=${encodeURIComponent(this.title)}`);
|
||||
}
|
||||
if (this.description) {
|
||||
arrayQuery.push(
|
||||
`description=${encodeURIComponent(this.description)}`,
|
||||
);
|
||||
}
|
||||
if (this.url) {
|
||||
arrayQuery.push(`url=${encodeURIComponent(this.url)}`);
|
||||
}
|
||||
if (this.color) {
|
||||
arrayQuery.push(
|
||||
`colour=${encodeURIComponent('#' + this.color.toString(16))}`,
|
||||
);
|
||||
}
|
||||
if (this.image?.url) {
|
||||
arrayQuery.push(`image=${encodeURIComponent(this.image.url)}`);
|
||||
}
|
||||
if (this.video?.url) {
|
||||
arrayQuery.push(`video=${encodeURIComponent(this.video.url)}`);
|
||||
}
|
||||
if (this.author) {
|
||||
if (this.author.name) arrayQuery.push(
|
||||
`author_name=${encodeURIComponent(this.author.name)}`,
|
||||
);
|
||||
if (this.author.url) arrayQuery.push(
|
||||
`author_url=${encodeURIComponent(this.author.url)}`,
|
||||
);
|
||||
}
|
||||
if (this.provider) {
|
||||
if (this.provider.name) arrayQuery.push(
|
||||
`provider_name=${encodeURIComponent(this.provider.name)}`,
|
||||
);
|
||||
if (this.provider.url) arrayQuery.push(
|
||||
`provider_url=${encodeURIComponent(this.provider.url)}`,
|
||||
);
|
||||
}
|
||||
const fullURL = `${baseURL}${arrayQuery.join('&')}`;
|
||||
if (this.shorten) {
|
||||
const url = await getShorten(fullURL);
|
||||
if (!url) console.log('Cannot shorten URL in WebEmbed');
|
||||
return this.hidden ? `${hiddenCharter} ${url || fullURL}` : (url || fullURL);
|
||||
} else {
|
||||
return this.hidden ? `${hiddenCharter} ${fullURL}` : fullURL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Credit: https://www.npmjs.com/package/node-url-shortener + google :))
|
||||
const getShorten = async (url) => {
|
||||
const APIurl = [
|
||||
// 'https://is.gd/create.php?format=simple&url=', :(
|
||||
'https://tinyurl.com/api-create.php?url=',
|
||||
'https://sagiri-fansub.tk/api/v1/short?url=', // my api, pls don't ddos :(
|
||||
'https://lazuee.ga/api/v1/shorten?url='
|
||||
// 'https://cdpt.in/shorten?url=', Redirects 5s :(
|
||||
];
|
||||
try {
|
||||
const res = await axios.get(
|
||||
`${
|
||||
APIurl[Math.floor(Math.random() * APIurl.length)]
|
||||
}${encodeURIComponent(url)}`,
|
||||
);
|
||||
return `${res.data}`;
|
||||
} catch {
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebEmbed;
|
||||
module.exports.hiddenEmbed = hiddenCharter;
|
||||
'use strict';
|
||||
const axios = require('axios');
|
||||
const baseURL = 'https://embed.benny.fun/?';
|
||||
const hiddenCharter =
|
||||
'||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||';
|
||||
const { RangeError } = require('../errors');
|
||||
const Util = require('../util/Util');
|
||||
|
||||
class WebEmbed {
|
||||
constructor(data = {}) {
|
||||
this._setup(data);
|
||||
/**
|
||||
* Shorten the link
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.shorten = data.shorten ?? true;
|
||||
|
||||
/**
|
||||
* Hidden Embed link
|
||||
* @type {?boolean}
|
||||
*/
|
||||
this.hidden = data.hidden ?? false;
|
||||
}
|
||||
_setup(data) {
|
||||
/**
|
||||
* The title of this embed
|
||||
* @type {?string}
|
||||
*/
|
||||
this.title = data.title ?? null;
|
||||
|
||||
/**
|
||||
* The description of this embed
|
||||
* @type {?string}
|
||||
*/
|
||||
this.description = data.description ?? null;
|
||||
|
||||
/**
|
||||
* The URL of this embed
|
||||
* @type {?string}
|
||||
*/
|
||||
this.url = data.url ?? null;
|
||||
|
||||
/**
|
||||
* The color of this embed
|
||||
* @type {?number}
|
||||
*/
|
||||
this.color = 'color' in data ? Util.resolveColor(data.color) : null;
|
||||
|
||||
/**
|
||||
* Represents the image of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedImage
|
||||
* @property {string} url URL for this image
|
||||
* @property {string} proxyURL ProxyURL for this image
|
||||
* @property {number} height Height of this image
|
||||
* @property {number} width Width of this image
|
||||
*/
|
||||
|
||||
/**
|
||||
* The image of this embed, if there is one
|
||||
* @type {?MessageEmbedImage}
|
||||
*/
|
||||
this.image = data.image
|
||||
? {
|
||||
url: data.image.url,
|
||||
proxyURL: data.image.proxyURL ?? data.image.proxy_url,
|
||||
height: data.image.height,
|
||||
width: data.image.width,
|
||||
}
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Represents the video of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedVideo
|
||||
* @property {string} url URL of this video
|
||||
* @property {string} proxyURL ProxyURL for this video
|
||||
* @property {number} height Height of this video
|
||||
* @property {number} width Width of this video
|
||||
*/
|
||||
|
||||
/**
|
||||
* The video of this embed (if there is one)
|
||||
* @type {?MessageEmbedVideo}
|
||||
* @readonly
|
||||
*/
|
||||
this.video = data.video
|
||||
? {
|
||||
url: data.video.url,
|
||||
proxyURL: data.video.proxyURL ?? data.video.proxy_url,
|
||||
height: data.video.height,
|
||||
width: data.video.width,
|
||||
}
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Represents the author field of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedAuthor
|
||||
* @property {string} name The name of this author
|
||||
* @property {string} url URL of this author
|
||||
* @property {string} iconURL URL of the icon for this author
|
||||
* @property {string} proxyIconURL Proxied URL of the icon for this author
|
||||
*/
|
||||
|
||||
/**
|
||||
* The author of this embed (if there is one)
|
||||
* @type {?MessageEmbedAuthor}
|
||||
*/
|
||||
this.author = data.author
|
||||
? {
|
||||
name: data.author.name,
|
||||
url: data.author.url,
|
||||
}
|
||||
: null;
|
||||
|
||||
/**
|
||||
* Represents the provider of a MessageEmbed
|
||||
* @typedef {Object} MessageEmbedProvider
|
||||
* @property {string} name The name of this provider
|
||||
* @property {string} url URL of this provider
|
||||
*/
|
||||
|
||||
/**
|
||||
* The provider of this embed (if there is one)
|
||||
* @type {?MessageEmbedProvider}
|
||||
*/
|
||||
this.provider = data.provider
|
||||
? {
|
||||
name: data.provider.name,
|
||||
url: data.provider.name,
|
||||
}
|
||||
: null;
|
||||
}
|
||||
/**
|
||||
* The options to provide for setting an author for a {@link MessageEmbed}.
|
||||
* @typedef {Object} EmbedAuthorData
|
||||
* @property {string} name The name of this author.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the author of this embed.
|
||||
* @param {string|EmbedAuthorData|null} options The options to provide for the author.
|
||||
* Provide `null` to remove the author data.
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setAuthor(options) {
|
||||
if (options === null) {
|
||||
this.author = {};
|
||||
return this;
|
||||
}
|
||||
const { name, url } = options;
|
||||
this.author = {
|
||||
name: Util.verifyString(name, RangeError, 'EMBED_AUTHOR_NAME'),
|
||||
url,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options to provide for setting an provider for a {@link MessageEmbed}.
|
||||
* @typedef {Object} EmbedProviderData
|
||||
* @property {string} name The name of this provider.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the provider of this embed.
|
||||
* @param {string|EmbedProviderData|null} options The options to provide for the provider.
|
||||
* Provide `null` to remove the provider data.
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setProvider(options) {
|
||||
if (options === null) {
|
||||
this.provider = {};
|
||||
return this;
|
||||
}
|
||||
const { name, url } = options;
|
||||
this.provider = {
|
||||
name: Util.verifyString(name, RangeError, 'EMBED_PROVIDER_NAME'),
|
||||
url,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of this embed.
|
||||
* @param {ColorResolvable} color The color of the embed
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setColor(color) {
|
||||
this.color = Util.resolveColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description of this embed.
|
||||
* @param {string} description The description (Limit 350 characters)
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setDescription(description) {
|
||||
this.description = Util.verifyString(
|
||||
description,
|
||||
RangeError,
|
||||
'EMBED_DESCRIPTION',
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image of this embed.
|
||||
* @param {string} url The URL of the image
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setImage(url) {
|
||||
this.image = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the video of this embed.
|
||||
* @param {string} url The URL of the video
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setVideo(url) {
|
||||
this.video = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of this embed.
|
||||
* @param {string} title The title
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setTitle(title) {
|
||||
this.title = Util.verifyString(title, RangeError, 'EMBED_TITLE');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URL of this embed.
|
||||
* @param {string} url The URL
|
||||
* @returns {MessageEmbed}
|
||||
*/
|
||||
setURL(url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Message Content + Embed (if hidden, pls check content length because it has 1000+ length)
|
||||
* @returns {string} Message Content
|
||||
*/
|
||||
async toMessage() {
|
||||
const arrayQuery = [];
|
||||
if (this.title) {
|
||||
arrayQuery.push(`title=${encodeURIComponent(this.title)}`);
|
||||
}
|
||||
if (this.description) {
|
||||
arrayQuery.push(
|
||||
`description=${encodeURIComponent(this.description)}`,
|
||||
);
|
||||
}
|
||||
if (this.url) {
|
||||
arrayQuery.push(`url=${encodeURIComponent(this.url)}`);
|
||||
}
|
||||
if (this.color) {
|
||||
arrayQuery.push(
|
||||
`colour=${encodeURIComponent('#' + this.color.toString(16))}`,
|
||||
);
|
||||
}
|
||||
if (this.image?.url) {
|
||||
arrayQuery.push(`image=${encodeURIComponent(this.image.url)}`);
|
||||
}
|
||||
if (this.video?.url) {
|
||||
arrayQuery.push(`video=${encodeURIComponent(this.video.url)}`);
|
||||
}
|
||||
if (this.author) {
|
||||
if (this.author.name) arrayQuery.push(
|
||||
`author_name=${encodeURIComponent(this.author.name)}`,
|
||||
);
|
||||
if (this.author.url) arrayQuery.push(
|
||||
`author_url=${encodeURIComponent(this.author.url)}`,
|
||||
);
|
||||
}
|
||||
if (this.provider) {
|
||||
if (this.provider.name) arrayQuery.push(
|
||||
`provider_name=${encodeURIComponent(this.provider.name)}`,
|
||||
);
|
||||
if (this.provider.url) arrayQuery.push(
|
||||
`provider_url=${encodeURIComponent(this.provider.url)}`,
|
||||
);
|
||||
}
|
||||
const fullURL = `${baseURL}${arrayQuery.join('&')}`;
|
||||
if (this.shorten) {
|
||||
const url = await getShorten(fullURL);
|
||||
if (!url) console.log('Cannot shorten URL in WebEmbed');
|
||||
return this.hidden ? `${hiddenCharter} ${url || fullURL}` : (url || fullURL);
|
||||
} else {
|
||||
return this.hidden ? `${hiddenCharter} ${fullURL}` : fullURL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Credit: https://www.npmjs.com/package/node-url-shortener + google :))
|
||||
const getShorten = async (url) => {
|
||||
const APIurl = [
|
||||
// 'https://is.gd/create.php?format=simple&url=', :(
|
||||
'https://tinyurl.com/api-create.php?url=',
|
||||
'https://sagiri-fansub.tk/api/v1/short?url=', // my api, pls don't ddos :(
|
||||
'https://lazuee.ga/api/v1/shorten?url='
|
||||
// 'https://cdpt.in/shorten?url=', Redirects 5s :(
|
||||
];
|
||||
try {
|
||||
const res = await axios.get(
|
||||
`${
|
||||
APIurl[Math.floor(Math.random() * APIurl.length)]
|
||||
}${encodeURIComponent(url)}`,
|
||||
);
|
||||
return `${res.data}`;
|
||||
} catch {
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebEmbed;
|
||||
module.exports.hiddenEmbed = hiddenCharter;
|
||||
|
@ -1,136 +1,136 @@
|
||||
'use strict';
|
||||
|
||||
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const Base = require('../Base');
|
||||
|
||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||
|
||||
/**
|
||||
* Represents an OAuth2 Application.
|
||||
* @abstract
|
||||
*/
|
||||
class Application extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
if (data) {
|
||||
this._patch(data);
|
||||
}
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
/**
|
||||
* The application's id
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
if ('name' in data) {
|
||||
/**
|
||||
* The name of the application
|
||||
* @type {?string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
} else {
|
||||
this.name ??= null;
|
||||
}
|
||||
|
||||
if ('description' in data) {
|
||||
/**
|
||||
* The application's description
|
||||
* @type {?string}
|
||||
*/
|
||||
this.description = data.description;
|
||||
} else {
|
||||
this.description ??= null;
|
||||
}
|
||||
|
||||
if ('icon' in data) {
|
||||
/**
|
||||
* The application's icon hash
|
||||
* @type {?string}
|
||||
*/
|
||||
this.icon = data.icon;
|
||||
} else {
|
||||
this.icon ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp the application was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return SnowflakeUtil.timestampFrom(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the application was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to the application's icon.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
iconURL({ format, size } = {}) {
|
||||
if (!this.icon) return null;
|
||||
return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to this application's cover image.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
coverURL({ format, size } = {}) {
|
||||
if (!this.cover) return null;
|
||||
return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
|
||||
}
|
||||
|
||||
/**
|
||||
* Asset data.
|
||||
* @typedef {Object} ApplicationAsset
|
||||
* @property {Snowflake} id The asset's id
|
||||
* @property {string} name The asset's name
|
||||
* @property {string} type The asset's type
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the application's rich presence assets.
|
||||
* @returns {Promise<Array<ApplicationAsset>>}
|
||||
*/
|
||||
async fetchAssets() {
|
||||
const assets = await this.client.api.oauth2.applications(this.id).assets.get();
|
||||
return assets.map(a => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
type: AssetTypes[a.type - 1],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the application's name instead of the
|
||||
* Application object.
|
||||
* @returns {?string}
|
||||
* @example
|
||||
* // Logs: Application name: My App
|
||||
* console.log(`Application name: ${application}`);
|
||||
*/
|
||||
toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return super.toJSON({ createdTimestamp: true });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Application;
|
||||
'use strict';
|
||||
|
||||
const { ClientApplicationAssetTypes, Endpoints } = require('../../util/Constants');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const Base = require('../Base');
|
||||
|
||||
const AssetTypes = Object.keys(ClientApplicationAssetTypes);
|
||||
|
||||
/**
|
||||
* Represents an OAuth2 Application.
|
||||
* @abstract
|
||||
*/
|
||||
class Application extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
if (data) {
|
||||
this._patch(data);
|
||||
}
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
/**
|
||||
* The application's id
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
if ('name' in data) {
|
||||
/**
|
||||
* The name of the application
|
||||
* @type {?string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
} else {
|
||||
this.name ??= null;
|
||||
}
|
||||
|
||||
if ('description' in data) {
|
||||
/**
|
||||
* The application's description
|
||||
* @type {?string}
|
||||
*/
|
||||
this.description = data.description;
|
||||
} else {
|
||||
this.description ??= null;
|
||||
}
|
||||
|
||||
if ('icon' in data) {
|
||||
/**
|
||||
* The application's icon hash
|
||||
* @type {?string}
|
||||
*/
|
||||
this.icon = data.icon;
|
||||
} else {
|
||||
this.icon ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The timestamp the application was created at
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get createdTimestamp() {
|
||||
return SnowflakeUtil.timestampFrom(this.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The time the application was created at
|
||||
* @type {Date}
|
||||
* @readonly
|
||||
*/
|
||||
get createdAt() {
|
||||
return new Date(this.createdTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to the application's icon.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
iconURL({ format, size } = {}) {
|
||||
if (!this.icon) return null;
|
||||
return this.client.rest.cdn.AppIcon(this.id, this.icon, { format, size });
|
||||
}
|
||||
|
||||
/**
|
||||
* A link to this application's cover image.
|
||||
* @param {StaticImageURLOptions} [options={}] Options for the Image URL
|
||||
* @returns {?string}
|
||||
*/
|
||||
coverURL({ format, size } = {}) {
|
||||
if (!this.cover) return null;
|
||||
return Endpoints.CDN(this.client.options.http.cdn).AppIcon(this.id, this.cover, { format, size });
|
||||
}
|
||||
|
||||
/**
|
||||
* Asset data.
|
||||
* @typedef {Object} ApplicationAsset
|
||||
* @property {Snowflake} id The asset's id
|
||||
* @property {string} name The asset's name
|
||||
* @property {string} type The asset's type
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the application's rich presence assets.
|
||||
* @returns {Promise<Array<ApplicationAsset>>}
|
||||
*/
|
||||
async fetchAssets() {
|
||||
const assets = await this.client.api.oauth2.applications(this.id).assets.get();
|
||||
return assets.map(a => ({
|
||||
id: a.id,
|
||||
name: a.name,
|
||||
type: AssetTypes[a.type - 1],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* When concatenated with a string, this automatically returns the application's name instead of the
|
||||
* Application object.
|
||||
* @returns {?string}
|
||||
* @example
|
||||
* // Logs: Application name: My App
|
||||
* console.log(`Application name: ${application}`);
|
||||
*/
|
||||
toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return super.toJSON({ createdTimestamp: true });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Application;
|
||||
|
@ -1,360 +1,360 @@
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable import/order */
|
||||
const MessageCollector = require('../MessageCollector');
|
||||
const MessagePayload = require('../MessagePayload');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { InteractionTypes } = require('../../util/Constants');
|
||||
const { TypeError, Error } = require('../../errors');
|
||||
const InteractionCollector = require('../InteractionCollector');
|
||||
|
||||
/**
|
||||
* Interface for classes that have text-channel-like features.
|
||||
* @interface
|
||||
*/
|
||||
class TextBasedChannel {
|
||||
constructor() {
|
||||
/**
|
||||
* A manager of the messages sent to this channel
|
||||
* @type {MessageManager}
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* The channel's last message id, if one was sent
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.lastMessageId = null;
|
||||
|
||||
/**
|
||||
* The timestamp when the last pinned message was pinned, if there was one
|
||||
* @type {?number}
|
||||
*/
|
||||
this.lastPinTimestamp = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Message object of the last message in the channel, if one was sent
|
||||
* @type {?Message}
|
||||
* @readonly
|
||||
*/
|
||||
get lastMessage() {
|
||||
return this.messages.resolve(this.lastMessageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The date when the last pinned message was pinned, if there was one
|
||||
* @type {?Date}
|
||||
* @readonly
|
||||
*/
|
||||
get lastPinAt() {
|
||||
return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base options provided when sending.
|
||||
* @typedef {Object} BaseMessageOptions
|
||||
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud
|
||||
* @property {string} [nonce=''] The nonce for the message
|
||||
* @property {string} [content=''] The content for the message
|
||||
* @property {WebEmbed[]|MessageEmbed[]|APIEmbed[]} [embeds] The embeds for the message
|
||||
* (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
|
||||
* @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
|
||||
* (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
|
||||
* @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message
|
||||
* @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
|
||||
* Action rows containing interactive components for the message (buttons, select menus)
|
||||
* @property {MessageAttachment[]} [attachments] Attachments to send in the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options provided when sending or editing a message.
|
||||
* @typedef {BaseMessageOptions} MessageOptions
|
||||
* @property {ReplyOptions} [reply] The options for replying to a message
|
||||
* @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options provided to control parsing of mentions by Discord
|
||||
* @typedef {Object} MessageMentionOptions
|
||||
* @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed
|
||||
* @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions
|
||||
* @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions
|
||||
* @property {boolean} [repliedUser=true] Whether the author of the Message being replied to should be pinged
|
||||
*/
|
||||
|
||||
/**
|
||||
* Types of mentions to enable in MessageMentionOptions.
|
||||
* - `roles`
|
||||
* - `users`
|
||||
* - `everyone`
|
||||
* @typedef {string} MessageMentionTypes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileOptions
|
||||
* @property {BufferResolvable} attachment File to attach
|
||||
* @property {string} [name='file.jpg'] Filename of the attachment
|
||||
* @property {string} description The description of the file
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for sending a message with a reply.
|
||||
* @typedef {Object} ReplyOptions
|
||||
* @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system)
|
||||
* @property {boolean} [failIfNotExists=true] Whether to error if the referenced message
|
||||
* does not exist (creates a standard message in this case when false)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends a message to this channel.
|
||||
* @param {string|MessagePayload|MessageOptions} options The options to provide
|
||||
* @returns {Promise<Message>}
|
||||
* @example
|
||||
* // Send a basic message
|
||||
* channel.send('hello!')
|
||||
* .then(message => console.log(`Sent message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Send a remote file
|
||||
* channel.send({
|
||||
* files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Send a local file
|
||||
* channel.send({
|
||||
* files: [{
|
||||
* attachment: 'entire/path/to/file.jpg',
|
||||
* name: 'file.jpg'
|
||||
* description: 'A description of the file'
|
||||
* }]
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Send an embed with a local image inside
|
||||
* channel.send({
|
||||
* content: 'This is an embed',
|
||||
* embeds: [
|
||||
* {
|
||||
* thumbnail: {
|
||||
* url: 'attachment://file.jpg'
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* files: [{
|
||||
* attachment: 'entire/path/to/file.jpg',
|
||||
* name: 'file.jpg'
|
||||
* description: 'A description of the file'
|
||||
* }]
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async send(options) {
|
||||
const User = require('../User');
|
||||
const { GuildMember } = require('../GuildMember');
|
||||
|
||||
if (this instanceof User || this instanceof GuildMember) {
|
||||
const dm = await this.createDM();
|
||||
return dm.send(options);
|
||||
}
|
||||
|
||||
let messagePayload;
|
||||
|
||||
if (options instanceof MessagePayload) {
|
||||
messagePayload = await options.resolveData();
|
||||
} else {
|
||||
messagePayload = await MessagePayload.create(this, options).resolveData();
|
||||
}
|
||||
|
||||
const { data, files } = await messagePayload.resolveFiles();
|
||||
const d = await this.client.api.channels[this.id].messages.post({ data, files });
|
||||
|
||||
return this.messages.cache.get(d.id) ?? this.messages._add(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a typing indicator in the channel.
|
||||
* @returns {Promise<void>} Resolves upon the typing status being sent
|
||||
* @example
|
||||
* // Start typing in a channel
|
||||
* channel.sendTyping();
|
||||
*/
|
||||
async sendTyping() {
|
||||
await this.client.api.channels(this.id).typing.post();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Message Collector.
|
||||
* @param {MessageCollectorOptions} [options={}] The options to pass to the collector
|
||||
* @returns {MessageCollector}
|
||||
* @example
|
||||
* // Create a message collector
|
||||
* const filter = m => m.content.includes('discord');
|
||||
* const collector = channel.createMessageCollector({ filter, time: 15_000 });
|
||||
* collector.on('collect', m => console.log(`Collected ${m.content}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageCollector(options = {}) {
|
||||
return new MessageCollector(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing the same properties as CollectorOptions, but a few more:
|
||||
* @typedef {MessageCollectorOptions} AwaitMessagesOptions
|
||||
* @property {string[]} [errors] Stop/end reasons that cause the promise to reject
|
||||
*/
|
||||
|
||||
/**
|
||||
* Similar to createMessageCollector but in promise form.
|
||||
* Resolves with a collection of messages that pass the specified filter.
|
||||
* @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector
|
||||
* @returns {Promise<Collection<Snowflake, Message>>}
|
||||
* @example
|
||||
* // Await !vote messages
|
||||
* const filter = m => m.content.startsWith('!vote');
|
||||
* // Errors: ['time'] treats ending because of the time limit as an error
|
||||
* channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] })
|
||||
* .then(collected => console.log(collected.size))
|
||||
* .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
|
||||
*/
|
||||
awaitMessages(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = this.createMessageCollector(options);
|
||||
collector.once('end', (collection, reason) => {
|
||||
if (options.errors?.includes(reason)) {
|
||||
reject(collection);
|
||||
} else {
|
||||
resolve(collection);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button interaction collector.
|
||||
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
|
||||
* @returns {InteractionCollector}
|
||||
* @example
|
||||
* // Create a button interaction collector
|
||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
||||
* const collector = channel.createMessageComponentCollector({ filter, time: 15_000 });
|
||||
* collector.on('collect', i => console.log(`Collected ${i.customId}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageComponentCollector(options = {}) {
|
||||
return new InteractionCollector(this.client, {
|
||||
...options,
|
||||
interactionType: InteractionTypes.MESSAGE_COMPONENT,
|
||||
channel: this,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects a single component interaction that passes the filter.
|
||||
* The Promise will reject if the time expires.
|
||||
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
|
||||
* @returns {Promise<MessageComponentInteraction>}
|
||||
* @example
|
||||
* // Collect a message component interaction
|
||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
||||
* channel.awaitMessageComponent({ filter, time: 15_000 })
|
||||
* .then(interaction => console.log(`${interaction.customId} was clicked!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
awaitMessageComponent(options = {}) {
|
||||
const _options = { ...options, max: 1 };
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = this.createMessageComponentCollector(_options);
|
||||
collector.once('end', (interactions, reason) => {
|
||||
const interaction = interactions.first();
|
||||
if (interaction) resolve(interaction);
|
||||
else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk deletes given messages that are newer than two weeks.
|
||||
* @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages
|
||||
* Messages or number of messages to delete
|
||||
* @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
|
||||
* @returns {Promise<Collection<Snowflake, Message>>} Returns the deleted messages
|
||||
* @example
|
||||
* // Bulk delete messages
|
||||
* channel.bulkDelete(5)
|
||||
* .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async bulkDelete(messages, filterOld = false) {
|
||||
if (Array.isArray(messages) || messages instanceof Collection) {
|
||||
let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m);
|
||||
if (filterOld) {
|
||||
messageIds = messageIds.filter(id => Date.now() - SnowflakeUtil.timestampFrom(id) < 1_209_600_000);
|
||||
}
|
||||
if (messageIds.length === 0) return new Collection();
|
||||
if (messageIds.length === 1) {
|
||||
await this.client.api.channels(this.id).messages(messageIds[0]).delete();
|
||||
const message = this.client.actions.MessageDelete.getMessage(
|
||||
{
|
||||
message_id: messageIds[0],
|
||||
},
|
||||
this,
|
||||
);
|
||||
return message ? new Collection([[message.id, message]]) : new Collection();
|
||||
}
|
||||
await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } });
|
||||
return messageIds.reduce(
|
||||
(col, id) =>
|
||||
col.set(
|
||||
id,
|
||||
this.client.actions.MessageDeleteBulk.getMessage(
|
||||
{
|
||||
message_id: id,
|
||||
},
|
||||
this,
|
||||
),
|
||||
),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
if (!isNaN(messages)) {
|
||||
const msgs = await this.messages.fetch({ limit: messages });
|
||||
return this.bulkDelete(msgs, filterOld);
|
||||
}
|
||||
throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
|
||||
}
|
||||
|
||||
static applyToClass(structure, full = false, ignore = []) {
|
||||
const props = ['send'];
|
||||
if (full) {
|
||||
props.push(
|
||||
'lastMessage',
|
||||
'lastPinAt',
|
||||
'bulkDelete',
|
||||
'sendTyping',
|
||||
'createMessageCollector',
|
||||
'awaitMessages',
|
||||
'createMessageComponentCollector',
|
||||
'awaitMessageComponent',
|
||||
);
|
||||
}
|
||||
for (const prop of props) {
|
||||
if (ignore.includes(prop)) continue;
|
||||
Object.defineProperty(
|
||||
structure.prototype,
|
||||
prop,
|
||||
Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextBasedChannel;
|
||||
|
||||
// Fixes Circular
|
||||
const MessageManager = require('../../managers/MessageManager');
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable import/order */
|
||||
const MessageCollector = require('../MessageCollector');
|
||||
const MessagePayload = require('../MessagePayload');
|
||||
const SnowflakeUtil = require('../../util/SnowflakeUtil');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { InteractionTypes } = require('../../util/Constants');
|
||||
const { TypeError, Error } = require('../../errors');
|
||||
const InteractionCollector = require('../InteractionCollector');
|
||||
|
||||
/**
|
||||
* Interface for classes that have text-channel-like features.
|
||||
* @interface
|
||||
*/
|
||||
class TextBasedChannel {
|
||||
constructor() {
|
||||
/**
|
||||
* A manager of the messages sent to this channel
|
||||
* @type {MessageManager}
|
||||
*/
|
||||
this.messages = new MessageManager(this);
|
||||
|
||||
/**
|
||||
* The channel's last message id, if one was sent
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.lastMessageId = null;
|
||||
|
||||
/**
|
||||
* The timestamp when the last pinned message was pinned, if there was one
|
||||
* @type {?number}
|
||||
*/
|
||||
this.lastPinTimestamp = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Message object of the last message in the channel, if one was sent
|
||||
* @type {?Message}
|
||||
* @readonly
|
||||
*/
|
||||
get lastMessage() {
|
||||
return this.messages.resolve(this.lastMessageId);
|
||||
}
|
||||
|
||||
/**
|
||||
* The date when the last pinned message was pinned, if there was one
|
||||
* @type {?Date}
|
||||
* @readonly
|
||||
*/
|
||||
get lastPinAt() {
|
||||
return this.lastPinTimestamp ? new Date(this.lastPinTimestamp) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base options provided when sending.
|
||||
* @typedef {Object} BaseMessageOptions
|
||||
* @property {boolean} [tts=false] Whether or not the message should be spoken aloud
|
||||
* @property {string} [nonce=''] The nonce for the message
|
||||
* @property {string} [content=''] The content for the message
|
||||
* @property {WebEmbed[]|MessageEmbed[]|APIEmbed[]} [embeds] The embeds for the message
|
||||
* (see [here](https://discord.com/developers/docs/resources/channel#embed-object) for more details)
|
||||
* @property {MessageMentionOptions} [allowedMentions] Which mentions should be parsed from the message content
|
||||
* (see [here](https://discord.com/developers/docs/resources/channel#allowed-mentions-object) for more details)
|
||||
* @property {FileOptions[]|BufferResolvable[]|MessageAttachment[]} [files] Files to send with the message
|
||||
* @property {MessageActionRow[]|MessageActionRowOptions[]} [components]
|
||||
* Action rows containing interactive components for the message (buttons, select menus)
|
||||
* @property {MessageAttachment[]} [attachments] Attachments to send in the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options provided when sending or editing a message.
|
||||
* @typedef {BaseMessageOptions} MessageOptions
|
||||
* @property {ReplyOptions} [reply] The options for replying to a message
|
||||
* @property {StickerResolvable[]} [stickers=[]] Stickers to send in the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options provided to control parsing of mentions by Discord
|
||||
* @typedef {Object} MessageMentionOptions
|
||||
* @property {MessageMentionTypes[]} [parse] Types of mentions to be parsed
|
||||
* @property {Snowflake[]} [users] Snowflakes of Users to be parsed as mentions
|
||||
* @property {Snowflake[]} [roles] Snowflakes of Roles to be parsed as mentions
|
||||
* @property {boolean} [repliedUser=true] Whether the author of the Message being replied to should be pinged
|
||||
*/
|
||||
|
||||
/**
|
||||
* Types of mentions to enable in MessageMentionOptions.
|
||||
* - `roles`
|
||||
* - `users`
|
||||
* - `everyone`
|
||||
* @typedef {string} MessageMentionTypes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} FileOptions
|
||||
* @property {BufferResolvable} attachment File to attach
|
||||
* @property {string} [name='file.jpg'] Filename of the attachment
|
||||
* @property {string} description The description of the file
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options for sending a message with a reply.
|
||||
* @typedef {Object} ReplyOptions
|
||||
* @property {MessageResolvable} messageReference The message to reply to (must be in the same channel and not system)
|
||||
* @property {boolean} [failIfNotExists=true] Whether to error if the referenced message
|
||||
* does not exist (creates a standard message in this case when false)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends a message to this channel.
|
||||
* @param {string|MessagePayload|MessageOptions} options The options to provide
|
||||
* @returns {Promise<Message>}
|
||||
* @example
|
||||
* // Send a basic message
|
||||
* channel.send('hello!')
|
||||
* .then(message => console.log(`Sent message: ${message.content}`))
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Send a remote file
|
||||
* channel.send({
|
||||
* files: ['https://cdn.discordapp.com/icons/222078108977594368/6e1019b3179d71046e463a75915e7244.png?size=2048']
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Send a local file
|
||||
* channel.send({
|
||||
* files: [{
|
||||
* attachment: 'entire/path/to/file.jpg',
|
||||
* name: 'file.jpg'
|
||||
* description: 'A description of the file'
|
||||
* }]
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
* @example
|
||||
* // Send an embed with a local image inside
|
||||
* channel.send({
|
||||
* content: 'This is an embed',
|
||||
* embeds: [
|
||||
* {
|
||||
* thumbnail: {
|
||||
* url: 'attachment://file.jpg'
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* files: [{
|
||||
* attachment: 'entire/path/to/file.jpg',
|
||||
* name: 'file.jpg'
|
||||
* description: 'A description of the file'
|
||||
* }]
|
||||
* })
|
||||
* .then(console.log)
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async send(options) {
|
||||
const User = require('../User');
|
||||
const { GuildMember } = require('../GuildMember');
|
||||
|
||||
if (this instanceof User || this instanceof GuildMember) {
|
||||
const dm = await this.createDM();
|
||||
return dm.send(options);
|
||||
}
|
||||
|
||||
let messagePayload;
|
||||
|
||||
if (options instanceof MessagePayload) {
|
||||
messagePayload = await options.resolveData();
|
||||
} else {
|
||||
messagePayload = await MessagePayload.create(this, options).resolveData();
|
||||
}
|
||||
|
||||
const { data, files } = await messagePayload.resolveFiles();
|
||||
const d = await this.client.api.channels[this.id].messages.post({ data, files });
|
||||
|
||||
return this.messages.cache.get(d.id) ?? this.messages._add(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a typing indicator in the channel.
|
||||
* @returns {Promise<void>} Resolves upon the typing status being sent
|
||||
* @example
|
||||
* // Start typing in a channel
|
||||
* channel.sendTyping();
|
||||
*/
|
||||
async sendTyping() {
|
||||
await this.client.api.channels(this.id).typing.post();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Message Collector.
|
||||
* @param {MessageCollectorOptions} [options={}] The options to pass to the collector
|
||||
* @returns {MessageCollector}
|
||||
* @example
|
||||
* // Create a message collector
|
||||
* const filter = m => m.content.includes('discord');
|
||||
* const collector = channel.createMessageCollector({ filter, time: 15_000 });
|
||||
* collector.on('collect', m => console.log(`Collected ${m.content}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageCollector(options = {}) {
|
||||
return new MessageCollector(this, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing the same properties as CollectorOptions, but a few more:
|
||||
* @typedef {MessageCollectorOptions} AwaitMessagesOptions
|
||||
* @property {string[]} [errors] Stop/end reasons that cause the promise to reject
|
||||
*/
|
||||
|
||||
/**
|
||||
* Similar to createMessageCollector but in promise form.
|
||||
* Resolves with a collection of messages that pass the specified filter.
|
||||
* @param {AwaitMessagesOptions} [options={}] Optional options to pass to the internal collector
|
||||
* @returns {Promise<Collection<Snowflake, Message>>}
|
||||
* @example
|
||||
* // Await !vote messages
|
||||
* const filter = m => m.content.startsWith('!vote');
|
||||
* // Errors: ['time'] treats ending because of the time limit as an error
|
||||
* channel.awaitMessages({ filter, max: 4, time: 60_000, errors: ['time'] })
|
||||
* .then(collected => console.log(collected.size))
|
||||
* .catch(collected => console.log(`After a minute, only ${collected.size} out of 4 voted.`));
|
||||
*/
|
||||
awaitMessages(options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = this.createMessageCollector(options);
|
||||
collector.once('end', (collection, reason) => {
|
||||
if (options.errors?.includes(reason)) {
|
||||
reject(collection);
|
||||
} else {
|
||||
resolve(collection);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a button interaction collector.
|
||||
* @param {MessageComponentCollectorOptions} [options={}] Options to send to the collector
|
||||
* @returns {InteractionCollector}
|
||||
* @example
|
||||
* // Create a button interaction collector
|
||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
||||
* const collector = channel.createMessageComponentCollector({ filter, time: 15_000 });
|
||||
* collector.on('collect', i => console.log(`Collected ${i.customId}`));
|
||||
* collector.on('end', collected => console.log(`Collected ${collected.size} items`));
|
||||
*/
|
||||
createMessageComponentCollector(options = {}) {
|
||||
return new InteractionCollector(this.client, {
|
||||
...options,
|
||||
interactionType: InteractionTypes.MESSAGE_COMPONENT,
|
||||
channel: this,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects a single component interaction that passes the filter.
|
||||
* The Promise will reject if the time expires.
|
||||
* @param {AwaitMessageComponentOptions} [options={}] Options to pass to the internal collector
|
||||
* @returns {Promise<MessageComponentInteraction>}
|
||||
* @example
|
||||
* // Collect a message component interaction
|
||||
* const filter = (interaction) => interaction.customId === 'button' && interaction.user.id === 'someId';
|
||||
* channel.awaitMessageComponent({ filter, time: 15_000 })
|
||||
* .then(interaction => console.log(`${interaction.customId} was clicked!`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
awaitMessageComponent(options = {}) {
|
||||
const _options = { ...options, max: 1 };
|
||||
return new Promise((resolve, reject) => {
|
||||
const collector = this.createMessageComponentCollector(_options);
|
||||
collector.once('end', (interactions, reason) => {
|
||||
const interaction = interactions.first();
|
||||
if (interaction) resolve(interaction);
|
||||
else reject(new Error('INTERACTION_COLLECTOR_ERROR', reason));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk deletes given messages that are newer than two weeks.
|
||||
* @param {Collection<Snowflake, Message>|MessageResolvable[]|number} messages
|
||||
* Messages or number of messages to delete
|
||||
* @param {boolean} [filterOld=false] Filter messages to remove those which are older than two weeks automatically
|
||||
* @returns {Promise<Collection<Snowflake, Message>>} Returns the deleted messages
|
||||
* @example
|
||||
* // Bulk delete messages
|
||||
* channel.bulkDelete(5)
|
||||
* .then(messages => console.log(`Bulk deleted ${messages.size} messages`))
|
||||
* .catch(console.error);
|
||||
*/
|
||||
async bulkDelete(messages, filterOld = false) {
|
||||
if (Array.isArray(messages) || messages instanceof Collection) {
|
||||
let messageIds = messages instanceof Collection ? [...messages.keys()] : messages.map(m => m.id ?? m);
|
||||
if (filterOld) {
|
||||
messageIds = messageIds.filter(id => Date.now() - SnowflakeUtil.timestampFrom(id) < 1_209_600_000);
|
||||
}
|
||||
if (messageIds.length === 0) return new Collection();
|
||||
if (messageIds.length === 1) {
|
||||
await this.client.api.channels(this.id).messages(messageIds[0]).delete();
|
||||
const message = this.client.actions.MessageDelete.getMessage(
|
||||
{
|
||||
message_id: messageIds[0],
|
||||
},
|
||||
this,
|
||||
);
|
||||
return message ? new Collection([[message.id, message]]) : new Collection();
|
||||
}
|
||||
await this.client.api.channels[this.id].messages['bulk-delete'].post({ data: { messages: messageIds } });
|
||||
return messageIds.reduce(
|
||||
(col, id) =>
|
||||
col.set(
|
||||
id,
|
||||
this.client.actions.MessageDeleteBulk.getMessage(
|
||||
{
|
||||
message_id: id,
|
||||
},
|
||||
this,
|
||||
),
|
||||
),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
if (!isNaN(messages)) {
|
||||
const msgs = await this.messages.fetch({ limit: messages });
|
||||
return this.bulkDelete(msgs, filterOld);
|
||||
}
|
||||
throw new TypeError('MESSAGE_BULK_DELETE_TYPE');
|
||||
}
|
||||
|
||||
static applyToClass(structure, full = false, ignore = []) {
|
||||
const props = ['send'];
|
||||
if (full) {
|
||||
props.push(
|
||||
'lastMessage',
|
||||
'lastPinAt',
|
||||
'bulkDelete',
|
||||
'sendTyping',
|
||||
'createMessageCollector',
|
||||
'awaitMessages',
|
||||
'createMessageComponentCollector',
|
||||
'awaitMessageComponent',
|
||||
);
|
||||
}
|
||||
for (const prop of props) {
|
||||
if (ignore.includes(prop)) continue;
|
||||
Object.defineProperty(
|
||||
structure.prototype,
|
||||
prop,
|
||||
Object.getOwnPropertyDescriptor(TextBasedChannel.prototype, prop),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextBasedChannel;
|
||||
|
||||
// Fixes Circular
|
||||
const MessageManager = require('../../managers/MessageManager');
|
||||
|
Loading…
Reference in New Issue
Block a user