refactor(util): make utility functions top level (#8052)

* refactor(util): make functions top level

* types: make channel typeguards more strict

* chore: make requested changes
This commit is contained in:
Suneet Tipirneni
2022-06-13 14:04:53 -04:00
committed by GitHub
parent 51eadf3737
commit e53d162198
39 changed files with 808 additions and 788 deletions

View File

@@ -4,7 +4,7 @@ const EventEmitter = require('node:events');
const { REST } = require('@discordjs/rest'); const { REST } = require('@discordjs/rest');
const { TypeError } = require('../errors'); const { TypeError } = require('../errors');
const Options = require('../util/Options'); const Options = require('../util/Options');
const Util = require('../util/Util'); const { mergeDefault, flatten } = require('../util/Util');
/** /**
* The base class for all clients. * The base class for all clients.
@@ -22,7 +22,7 @@ class BaseClient extends EventEmitter {
* The options the client was instantiated with * The options the client was instantiated with
* @type {ClientOptions} * @type {ClientOptions}
*/ */
this.options = Util.mergeDefault(Options.createDefault(), options); this.options = mergeDefault(Options.createDefault(), options);
/** /**
* The REST manager of the client * The REST manager of the client
@@ -63,7 +63,7 @@ class BaseClient extends EventEmitter {
} }
toJSON(...props) { toJSON(...props) {
return Util.flatten(this, { domain: false }, ...props); return flatten(this, { domain: false }, ...props);
} }
} }

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const Action = require('./Action'); const Action = require('./Action');
const { Channel } = require('../../structures/Channel'); const { createChannel } = require('../../util/Channels');
class ChannelUpdateAction extends Action { class ChannelUpdateAction extends Action {
handle(data) { handle(data) {
@@ -12,7 +12,7 @@ class ChannelUpdateAction extends Action {
const old = channel._update(data); const old = channel._update(data);
if (channel.type !== data.type) { if (channel.type !== data.type) {
const newChannel = Channel.create(this.client, data, channel.guild); const newChannel = createChannel(this.client, data, channel.guild);
for (const [id, message] of channel.messages.cache) newChannel.messages.cache.set(id, message); for (const [id, message] of channel.messages.cache) newChannel.messages.cache.set(id, message);
channel = newChannel; channel = newChannel;
this.client.channels.cache.set(channel.id, channel); this.client.channels.cache.set(channel.id, channel);

View File

@@ -34,7 +34,7 @@ exports.Sweepers = require('./util/Sweepers');
exports.SystemChannelFlagsBitField = require('./util/SystemChannelFlagsBitField'); exports.SystemChannelFlagsBitField = require('./util/SystemChannelFlagsBitField');
exports.ThreadMemberFlagsBitField = require('./util/ThreadMemberFlagsBitField'); exports.ThreadMemberFlagsBitField = require('./util/ThreadMemberFlagsBitField');
exports.UserFlagsBitField = require('./util/UserFlagsBitField'); exports.UserFlagsBitField = require('./util/UserFlagsBitField');
exports.Util = require('./util/Util'); __exportStar(require('./util/Util.js'), exports);
exports.version = require('../package.json').version; exports.version = require('../package.json').version;
// Managers // Managers

View File

@@ -4,6 +4,7 @@ const process = require('node:process');
const { Routes } = require('discord-api-types/v10'); const { Routes } = require('discord-api-types/v10');
const CachedManager = require('./CachedManager'); const CachedManager = require('./CachedManager');
const { Channel } = require('../structures/Channel'); const { Channel } = require('../structures/Channel');
const { createChannel } = require('../util/Channels');
const { ThreadChannelTypes } = require('../util/Constants'); const { ThreadChannelTypes } = require('../util/Constants');
const Events = require('../util/Events'); const Events = require('../util/Events');
@@ -46,7 +47,7 @@ class ChannelManager extends CachedManager {
return existing; return existing;
} }
const channel = Channel.create(this.client, data, guild, { allowUnknownGuild, fromInteraction }); const channel = createChannel(this.client, data, guild, { allowUnknownGuild, fromInteraction });
if (!channel) { if (!channel) {
this.client.emit(Events.Debug, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`); this.client.emit(Events.Debug, `Failed to find guild, or unknown type for channel ${data.id} ${data.type}`);

View File

@@ -12,7 +12,7 @@ const ThreadChannel = require('../structures/ThreadChannel');
const Webhook = require('../structures/Webhook'); const Webhook = require('../structures/Webhook');
const { ThreadChannelTypes } = require('../util/Constants'); const { ThreadChannelTypes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const Util = require('../util/Util'); const { setPosition } = require('../util/Util');
let cacheWarningEmitted = false; let cacheWarningEmitted = false;
@@ -296,7 +296,7 @@ class GuildChannelManager extends CachedManager {
async setPosition(channel, position, { relative, reason } = {}) { async setPosition(channel, position, { relative, reason } = {}) {
channel = this.resolve(channel); channel = this.resolve(channel);
if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable'); if (!channel) throw new TypeError('INVALID_TYPE', 'channel', 'GuildChannelResolvable');
const updatedChannels = await Util.setPosition( const updatedChannels = await setPosition(
channel, channel,
position, position,
relative, relative,

View File

@@ -7,7 +7,7 @@ const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors'); const { TypeError } = require('../errors');
const { Message } = require('../structures/Message'); const { Message } = require('../structures/Message');
const MessagePayload = require('../structures/MessagePayload'); const MessagePayload = require('../structures/MessagePayload');
const Util = require('../util/Util'); const { resolvePartialEmoji } = require('../util/Util');
/** /**
* Manages API methods for Messages and holds their cache. * Manages API methods for Messages and holds their cache.
@@ -223,7 +223,7 @@ class MessageManager extends CachedManager {
message = this.resolveId(message); message = this.resolveId(message);
if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable'); if (!message) throw new TypeError('INVALID_TYPE', 'message', 'MessageResolvable');
emoji = Util.resolvePartialEmoji(emoji); emoji = resolvePartialEmoji(emoji);
if (!emoji) throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable'); if (!emoji) throw new TypeError('EMOJI_TYPE', 'emoji', 'EmojiIdentifierResolvable');
const emojiId = emoji.id const emojiId = emoji.id

View File

@@ -8,8 +8,7 @@ const { TypeError } = require('../errors');
const { Role } = require('../structures/Role'); const { Role } = require('../structures/Role');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const PermissionsBitField = require('../util/PermissionsBitField'); const PermissionsBitField = require('../util/PermissionsBitField');
const { resolveColor } = require('../util/Util'); const { setPosition, resolveColor } = require('../util/Util');
const Util = require('../util/Util');
let cacheWarningEmitted = false; let cacheWarningEmitted = false;
@@ -246,7 +245,7 @@ class RoleManager extends CachedManager {
async setPosition(role, position, { relative, reason } = {}) { async setPosition(role, position, { relative, reason } = {}) {
role = this.resolve(role); role = this.resolve(role);
if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable'); if (!role) throw new TypeError('INVALID_TYPE', 'role', 'RoleResolvable');
const updatedRoles = await Util.setPosition( const updatedRoles = await setPosition(
role, role,
position, position,
relative, relative,

View File

@@ -6,7 +6,7 @@ const process = require('node:process');
const { setTimeout, clearTimeout } = require('node:timers'); const { setTimeout, clearTimeout } = require('node:timers');
const { setTimeout: sleep } = require('node:timers/promises'); const { setTimeout: sleep } = require('node:timers/promises');
const { Error } = require('../errors'); const { Error } = require('../errors');
const Util = require('../util/Util'); const { makeError, makePlainError } = require('../util/Util');
let childProcess = null; let childProcess = null;
let Worker = null; let Worker = null;
@@ -252,7 +252,7 @@ class Shard extends EventEmitter {
this.decrementMaxListeners(child); this.decrementMaxListeners(child);
this._fetches.delete(prop); this._fetches.delete(prop);
if (!message._error) resolve(message._result); if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error)); else reject(makeError(message._error));
}; };
this.incrementMaxListeners(child); this.incrementMaxListeners(child);
@@ -295,7 +295,7 @@ class Shard extends EventEmitter {
this.decrementMaxListeners(child); this.decrementMaxListeners(child);
this._evals.delete(_eval); this._evals.delete(_eval);
if (!message._error) resolve(message._result); if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error)); else reject(makeError(message._error));
}; };
this.incrementMaxListeners(child); this.incrementMaxListeners(child);
@@ -358,7 +358,7 @@ class Shard extends EventEmitter {
const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard }; const resp = { _sFetchProp: message._sFetchProp, _sFetchPropShard: message._sFetchPropShard };
this.manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then( this.manager.fetchClientValues(message._sFetchProp, message._sFetchPropShard).then(
results => this.send({ ...resp, _result: results }), results => this.send({ ...resp, _result: results }),
err => this.send({ ...resp, _error: Util.makePlainError(err) }), err => this.send({ ...resp, _error: makePlainError(err) }),
); );
return; return;
} }
@@ -368,7 +368,7 @@ class Shard extends EventEmitter {
const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard }; const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
this.manager._performOnShards('eval', [message._sEval], message._sEvalShard).then( this.manager._performOnShards('eval', [message._sEval], message._sEvalShard).then(
results => this.send({ ...resp, _result: results }), results => this.send({ ...resp, _result: results }),
err => this.send({ ...resp, _error: Util.makePlainError(err) }), err => this.send({ ...resp, _error: makePlainError(err) }),
); );
return; return;
} }

View File

@@ -3,7 +3,7 @@
const process = require('node:process'); const process = require('node:process');
const { Error } = require('../errors'); const { Error } = require('../errors');
const Events = require('../util/Events'); const Events = require('../util/Events');
const Util = require('../util/Util'); const { makeError, makePlainError } = require('../util/Util');
/** /**
* Helper class for sharded clients spawned as a child process/worker, such as from a {@link ShardingManager}. * Helper class for sharded clients spawned as a child process/worker, such as from a {@link ShardingManager}.
@@ -113,7 +113,7 @@ class ShardClientUtil {
parent.removeListener('message', listener); parent.removeListener('message', listener);
this.decrementMaxListeners(parent); this.decrementMaxListeners(parent);
if (!message._error) resolve(message._result); if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error)); else reject(makeError(message._error));
}; };
this.incrementMaxListeners(parent); this.incrementMaxListeners(parent);
parent.on('message', listener); parent.on('message', listener);
@@ -151,7 +151,7 @@ class ShardClientUtil {
parent.removeListener('message', listener); parent.removeListener('message', listener);
this.decrementMaxListeners(parent); this.decrementMaxListeners(parent);
if (!message._error) resolve(message._result); if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error)); else reject(makeError(message._error));
}; };
this.incrementMaxListeners(parent); this.incrementMaxListeners(parent);
parent.on('message', listener); parent.on('message', listener);
@@ -187,13 +187,13 @@ class ShardClientUtil {
for (const prop of props) value = value[prop]; for (const prop of props) value = value[prop];
this._respond('fetchProp', { _fetchProp: message._fetchProp, _result: value }); this._respond('fetchProp', { _fetchProp: message._fetchProp, _result: value });
} catch (err) { } catch (err) {
this._respond('fetchProp', { _fetchProp: message._fetchProp, _error: Util.makePlainError(err) }); this._respond('fetchProp', { _fetchProp: message._fetchProp, _error: makePlainError(err) });
} }
} else if (message._eval) { } else if (message._eval) {
try { try {
this._respond('eval', { _eval: message._eval, _result: await this.client._eval(message._eval) }); this._respond('eval', { _eval: message._eval, _result: await this.client._eval(message._eval) });
} catch (err) { } catch (err) {
this._respond('eval', { _eval: message._eval, _error: Util.makePlainError(err) }); this._respond('eval', { _eval: message._eval, _error: makePlainError(err) });
} }
} }
} }

View File

@@ -8,7 +8,7 @@ const { setTimeout: sleep } = require('node:timers/promises');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const Shard = require('./Shard'); const Shard = require('./Shard');
const { Error, TypeError, RangeError } = require('../errors'); const { Error, TypeError, RangeError } = require('../errors');
const Util = require('../util/Util'); const { mergeDefault, fetchRecommendedShards } = require('../util/Util');
/** /**
* This is a utility class that makes multi-process sharding of a bot an easy and painless experience. * This is a utility class that makes multi-process sharding of a bot an easy and painless experience.
@@ -47,7 +47,7 @@ class ShardingManager extends EventEmitter {
*/ */
constructor(file, options = {}) { constructor(file, options = {}) {
super(); super();
options = Util.mergeDefault( options = mergeDefault(
{ {
totalShards: 'auto', totalShards: 'auto',
mode: 'process', mode: 'process',
@@ -183,7 +183,7 @@ class ShardingManager extends EventEmitter {
async spawn({ amount = this.totalShards, delay = 5500, timeout = 30_000 } = {}) { async spawn({ amount = this.totalShards, delay = 5500, timeout = 30_000 } = {}) {
// Obtain/verify the number of shards to spawn // Obtain/verify the number of shards to spawn
if (amount === 'auto') { if (amount === 'auto') {
amount = await Util.fetchRecommendedShards(this.token); amount = await fetchRecommendedShards(this.token);
} else { } else {
if (typeof amount !== 'number' || isNaN(amount)) { if (typeof amount !== 'number' || isNaN(amount)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.'); throw new TypeError('CLIENT_INVALID_OPTION', 'Amount of shards', 'a number.');

View File

@@ -2,7 +2,7 @@
const { isJSONEncodable } = require('@discordjs/builders'); const { isJSONEncodable } = require('@discordjs/builders');
const Component = require('./Component'); const Component = require('./Component');
const Components = require('../util/Components'); const { createComponent } = require('../util/Components');
/** /**
* Represents an action row * Represents an action row
@@ -17,7 +17,7 @@ class ActionRow extends Component {
* @type {Component[]} * @type {Component[]}
* @readonly * @readonly
*/ */
this.components = components.map(c => Components.createComponent(c)); this.components = components.map(c => createComponent(c));
} }
/** /**

View File

@@ -1,8 +1,8 @@
'use strict'; 'use strict';
const { ActionRowBuilder: BuildersActionRow, ComponentBuilder, isJSONEncodable } = require('@discordjs/builders'); const { ActionRowBuilder: BuildersActionRow, ComponentBuilder, isJSONEncodable } = require('@discordjs/builders');
const Components = require('../util/Components'); const { createComponentBuilder } = require('../util/Components');
const Transformers = require('../util/Transformers'); const { toSnakeCase } = require('../util/Transformers');
/** /**
* Represents an action row builder. * Represents an action row builder.
@@ -11,8 +11,8 @@ const Transformers = require('../util/Transformers');
class ActionRowBuilder extends BuildersActionRow { class ActionRowBuilder extends BuildersActionRow {
constructor({ components, ...data } = {}) { constructor({ components, ...data } = {}) {
super({ super({
...Transformers.toSnakeCase(data), ...toSnakeCase(data),
components: components?.map(c => (c instanceof ComponentBuilder ? c : Components.createComponentBuilder(c))), components: components?.map(c => (c instanceof ComponentBuilder ? c : createComponentBuilder(c))),
}); });
} }

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Util = require('../util/Util'); const { basename, flatten } = require('../util/Util');
/** /**
* @typedef {Object} AttachmentPayload * @typedef {Object} AttachmentPayload
@@ -107,11 +107,11 @@ class Attachment {
* @readonly * @readonly
*/ */
get spoiler() { get spoiler() {
return Util.basename(this.url ?? this.name).startsWith('SPOILER_'); return basename(this.url ?? this.name).startsWith('SPOILER_');
} }
toJSON() { toJSON() {
return Util.flatten(this); return flatten(this);
} }
} }

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Util = require('../util/Util'); const { basename, flatten } = require('../util/Util');
/** /**
* Represents an attachment builder * Represents an attachment builder
@@ -82,11 +82,11 @@ class AttachmentBuilder {
* @readonly * @readonly
*/ */
get spoiler() { get spoiler() {
return Util.basename(this.name).startsWith('SPOILER_'); return basename(this.name).startsWith('SPOILER_');
} }
toJSON() { toJSON() {
return Util.flatten(this); return flatten(this);
} }
/** /**

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
/** /**
* Represents a data model that is identifiable by a Snowflake (i.e. Discord API data models). * Represents a data model that is identifiable by a Snowflake (i.e. Discord API data models).
@@ -32,7 +32,7 @@ class Base {
} }
toJSON(...props) { toJSON(...props) {
return Util.flatten(this, ...props); return flatten(this, ...props);
} }
valueOf() { valueOf() {

View File

@@ -1,8 +1,8 @@
'use strict'; 'use strict';
const { ButtonBuilder: BuildersButton, isJSONEncodable } = require('@discordjs/builders'); const { ButtonBuilder: BuildersButton, isJSONEncodable } = require('@discordjs/builders');
const Transformers = require('../util/Transformers'); const { toSnakeCase } = require('../util/Transformers');
const Util = require('../util/Util'); const { parseEmoji } = require('../util/Util');
/** /**
* Represents a button builder. * Represents a button builder.
@@ -10,9 +10,7 @@ const Util = require('../util/Util');
*/ */
class ButtonBuilder extends BuildersButton { class ButtonBuilder extends BuildersButton {
constructor({ emoji, ...data } = {}) { constructor({ emoji, ...data } = {}) {
super( super(toSnakeCase({ ...data, emoji: emoji && typeof emoji === 'string' ? parseEmoji(emoji) : emoji }));
Transformers.toSnakeCase({ ...data, emoji: emoji && typeof emoji === 'string' ? Util.parseEmoji(emoji) : emoji }),
);
} }
/** /**
@@ -22,7 +20,7 @@ class ButtonBuilder extends BuildersButton {
*/ */
setEmoji(emoji) { setEmoji(emoji) {
if (typeof emoji === 'string') { if (typeof emoji === 'string') {
return super.setEmoji(Util.parseEmoji(emoji)); return super.setEmoji(parseEmoji(emoji));
} }
return super.setEmoji(emoji); return super.setEmoji(emoji);
} }

View File

@@ -4,14 +4,6 @@ const { DiscordSnowflake } = require('@sapphire/snowflake');
const { ChannelType, Routes } = require('discord-api-types/v10'); const { ChannelType, Routes } = require('discord-api-types/v10');
const Base = require('./Base'); const Base = require('./Base');
const { ThreadChannelTypes } = require('../util/Constants'); const { ThreadChannelTypes } = require('../util/Constants');
let CategoryChannel;
let DMChannel;
let NewsChannel;
let StageChannel;
let TextChannel;
let ThreadChannel;
let VoiceChannel;
let DirectoryChannel;
/** /**
* Represents any channel on Discord. * Represents any channel on Discord.
@@ -142,66 +134,6 @@ class Channel extends Base {
return 'bitrate' in this; return 'bitrate' in this;
} }
static create(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) {
CategoryChannel ??= require('./CategoryChannel');
DMChannel ??= require('./DMChannel');
NewsChannel ??= require('./NewsChannel');
StageChannel ??= require('./StageChannel');
TextChannel ??= require('./TextChannel');
ThreadChannel ??= require('./ThreadChannel');
VoiceChannel ??= require('./VoiceChannel');
DirectoryChannel ??= require('./DirectoryChannel');
let channel;
if (!data.guild_id && !guild) {
if ((data.recipients && data.type !== ChannelType.GroupDM) || data.type === ChannelType.DM) {
channel = new DMChannel(client, data);
} else if (data.type === ChannelType.GroupDM) {
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 ChannelType.GuildText: {
channel = new TextChannel(guild, data, client);
break;
}
case ChannelType.GuildVoice: {
channel = new VoiceChannel(guild, data, client);
break;
}
case ChannelType.GuildCategory: {
channel = new CategoryChannel(guild, data, client);
break;
}
case ChannelType.GuildNews: {
channel = new NewsChannel(guild, data, client);
break;
}
case ChannelType.GuildStageVoice: {
channel = new StageChannel(guild, data, client);
break;
}
case ChannelType.GuildNewsThread:
case ChannelType.GuildPublicThread:
case ChannelType.GuildPrivateThread: {
channel = new ThreadChannel(guild, data, client, fromInteraction);
if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel);
break;
}
case ChannelType.GuildDirectory:
channel = new DirectoryChannel(guild, data, client);
break;
}
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
}
}
return channel;
}
toJSON(...props) { toJSON(...props) {
return super.toJSON({ createdTimestamp: true }, ...props); return super.toJSON({ createdTimestamp: true }, ...props);
} }

View File

@@ -1,8 +1,8 @@
'use strict'; 'use strict';
const { EmbedBuilder: BuildersEmbed, isJSONEncodable } = require('@discordjs/builders'); const { EmbedBuilder: BuildersEmbed, isJSONEncodable } = require('@discordjs/builders');
const Transformers = require('../util/Transformers'); const { toSnakeCase } = require('../util/Transformers');
const Util = require('../util/Util'); const { resolveColor } = require('../util/Util');
/** /**
* Represents an embed builder. * Represents an embed builder.
@@ -10,7 +10,7 @@ const Util = require('../util/Util');
*/ */
class EmbedBuilder extends BuildersEmbed { class EmbedBuilder extends BuildersEmbed {
constructor(data) { constructor(data) {
super(Transformers.toSnakeCase(data)); super(toSnakeCase(data));
} }
/** /**
@@ -19,7 +19,7 @@ class EmbedBuilder extends BuildersEmbed {
* @returns {EmbedBuilder} * @returns {EmbedBuilder}
*/ */
setColor(color) { setColor(color) {
return super.setColor(color && Util.resolveColor(color)); return super.setColor(color && resolveColor(color));
} }
/** /**

View File

@@ -27,7 +27,7 @@ const VoiceStateManager = require('../managers/VoiceStateManager');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const Status = require('../util/Status'); const Status = require('../util/Status');
const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField'); const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField');
const Util = require('../util/Util'); const { discordSort } = require('../util/Util');
/** /**
* Represents a guild (or a server) on Discord. * Represents a guild (or a server) on Discord.
@@ -1253,7 +1253,7 @@ class Guild extends AnonymousGuild {
* @private * @private
*/ */
_sortedRoles() { _sortedRoles() {
return Util.discordSort(this.roles.cache); return discordSort(this.roles.cache);
} }
/** /**
@@ -1265,7 +1265,7 @@ class Guild extends AnonymousGuild {
_sortedChannels(channel) { _sortedChannels(channel) {
const category = channel.type === ChannelType.GuildCategory; const category = channel.type === ChannelType.GuildCategory;
const channelTypes = [ChannelType.GuildText, ChannelType.GuildNews]; const channelTypes = [ChannelType.GuildText, ChannelType.GuildNews];
return Util.discordSort( return discordSort(
this.channels.cache.filter( this.channels.cache.filter(
c => c =>
(channelTypes.includes(channel.type) ? channelTypes.includes(c.type) : c.type === channel.type) && (channelTypes.includes(channel.type) ? channelTypes.includes(c.type) : c.type === channel.type) &&

View File

@@ -5,7 +5,7 @@ const ApplicationCommand = require('./ApplicationCommand');
const GuildAuditLogsEntry = require('./GuildAuditLogsEntry'); const GuildAuditLogsEntry = require('./GuildAuditLogsEntry');
const Integration = require('./Integration'); const Integration = require('./Integration');
const Webhook = require('./Webhook'); const Webhook = require('./Webhook');
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
/** /**
* The target type of an entry. Here are the available types: * The target type of an entry. Here are the available types:
@@ -92,7 +92,7 @@ class GuildAuditLogs {
} }
toJSON() { toJSON() {
return Util.flatten(this); return flatten(this);
} }
} }

View File

@@ -9,7 +9,7 @@ const { StageInstance } = require('./StageInstance');
const { Sticker } = require('./Sticker'); const { Sticker } = require('./Sticker');
const Webhook = require('./Webhook'); const Webhook = require('./Webhook');
const Partials = require('../util/Partials'); const Partials = require('../util/Partials');
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
const Targets = { const Targets = {
All: 'All', All: 'All',
@@ -459,7 +459,7 @@ class GuildAuditLogsEntry {
} }
toJSON() { toJSON() {
return Util.flatten(this, { createdTimestamp: true }); return flatten(this, { createdTimestamp: true });
} }
} }

View File

@@ -20,11 +20,11 @@ const ReactionCollector = require('./ReactionCollector');
const { Sticker } = require('./Sticker'); const { Sticker } = require('./Sticker');
const { Error } = require('../errors'); const { Error } = require('../errors');
const ReactionManager = require('../managers/ReactionManager'); const ReactionManager = require('../managers/ReactionManager');
const Components = require('../util/Components'); const { createComponent } = require('../util/Components');
const { NonSystemMessageTypes } = require('../util/Constants'); const { NonSystemMessageTypes } = require('../util/Constants');
const MessageFlagsBitField = require('../util/MessageFlagsBitField'); const MessageFlagsBitField = require('../util/MessageFlagsBitField');
const PermissionsBitField = require('../util/PermissionsBitField'); const PermissionsBitField = require('../util/PermissionsBitField');
const Util = require('../util/Util'); const { cleanContent, resolvePartialEmoji } = require('../util/Util');
/** /**
* Represents a message on Discord. * Represents a message on Discord.
@@ -146,7 +146,7 @@ class Message extends Base {
* A list of MessageActionRows in the message * A list of MessageActionRows in the message
* @type {ActionRow[]} * @type {ActionRow[]}
*/ */
this.components = data.components.map(c => Components.createComponent(c)); this.components = data.components.map(c => createComponent(c));
} else { } else {
this.components = this.components?.slice() ?? []; this.components = this.components?.slice() ?? [];
} }
@@ -441,7 +441,7 @@ class Message extends Base {
*/ */
get cleanContent() { get cleanContent() {
// eslint-disable-next-line eqeqeq // eslint-disable-next-line eqeqeq
return this.content != null ? Util.cleanContent(this.content, this.channel) : null; return this.content != null ? cleanContent(this.content, this.channel) : null;
} }
/** /**
@@ -737,7 +737,7 @@ class Message extends Base {
user: this.client.user, user: this.client.user,
channel: this.channel, channel: this.channel,
message: this, message: this,
emoji: Util.resolvePartialEmoji(emoji), emoji: resolvePartialEmoji(emoji),
}, },
true, true,
).reaction; ).reaction;

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
/** /**
* Keeps track of mentions in a {@link Message}. * Keeps track of mentions in a {@link Message}.
@@ -233,7 +233,7 @@ class MessageMentions {
} }
toJSON() { toJSON() {
return Util.flatten(this, { return flatten(this, {
members: true, members: true,
channels: true, channels: true,
}); });

View File

@@ -7,7 +7,7 @@ const ActionRowBuilder = require('./ActionRowBuilder');
const { RangeError } = require('../errors'); const { RangeError } = require('../errors');
const DataResolver = require('../util/DataResolver'); const DataResolver = require('../util/DataResolver');
const MessageFlagsBitField = require('../util/MessageFlagsBitField'); const MessageFlagsBitField = require('../util/MessageFlagsBitField');
const Util = require('../util/Util'); const { basename, cloneObject, verifyString } = require('../util/Util');
/** /**
* Represents a message to be sent to the API. * Represents a message to be sent to the API.
@@ -105,7 +105,7 @@ class MessagePayload {
if (this.options.content === null) { if (this.options.content === null) {
content = ''; content = '';
} else if (typeof this.options.content !== 'undefined') { } else if (typeof this.options.content !== 'undefined') {
content = Util.verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', true); content = verifyString(this.options.content, RangeError, 'MESSAGE_CONTENT_TYPE', true);
} }
return content; return content;
@@ -164,7 +164,7 @@ class MessagePayload {
: this.options.allowedMentions; : this.options.allowedMentions;
if (allowedMentions) { if (allowedMentions) {
allowedMentions = Util.cloneObject(allowedMentions); allowedMentions = cloneObject(allowedMentions);
allowedMentions.replied_user = allowedMentions.repliedUser; allowedMentions.replied_user = allowedMentions.repliedUser;
delete allowedMentions.repliedUser; delete allowedMentions.repliedUser;
} }
@@ -234,11 +234,11 @@ class MessagePayload {
const findName = thing => { const findName = thing => {
if (typeof thing === 'string') { if (typeof thing === 'string') {
return Util.basename(thing); return basename(thing);
} }
if (thing.path) { if (thing.path) {
return Util.basename(thing.path); return basename(thing.path);
} }
return 'file.jpg'; return 'file.jpg';

View File

@@ -4,7 +4,7 @@ const { Routes } = require('discord-api-types/v10');
const GuildEmoji = require('./GuildEmoji'); const GuildEmoji = require('./GuildEmoji');
const ReactionEmoji = require('./ReactionEmoji'); const ReactionEmoji = require('./ReactionEmoji');
const ReactionUserManager = require('../managers/ReactionUserManager'); const ReactionUserManager = require('../managers/ReactionUserManager');
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
/** /**
* Represents a reaction to a message. * Represents a reaction to a message.
@@ -114,7 +114,7 @@ class MessageReaction {
} }
toJSON() { toJSON() {
return Util.flatten(this, { emoji: 'emojiId', message: 'messageId' }); return flatten(this, { emoji: 'emojiId', message: 'messageId' });
} }
_add(user) { _add(user) {

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const { ModalBuilder: BuildersModal, ComponentBuilder, isJSONEncodable } = require('@discordjs/builders'); const { ModalBuilder: BuildersModal, ComponentBuilder, isJSONEncodable } = require('@discordjs/builders');
const Transformers = require('../util/Transformers'); const { toSnakeCase } = require('../util/Transformers');
/** /**
* Represents a modal builder. * Represents a modal builder.
@@ -10,8 +10,8 @@ const Transformers = require('../util/Transformers');
class ModalBuilder extends BuildersModal { class ModalBuilder extends BuildersModal {
constructor({ components, ...data } = {}) { constructor({ components, ...data } = {}) {
super({ super({
...Transformers.toSnakeCase(data), ...toSnakeCase(data),
components: components?.map(c => (c instanceof ComponentBuilder ? c : Transformers.toSnakeCase(c))), components: components?.map(c => (c instanceof ComponentBuilder ? c : toSnakeCase(c))),
}); });
} }

View File

@@ -3,7 +3,7 @@
const Base = require('./Base'); const Base = require('./Base');
const { Emoji } = require('./Emoji'); const { Emoji } = require('./Emoji');
const ActivityFlagsBitField = require('../util/ActivityFlagsBitField'); const ActivityFlagsBitField = require('../util/ActivityFlagsBitField');
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
/** /**
* Activity sent in a message. * Activity sent in a message.
@@ -132,7 +132,7 @@ class Presence extends Base {
} }
toJSON() { toJSON() {
return Util.flatten(this); return flatten(this);
} }
} }

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const { Emoji } = require('./Emoji'); const { Emoji } = require('./Emoji');
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
/** /**
* Represents a limited emoji set used for both custom and unicode emojis. Custom emojis * Represents a limited emoji set used for both custom and unicode emojis. Custom emojis
@@ -20,7 +20,7 @@ class ReactionEmoji extends Emoji {
} }
toJSON() { toJSON() {
return Util.flatten(this, { identifier: true }); return flatten(this, { identifier: true });
} }
valueOf() { valueOf() {

View File

@@ -1,8 +1,8 @@
'use strict'; 'use strict';
const { SelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders'); const { SelectMenuBuilder: BuildersSelectMenu, isJSONEncodable, normalizeArray } = require('@discordjs/builders');
const Transformers = require('../util/Transformers'); const { toSnakeCase } = require('../util/Transformers');
const Util = require('../util/Util'); const { parseEmoji } = require('../util/Util');
/** /**
* Class used to build select menu components to be sent through the API * Class used to build select menu components to be sent through the API
@@ -11,11 +11,11 @@ const Util = require('../util/Util');
class SelectMenuBuilder extends BuildersSelectMenu { class SelectMenuBuilder extends BuildersSelectMenu {
constructor({ options, ...data } = {}) { constructor({ options, ...data } = {}) {
super( super(
Transformers.toSnakeCase({ toSnakeCase({
...data, ...data,
options: options?.map(({ emoji, ...option }) => ({ options: options?.map(({ emoji, ...option }) => ({
...option, ...option,
emoji: emoji && typeof emoji === 'string' ? Util.parseEmoji(emoji) : emoji, emoji: emoji && typeof emoji === 'string' ? parseEmoji(emoji) : emoji,
})), })),
}), }),
); );
@@ -30,7 +30,7 @@ class SelectMenuBuilder extends BuildersSelectMenu {
return super.addOptions( return super.addOptions(
normalizeArray(options).map(({ emoji, ...option }) => ({ normalizeArray(options).map(({ emoji, ...option }) => ({
...option, ...option,
emoji: emoji && typeof emoji === 'string' ? Util.parseEmoji(emoji) : emoji, emoji: emoji && typeof emoji === 'string' ? parseEmoji(emoji) : emoji,
})), })),
); );
} }
@@ -44,7 +44,7 @@ class SelectMenuBuilder extends BuildersSelectMenu {
return super.setOptions( return super.setOptions(
normalizeArray(options).map(({ emoji, ...option }) => ({ normalizeArray(options).map(({ emoji, ...option }) => ({
...option, ...option,
emoji: emoji && typeof emoji === 'string' ? Util.parseEmoji(emoji) : emoji, emoji: emoji && typeof emoji === 'string' ? parseEmoji(emoji) : emoji,
})), })),
); );
} }

View File

@@ -1,8 +1,8 @@
'use strict'; 'use strict';
const { SelectMenuOptionBuilder: BuildersSelectMenuOption, isJSONEncodable } = require('@discordjs/builders'); const { SelectMenuOptionBuilder: BuildersSelectMenuOption, isJSONEncodable } = require('@discordjs/builders');
const Transformers = require('../util/Transformers'); const { toSnakeCase } = require('../util/Transformers');
const Util = require('../util/Util'); const { parseEmoji } = require('../util/Util');
/** /**
* Represents a select menu option builder. * Represents a select menu option builder.
@@ -11,9 +11,9 @@ const Util = require('../util/Util');
class SelectMenuOptionBuilder extends BuildersSelectMenuOption { class SelectMenuOptionBuilder extends BuildersSelectMenuOption {
constructor({ emoji, ...data } = {}) { constructor({ emoji, ...data } = {}) {
super( super(
Transformers.toSnakeCase({ toSnakeCase({
...data, ...data,
emoji: emoji && typeof emoji === 'string' ? Util.parseEmoji(emoji) : emoji, emoji: emoji && typeof emoji === 'string' ? parseEmoji(emoji) : emoji,
}), }),
); );
} }
@@ -24,7 +24,7 @@ class SelectMenuOptionBuilder extends BuildersSelectMenuOption {
*/ */
setEmoji(emoji) { setEmoji(emoji) {
if (typeof emoji === 'string') { if (typeof emoji === 'string') {
return super.setEmoji(Util.parseEmoji(emoji)); return super.setEmoji(parseEmoji(emoji));
} }
return super.setEmoji(emoji); return super.setEmoji(emoji);
} }

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
const { TextInputBuilder: BuildersTextInput, isJSONEncodable } = require('@discordjs/builders'); const { TextInputBuilder: BuildersTextInput, isJSONEncodable } = require('@discordjs/builders');
const Transformers = require('../util/Transformers'); const { toSnakeCase } = require('../util/Transformers');
/** /**
* Represents a text input builder. * Represents a text input builder.
@@ -9,7 +9,7 @@ const Transformers = require('../util/Transformers');
*/ */
class TextInputBuilder extends BuildersTextInput { class TextInputBuilder extends BuildersTextInput {
constructor(data) { constructor(data) {
super(Transformers.toSnakeCase(data)); super(toSnakeCase(data));
} }
/** /**

View File

@@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Util = require('../util/Util'); const { flatten } = require('../util/Util');
/** /**
* Represents a Discord voice region for guilds. * Represents a Discord voice region for guilds.
@@ -39,7 +39,7 @@ class VoiceRegion {
} }
toJSON() { toJSON() {
return Util.flatten(this); return flatten(this);
} }
} }

View File

@@ -4,7 +4,7 @@ const EventEmitter = require('node:events');
const { setTimeout, clearTimeout } = require('node:timers'); const { setTimeout, clearTimeout } = require('node:timers');
const { Collection } = require('@discordjs/collection'); const { Collection } = require('@discordjs/collection');
const { TypeError } = require('../../errors'); const { TypeError } = require('../../errors');
const Util = require('../../util/Util'); const { flatten } = require('../../util/Util');
/** /**
* Filter to be applied to the collector. * Filter to be applied to the collector.
@@ -281,7 +281,7 @@ class Collector extends EventEmitter {
} }
toJSON() { toJSON() {
return Util.flatten(this); return flatten(this);
} }
/* eslint-disable no-empty-function */ /* eslint-disable no-empty-function */

View File

@@ -0,0 +1,72 @@
'use strict';
const { ChannelType } = require('discord-api-types/v10');
const { lazy } = require('./Util');
const getCategoryChannel = lazy(() => require('../structures/CategoryChannel'));
const getDMChannel = lazy(() => require('../structures/DMChannel'));
const getNewsChannel = lazy(() => require('../structures/NewsChannel'));
const getStageChannel = lazy(() => require('../structures/StageChannel'));
const getTextChannel = lazy(() => require('../structures/TextChannel'));
const getThreadChannel = lazy(() => require('../structures/ThreadChannel'));
const getVoiceChannel = lazy(() => require('../structures/VoiceChannel'));
const getDirectoryChannel = lazy(() => require('../structures/DirectoryChannel'));
const getPartialGroupDMChannel = lazy(() => require('../structures/PartialGroupDMChannel'));
// eslint-disable-next-line valid-jsdoc
/**
* @private
*/
function createChannel(client, data, guild, { allowUnknownGuild, fromInteraction } = {}) {
let channel;
if (!data.guild_id && !guild) {
if ((data.recipients && data.type !== ChannelType.GroupDM) || data.type === ChannelType.DM) {
channel = new (getDMChannel())(client, data);
} else if (data.type === ChannelType.GroupDM) {
channel = new (getPartialGroupDMChannel())(client, data);
}
} else {
guild ??= client.guilds.cache.get(data.guild_id);
if (guild || allowUnknownGuild) {
switch (data.type) {
case ChannelType.GuildText: {
channel = new (getTextChannel())(guild, data, client);
break;
}
case ChannelType.GuildVoice: {
channel = new (getVoiceChannel())(guild, data, client);
break;
}
case ChannelType.GuildCategory: {
channel = new (getCategoryChannel())(guild, data, client);
break;
}
case ChannelType.GuildNews: {
channel = new (getNewsChannel())(guild, data, client);
break;
}
case ChannelType.GuildStageVoice: {
channel = new (getStageChannel())(guild, data, client);
break;
}
case ChannelType.GuildNewsThread:
case ChannelType.GuildPublicThread:
case ChannelType.GuildPrivateThread: {
channel = new (getThreadChannel())(guild, data, client, fromInteraction);
if (!allowUnknownGuild) channel.parent?.threads.cache.set(channel.id, channel);
break;
}
case ChannelType.GuildDirectory:
channel = new (getDirectoryChannel())(guild, data, client);
break;
}
if (channel && !allowUnknownGuild) guild.channels?.cache.set(channel.id, channel);
}
}
return channel;
}
module.exports = {
createChannel,
};

View File

@@ -67,13 +67,12 @@ const { ComponentType } = require('discord-api-types/v10');
* @typedef {APIMessageComponentEmoji|string} ComponentEmojiResolvable * @typedef {APIMessageComponentEmoji|string} ComponentEmojiResolvable
*/ */
class Components extends null {
/** /**
* Transforms API data into a component * Transforms API data into a component
* @param {APIMessageComponent|Component} data The data to create the component from * @param {APIMessageComponent|Component} data The data to create the component from
* @returns {Component} * @returns {Component}
*/ */
static createComponent(data) { function createComponent(data) {
if (data instanceof Component) { if (data instanceof Component) {
return data; return data;
} }
@@ -97,7 +96,7 @@ class Components extends null {
* @param {APIMessageComponent|ComponentBuilder} data The data to create the component from * @param {APIMessageComponent|ComponentBuilder} data The data to create the component from
* @returns {ComponentBuilder} * @returns {ComponentBuilder}
*/ */
static createComponentBuilder(data) { function createComponentBuilder(data) {
if (data instanceof ComponentBuilder) { if (data instanceof ComponentBuilder) {
return data; return data;
} }
@@ -115,9 +114,8 @@ class Components extends null {
throw new Error(`Found unknown component type: ${data.type}`); throw new Error(`Found unknown component type: ${data.type}`);
} }
} }
}
module.exports = Components; module.exports = { createComponent, createComponentBuilder };
const ActionRow = require('../structures/ActionRow'); const ActionRow = require('../structures/ActionRow');
const ActionRowBuilder = require('../structures/ActionRowBuilder'); const ActionRowBuilder = require('../structures/ActionRowBuilder');

View File

@@ -2,7 +2,7 @@
const process = require('node:process'); const process = require('node:process');
const { DefaultRestOptions } = require('@discordjs/rest'); const { DefaultRestOptions } = require('@discordjs/rest');
const Transformers = require('./Transformers'); const { toSnakeCase } = require('./Transformers');
/** /**
* @typedef {Function} CacheFactory * @typedef {Function} CacheFactory
@@ -93,7 +93,7 @@ class Options extends null {
version: 10, version: 10,
}, },
rest: DefaultRestOptions, rest: DefaultRestOptions,
jsonTransformer: Transformers.toSnakeCase, jsonTransformer: toSnakeCase,
}; };
} }

View File

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

View File

@@ -8,17 +8,13 @@ const Colors = require('./Colors');
const { Error: DiscordError, RangeError, TypeError } = require('../errors'); const { Error: DiscordError, RangeError, TypeError } = require('../errors');
const isObject = d => typeof d === 'object' && d !== null; const isObject = d => typeof d === 'object' && d !== null;
/**
* Contains various general-purpose utility methods.
*/
class Util extends null {
/** /**
* Flatten an object. Any properties that are collections will get converted to an array of keys. * Flatten an object. Any properties that are collections will get converted to an array of keys.
* @param {Object} obj The object to flatten. * @param {Object} obj The object to flatten.
* @param {...Object<string, boolean|string>} [props] Specific properties to include/exclude. * @param {...Object<string, boolean|string>} [props] Specific properties to include/exclude.
* @returns {Object} * @returns {Object}
*/ */
static flatten(obj, ...props) { function flatten(obj, ...props) {
if (!isObject(obj)) return obj; if (!isObject(obj)) return obj;
const objProps = Object.keys(obj) const objProps = Object.keys(obj)
@@ -43,13 +39,13 @@ class Util extends null {
// If the valueOf is a Collection, use its array of keys // If the valueOf is a Collection, use its array of keys
else if (valueOf instanceof Collection) out[newProp] = Array.from(valueOf.keys()); else if (valueOf instanceof Collection) out[newProp] = Array.from(valueOf.keys());
// If it's an array, call toJSON function on each element if present, otherwise flatten each element // If it's an array, call toJSON function on each element if present, otherwise flatten each element
else if (Array.isArray(element)) out[newProp] = element.map(e => e.toJSON?.() ?? Util.flatten(e)); else if (Array.isArray(element)) out[newProp] = element.map(e => e.toJSON?.() ?? flatten(e));
// If it's an object with a primitive `valueOf`, use that value // If it's an object with a primitive `valueOf`, use that value
else if (typeof valueOf !== 'object') out[newProp] = valueOf; else if (typeof valueOf !== 'object') out[newProp] = valueOf;
// If it's an object with a toJSON function, use the return value of it // If it's an object with a toJSON function, use the return value of it
else if (hasToJSON) out[newProp] = element.toJSON(); else if (hasToJSON) out[newProp] = element.toJSON();
// If element is an object, use the flattened version of it // If element is an object, use the flattened version of it
else if (typeof element === 'object') out[newProp] = Util.flatten(element); else if (typeof element === 'object') out[newProp] = flatten(element);
// If it's a primitive // If it's a primitive
else if (!elemIsObj) out[newProp] = element; else if (!elemIsObj) out[newProp] = element;
} }
@@ -77,7 +73,7 @@ class Util extends null {
* @param {EscapeMarkdownOptions} [options={}] Options for escaping the markdown * @param {EscapeMarkdownOptions} [options={}] Options for escaping the markdown
* @returns {string} * @returns {string}
*/ */
static escapeMarkdown( function escapeMarkdown(
text, text,
{ {
codeBlock = true, codeBlock = true,
@@ -96,7 +92,7 @@ class Util extends null {
.split('```') .split('```')
.map((subString, index, array) => { .map((subString, index, array) => {
if (index % 2 && index !== array.length - 1) return subString; if (index % 2 && index !== array.length - 1) return subString;
return Util.escapeMarkdown(subString, { return escapeMarkdown(subString, {
inlineCode, inlineCode,
bold, bold,
italic, italic,
@@ -113,7 +109,7 @@ class Util extends null {
.split(/(?<=^|[^`])`(?=[^`]|$)/g) .split(/(?<=^|[^`])`(?=[^`]|$)/g)
.map((subString, index, array) => { .map((subString, index, array) => {
if (index % 2 && index !== array.length - 1) return subString; if (index % 2 && index !== array.length - 1) return subString;
return Util.escapeMarkdown(subString, { return escapeMarkdown(subString, {
codeBlock, codeBlock,
bold, bold,
italic, italic,
@@ -124,13 +120,13 @@ class Util extends null {
}) })
.join(inlineCode ? '\\`' : '`'); .join(inlineCode ? '\\`' : '`');
} }
if (inlineCode) text = Util.escapeInlineCode(text); if (inlineCode) text = escapeInlineCode(text);
if (codeBlock) text = Util.escapeCodeBlock(text); if (codeBlock) text = escapeCodeBlock(text);
if (italic) text = Util.escapeItalic(text); if (italic) text = escapeItalic(text);
if (bold) text = Util.escapeBold(text); if (bold) text = escapeBold(text);
if (underline) text = Util.escapeUnderline(text); if (underline) text = escapeUnderline(text);
if (strikethrough) text = Util.escapeStrikethrough(text); if (strikethrough) text = escapeStrikethrough(text);
if (spoiler) text = Util.escapeSpoiler(text); if (spoiler) text = escapeSpoiler(text);
return text; return text;
} }
@@ -139,7 +135,7 @@ class Util extends null {
* @param {string} text Content to escape * @param {string} text Content to escape
* @returns {string} * @returns {string}
*/ */
static escapeCodeBlock(text) { function escapeCodeBlock(text) {
return text.replaceAll('```', '\\`\\`\\`'); return text.replaceAll('```', '\\`\\`\\`');
} }
@@ -148,7 +144,7 @@ class Util extends null {
* @param {string} text Content to escape * @param {string} text Content to escape
* @returns {string} * @returns {string}
*/ */
static escapeInlineCode(text) { function escapeInlineCode(text) {
return text.replace(/(?<=^|[^`])``?(?=[^`]|$)/g, match => (match.length === 2 ? '\\`\\`' : '\\`')); return text.replace(/(?<=^|[^`])``?(?=[^`]|$)/g, match => (match.length === 2 ? '\\`\\`' : '\\`'));
} }
@@ -157,7 +153,7 @@ class Util extends null {
* @param {string} text Content to escape * @param {string} text Content to escape
* @returns {string} * @returns {string}
*/ */
static escapeItalic(text) { function escapeItalic(text) {
let i = 0; let i = 0;
text = text.replace(/(?<=^|[^*])\*([^*]|\*\*|$)/g, (_, match) => { text = text.replace(/(?<=^|[^*])\*([^*]|\*\*|$)/g, (_, match) => {
if (match === '**') return ++i % 2 ? `\\*${match}` : `${match}\\*`; if (match === '**') return ++i % 2 ? `\\*${match}` : `${match}\\*`;
@@ -175,7 +171,7 @@ class Util extends null {
* @param {string} text Content to escape * @param {string} text Content to escape
* @returns {string} * @returns {string}
*/ */
static escapeBold(text) { function escapeBold(text) {
let i = 0; let i = 0;
return text.replace(/\*\*(\*)?/g, (_, match) => { return text.replace(/\*\*(\*)?/g, (_, match) => {
if (match) return ++i % 2 ? `${match}\\*\\*` : `\\*\\*${match}`; if (match) return ++i % 2 ? `${match}\\*\\*` : `\\*\\*${match}`;
@@ -188,7 +184,7 @@ class Util extends null {
* @param {string} text Content to escape * @param {string} text Content to escape
* @returns {string} * @returns {string}
*/ */
static escapeUnderline(text) { function escapeUnderline(text) {
let i = 0; let i = 0;
return text.replace(/__(_)?/g, (_, match) => { return text.replace(/__(_)?/g, (_, match) => {
if (match) return ++i % 2 ? `${match}\\_\\_` : `\\_\\_${match}`; if (match) return ++i % 2 ? `${match}\\_\\_` : `\\_\\_${match}`;
@@ -201,7 +197,7 @@ class Util extends null {
* @param {string} text Content to escape * @param {string} text Content to escape
* @returns {string} * @returns {string}
*/ */
static escapeStrikethrough(text) { function escapeStrikethrough(text) {
return text.replaceAll('~~', '\\~\\~'); return text.replaceAll('~~', '\\~\\~');
} }
@@ -210,7 +206,7 @@ class Util extends null {
* @param {string} text Content to escape * @param {string} text Content to escape
* @returns {string} * @returns {string}
*/ */
static escapeSpoiler(text) { function escapeSpoiler(text) {
return text.replaceAll('||', '\\|\\|'); return text.replaceAll('||', '\\|\\|');
} }
@@ -226,7 +222,7 @@ class Util extends null {
* @param {FetchRecommendedShardsOptions} [options] Options for fetching the recommended shard count * @param {FetchRecommendedShardsOptions} [options] Options for fetching the recommended shard count
* @returns {Promise<number>} The recommended number of shards * @returns {Promise<number>} The recommended number of shards
*/ */
static async fetchRecommendedShards(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) { async function fetchRecommendedShards(token, { guildsPerShard = 1_000, multipleOf = 1 } = {}) {
if (!token) throw new DiscordError('TOKEN_MISSING'); if (!token) throw new DiscordError('TOKEN_MISSING');
const response = await fetch(RouteBases.api + Routes.gatewayBot(), { const response = await fetch(RouteBases.api + Routes.gatewayBot(), {
method: 'GET', method: 'GET',
@@ -249,7 +245,7 @@ class Util extends null {
* @returns {APIEmoji} Object with `animated`, `name`, and `id` properties * @returns {APIEmoji} Object with `animated`, `name`, and `id` properties
* @private * @private
*/ */
static parseEmoji(text) { function parseEmoji(text) {
if (text.includes('%')) text = decodeURIComponent(text); if (text.includes('%')) text = decodeURIComponent(text);
if (!text.includes(':')) return { animated: false, name: text, id: undefined }; if (!text.includes(':')) return { animated: false, name: text, id: undefined };
const match = text.match(/<?(?:(a):)?(\w{2,32}):(\d{17,19})?>?/); const match = text.match(/<?(?:(a):)?(\w{2,32}):(\d{17,19})?>?/);
@@ -262,9 +258,9 @@ class Util extends null {
* @returns {?RawEmoji} * @returns {?RawEmoji}
* @private * @private
*/ */
static resolvePartialEmoji(emoji) { function resolvePartialEmoji(emoji) {
if (!emoji) return null; if (!emoji) return null;
if (typeof emoji === 'string') return /^\d{17,19}$/.test(emoji) ? { id: emoji } : Util.parseEmoji(emoji); if (typeof emoji === 'string') return /^\d{17,19}$/.test(emoji) ? { id: emoji } : parseEmoji(emoji);
const { id, name, animated } = emoji; const { id, name, animated } = emoji;
if (!id && !name) return null; if (!id && !name) return null;
return { id, name, animated: Boolean(animated) }; return { id, name, animated: Boolean(animated) };
@@ -276,7 +272,7 @@ class Util extends null {
* @returns {Object} * @returns {Object}
* @private * @private
*/ */
static cloneObject(obj) { function cloneObject(obj) {
return Object.assign(Object.create(obj), obj); return Object.assign(Object.create(obj), obj);
} }
@@ -287,13 +283,13 @@ class Util extends null {
* @returns {Object} * @returns {Object}
* @private * @private
*/ */
static mergeDefault(def, given) { function mergeDefault(def, given) {
if (!given) return def; if (!given) return def;
for (const key in def) { for (const key in def) {
if (!Object.hasOwn(given, key) || given[key] === undefined) { if (!Object.hasOwn(given, key) || given[key] === undefined) {
given[key] = def[key]; given[key] = def[key];
} else if (given[key] === Object(given[key])) { } else if (given[key] === Object(given[key])) {
given[key] = Util.mergeDefault(def[key], given[key]); given[key] = mergeDefault(def[key], given[key]);
} }
} }
@@ -314,7 +310,7 @@ class Util extends null {
* @returns {Error} * @returns {Error}
* @private * @private
*/ */
static makeError(obj) { function makeError(obj) {
const err = new Error(obj.message); const err = new Error(obj.message);
err.name = obj.name; err.name = obj.name;
err.stack = obj.stack; err.stack = obj.stack;
@@ -327,7 +323,7 @@ class Util extends null {
* @returns {MakeErrorOptions} * @returns {MakeErrorOptions}
* @private * @private
*/ */
static makePlainError(err) { function makePlainError(err) {
return { return {
name: err.name, name: err.name,
message: err.message, message: err.message,
@@ -344,7 +340,7 @@ class Util extends null {
* @returns {number} * @returns {number}
* @private * @private
*/ */
static moveElementInArray(array, element, newIndex, offset = false) { function moveElementInArray(array, element, newIndex, offset = false) {
const index = array.indexOf(element); const index = array.indexOf(element);
newIndex = (offset ? index : 0) + newIndex; newIndex = (offset ? index : 0) + newIndex;
if (newIndex > -1 && newIndex < array.length) { if (newIndex > -1 && newIndex < array.length) {
@@ -362,7 +358,7 @@ class Util extends null {
* @param {boolean} [allowEmpty=true] Whether an empty string should be allowed * @param {boolean} [allowEmpty=true] Whether an empty string should be allowed
* @returns {string} * @returns {string}
*/ */
static verifyString( function verifyString(
data, data,
error = Error, error = Error,
errorMessage = `Expected a string, got ${data} instead.`, errorMessage = `Expected a string, got ${data} instead.`,
@@ -418,7 +414,7 @@ class Util extends null {
* @param {ColorResolvable} color Color to resolve * @param {ColorResolvable} color Color to resolve
* @returns {number} A color * @returns {number} A color
*/ */
static resolveColor(color) { function resolveColor(color) {
if (typeof color === 'string') { if (typeof color === 'string') {
if (color === 'Random') return Math.floor(Math.random() * (0xffffff + 1)); if (color === 'Random') return Math.floor(Math.random() * (0xffffff + 1));
if (color === 'Default') return 0; if (color === 'Default') return 0;
@@ -438,7 +434,7 @@ class Util extends null {
* @param {Collection} collection Collection of objects to sort * @param {Collection} collection Collection of objects to sort
* @returns {Collection} * @returns {Collection}
*/ */
static discordSort(collection) { function discordSort(collection) {
const isGuildChannel = collection.first() instanceof GuildChannel; const isGuildChannel = collection.first() instanceof GuildChannel;
return collection.sorted( return collection.sorted(
isGuildChannel isGuildChannel
@@ -459,9 +455,9 @@ class Util extends null {
* @returns {Promise<Channel[]|Role[]>} Updated item list, with `id` and `position` properties * @returns {Promise<Channel[]|Role[]>} Updated item list, with `id` and `position` properties
* @private * @private
*/ */
static async setPosition(item, position, relative, sorted, client, route, reason) { async function setPosition(item, position, relative, sorted, client, route, reason) {
let updatedItems = [...sorted.values()]; let updatedItems = [...sorted.values()];
Util.moveElementInArray(updatedItems, item, position, relative); moveElementInArray(updatedItems, item, position, relative);
updatedItems = updatedItems.map((r, i) => ({ id: r.id, position: i })); updatedItems = updatedItems.map((r, i) => ({ id: r.id, position: i }));
await client.rest.patch(route, { body: updatedItems, reason }); await client.rest.patch(route, { body: updatedItems, reason });
return updatedItems; return updatedItems;
@@ -474,7 +470,7 @@ class Util extends null {
* @returns {string} Basename of the path * @returns {string} Basename of the path
* @private * @private
*/ */
static basename(path, ext) { function basename(path, ext) {
const res = parse(path); const res = parse(path);
return ext && res.ext.startsWith(ext) ? res.name : res.base.split('?')[0]; return ext && res.ext.startsWith(ext) ? res.name : res.base.split('?')[0];
} }
@@ -484,7 +480,7 @@ class Util extends null {
* @param {TextBasedChannels} channel The channel the string was sent in * @param {TextBasedChannels} channel The channel the string was sent in
* @returns {string} * @returns {string}
*/ */
static cleanContent(str, channel) { function cleanContent(str, channel) {
str = str str = str
.replace(/<@!?[0-9]+>/g, input => { .replace(/<@!?[0-9]+>/g, input => {
const id = input.replace(/<|!|>|@/g, ''); const id = input.replace(/<|!|>|@/g, '');
@@ -518,7 +514,7 @@ class Util extends null {
* @param {string} text The string to be converted * @param {string} text The string to be converted
* @returns {string} * @returns {string}
*/ */
static cleanCodeBlockContent(text) { function cleanCodeBlockContent(text) {
return text.replaceAll('```', '`\u200b``'); return text.replaceAll('```', '`\u200b``');
} }
@@ -527,13 +523,38 @@ class Util extends null {
* @param {Function} cb The callback to lazily evaluate * @param {Function} cb The callback to lazily evaluate
* @returns {Function} * @returns {Function}
*/ */
static lazy(cb) { function lazy(cb) {
let defaultValue; let defaultValue;
return () => (defaultValue ??= cb()); return () => (defaultValue ??= cb());
} }
}
module.exports = Util; module.exports = {
flatten,
escapeMarkdown,
escapeCodeBlock,
escapeInlineCode,
escapeItalic,
escapeBold,
escapeUnderline,
escapeStrikethrough,
escapeSpoiler,
fetchRecommendedShards,
parseEmoji,
resolvePartialEmoji,
cloneObject,
mergeDefault,
makeError,
makePlainError,
moveElementInArray,
verifyString,
resolveColor,
discordSort,
setPosition,
basename,
cleanContent,
cleanCodeBlockContent,
lazy,
};
// Fixes Circular // Fixes Circular
const GuildChannel = require('../structures/GuildChannel'); const GuildChannel = require('../structures/GuildChannel');

View File

@@ -119,6 +119,7 @@ import {
LocaleString, LocaleString,
MessageActivityType, MessageActivityType,
APIAttachment, APIAttachment,
APIChannel,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { ChildProcess } from 'node:child_process'; import { ChildProcess } from 'node:child_process';
import { EventEmitter } from 'node:events'; import { EventEmitter } from 'node:events';
@@ -2613,34 +2614,32 @@ export class UserFlagsBitField extends BitField<UserFlagsString> {
public static resolve(bit?: BitFieldResolvable<UserFlagsString, number>): number; public static resolve(bit?: BitFieldResolvable<UserFlagsString, number>): number;
} }
export class Util extends null { export function basename(path: string, ext?: string): string;
private constructor(); export function cleanContent(str: string, channel: TextBasedChannel): string;
public static basename(path: string, ext?: string): string; export function cloneObject(obj: unknown): unknown;
public static cleanContent(str: string, channel: TextBasedChannel): string; export function discordSort<K, V extends { rawPosition: number; id: Snowflake }>(
public static cloneObject(obj: unknown): unknown;
public static discordSort<K, V extends { rawPosition: number; id: Snowflake }>(
collection: Collection<K, V>, collection: Collection<K, V>,
): Collection<K, V>; ): Collection<K, V>;
public static escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string; export function escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string;
public static escapeCodeBlock(text: string): string; export function escapeCodeBlock(text: string): string;
public static escapeInlineCode(text: string): string; export function escapeInlineCode(text: string): string;
public static escapeBold(text: string): string; export function escapeBold(text: string): string;
public static escapeItalic(text: string): string; export function escapeItalic(text: string): string;
public static escapeUnderline(text: string): string; export function escapeUnderline(text: string): string;
public static escapeStrikethrough(text: string): string; export function escapeStrikethrough(text: string): string;
public static escapeSpoiler(text: string): string; export function escapeSpoiler(text: string): string;
public static cleanCodeBlockContent(text: string): string; export function cleanCodeBlockContent(text: string): string;
public static fetchRecommendedShards(token: string, options?: FetchRecommendedShardsOptions): Promise<number>; export function fetchRecommendedShards(token: string, options?: FetchRecommendedShardsOptions): Promise<number>;
public static flatten(obj: unknown, ...props: Record<string, boolean | string>[]): unknown; export function flatten(obj: unknown, ...props: Record<string, boolean | string>[]): unknown;
public static makeError(obj: MakeErrorOptions): Error; export function makeError(obj: MakeErrorOptions): Error;
public static makePlainError(err: Error): MakeErrorOptions; export function makePlainError(err: Error): MakeErrorOptions;
public static mergeDefault(def: unknown, given: unknown): unknown; export function mergeDefault(def: unknown, given: unknown): unknown;
public static moveElementInArray(array: unknown[], element: unknown, newIndex: number, offset?: boolean): number; export function moveElementInArray(array: unknown[], element: unknown, newIndex: number, offset?: boolean): number;
public static parseEmoji(text: string): { animated: boolean; name: string; id: Snowflake | null } | null; export function parseEmoji(text: string): { animated: boolean; name: string; id: Snowflake | null } | null;
public static resolveColor(color: ColorResolvable): number; export function resolveColor(color: ColorResolvable): number;
public static resolvePartialEmoji(emoji: EmojiIdentifierResolvable): Partial<APIPartialEmoji> | null; export function resolvePartialEmoji(emoji: EmojiIdentifierResolvable): Partial<APIPartialEmoji> | null;
public static verifyString(data: string, error?: typeof Error, errorMessage?: string, allowEmpty?: boolean): string; export function verifyString(data: string, error?: typeof Error, errorMessage?: string, allowEmpty?: boolean): string;
public static setPosition<T extends AnyChannel | Role>( export function setPosition<T extends AnyChannel | Role>(
item: T, item: T,
position: number, position: number,
relative: boolean, relative: boolean,
@@ -2649,7 +2648,6 @@ export class Util extends null {
route: string, route: string,
reason?: string, reason?: string,
): Promise<{ id: Snowflake; position: number }[]>; ): Promise<{ id: Snowflake; position: number }[]>;
}
export interface MappedComponentBuilderTypes { export interface MappedComponentBuilderTypes {
[ComponentType.Button]: ButtonBuilder; [ComponentType.Button]: ButtonBuilder;
@@ -2665,18 +2663,23 @@ export interface MappedComponentTypes {
[ComponentType.TextInput]: TextInputComponent; [ComponentType.TextInput]: TextInputComponent;
} }
export class Components extends null { export interface CreateChannelOptions {
public static createComponent<T extends keyof MappedComponentTypes>( allowFromUnknownGuild?: boolean;
fromInteraction?: boolean;
}
export function createChannel(client: Client, data: APIChannel, options?: CreateChannelOptions): AnyChannel;
export function createComponent<T extends keyof MappedComponentTypes>(
data: APIMessageComponent & { type: T }, data: APIMessageComponent & { type: T },
): MappedComponentTypes[T]; ): MappedComponentTypes[T];
public static createComponent<C extends Component>(data: C): C; export function createComponent<C extends Component>(data: C): C;
public static createComponent(data: APIMessageComponent | Component): Component; export function createComponent(data: APIMessageComponent | Component): Component;
public static createComponentBuilder<T extends keyof MappedComponentBuilderTypes>( export function createComponentBuilder<T extends keyof MappedComponentBuilderTypes>(
data: APIMessageComponent & { type: T }, data: APIMessageComponent & { type: T },
): MappedComponentBuilderTypes[T]; ): MappedComponentBuilderTypes[T];
public static createComponentBuilder<C extends ComponentBuilder>(data: C): C; export function createComponentBuilder<C extends ComponentBuilder>(data: C): C;
public static createComponentBuilder(data: APIMessageComponent | ComponentBuilder): ComponentBuilder; export function createComponentBuilder(data: APIMessageComponent | ComponentBuilder): ComponentBuilder;
}
export class Formatters extends null { export class Formatters extends null {
public static blockQuote: typeof blockQuote; public static blockQuote: typeof blockQuote;