From 2b8ac35e56f1684f696bda9bcd5f772eefb39fdc Mon Sep 17 00:00:00 2001 From: Jiralite <33201955+Jiralite@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:15:08 +0000 Subject: [PATCH] refactor: Document relevant types as `@internal` (#9974) * refactor: mark data resolver as internal * docs: mark relevant TypeScript symbols as `@internal` * docs: extra additions * style: prefer at end * docs: add more `@internal`s * test: update template code --- packages/discord.js/src/client/Client.js | 6 +- packages/discord.js/src/index.js | 2 +- .../src/managers/GuildChannelManager.js | 4 +- .../src/managers/GuildEmojiManager.js | 4 +- .../src/managers/GuildInviteManager.js | 8 +- .../discord.js/src/managers/GuildManager.js | 4 +- .../managers/GuildScheduledEventManager.js | 6 +- .../discord.js/src/managers/RoleManager.js | 6 +- .../src/structures/ClientApplication.js | 6 +- .../discord.js/src/structures/ClientUser.js | 4 +- packages/discord.js/src/structures/Guild.js | 10 +- .../src/structures/GuildTemplate.js | 4 +- .../src/structures/MessagePayload.js | 4 +- packages/discord.js/src/structures/Webhook.js | 4 +- packages/discord.js/src/util/Channels.js | 9 +- packages/discord.js/src/util/DataResolver.js | 253 +++++++++--------- packages/discord.js/src/util/Util.js | 1 + .../test/resolveGuildTemplateCode.test.js | 4 +- packages/discord.js/typings/index.d.ts | 53 +++- 19 files changed, 215 insertions(+), 177 deletions(-) diff --git a/packages/discord.js/src/client/Client.js b/packages/discord.js/src/client/Client.js index 1a5c19b55..f45b0388b 100644 --- a/packages/discord.js/src/client/Client.js +++ b/packages/discord.js/src/client/Client.js @@ -23,7 +23,7 @@ const StickerPack = require('../structures/StickerPack'); const VoiceRegion = require('../structures/VoiceRegion'); const Webhook = require('../structures/Webhook'); const Widget = require('../structures/Widget'); -const DataResolver = require('../util/DataResolver'); +const { resolveInviteCode, resolveGuildTemplateCode } = require('../util/DataResolver'); const Events = require('../util/Events'); const IntentsBitField = require('../util/IntentsBitField'); const Options = require('../util/Options'); @@ -273,7 +273,7 @@ class Client extends BaseClient { * .catch(console.error); */ async fetchInvite(invite, options) { - const code = DataResolver.resolveInviteCode(invite); + const code = resolveInviteCode(invite); const query = makeURLSearchParams({ with_counts: true, with_expiration: true, @@ -293,7 +293,7 @@ class Client extends BaseClient { * .catch(console.error); */ async fetchGuildTemplate(template) { - const code = DataResolver.resolveGuildTemplateCode(template); + const code = resolveGuildTemplateCode(template); const data = await this.rest.get(Routes.template(code)); return new GuildTemplate(this, data); } diff --git a/packages/discord.js/src/index.js b/packages/discord.js/src/index.js index 024dec06b..b74f71fa4 100644 --- a/packages/discord.js/src/index.js +++ b/packages/discord.js/src/index.js @@ -26,7 +26,7 @@ exports.ChannelFlagsBitField = require('./util/ChannelFlagsBitField'); exports.Collection = require('@discordjs/collection').Collection; exports.Constants = require('./util/Constants'); exports.Colors = require('./util/Colors'); -exports.DataResolver = require('./util/DataResolver'); +__exportStar(require('./util/DataResolver.js'), exports); exports.Events = require('./util/Events'); exports.Formatters = require('./util/Formatters'); exports.GuildMemberFlagsBitField = require('./util/GuildMemberFlagsBitField').GuildMemberFlagsBitField; diff --git a/packages/discord.js/src/managers/GuildChannelManager.js b/packages/discord.js/src/managers/GuildChannelManager.js index 535d1034c..d4c472ebb 100644 --- a/packages/discord.js/src/managers/GuildChannelManager.js +++ b/packages/discord.js/src/managers/GuildChannelManager.js @@ -13,7 +13,7 @@ const Webhook = require('../structures/Webhook'); const ChannelFlagsBitField = require('../util/ChannelFlagsBitField'); const { transformGuildForumTag, transformGuildDefaultReaction } = require('../util/Channels'); const { ThreadChannelTypes } = require('../util/Constants'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); const { setPosition } = require('../util/Util'); let cacheWarningEmitted = false; @@ -219,7 +219,7 @@ class GuildChannelManager extends CachedManager { const id = this.resolveId(channel); if (!id) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'channel', 'GuildChannelResolvable'); if (typeof avatar === 'string' && !avatar.startsWith('data:')) { - avatar = await DataResolver.resolveImage(avatar); + avatar = await resolveImage(avatar); } const data = await this.client.rest.post(Routes.channelWebhooks(id), { body: { diff --git a/packages/discord.js/src/managers/GuildEmojiManager.js b/packages/discord.js/src/managers/GuildEmojiManager.js index 231c5da47..e5911840e 100644 --- a/packages/discord.js/src/managers/GuildEmojiManager.js +++ b/packages/discord.js/src/managers/GuildEmojiManager.js @@ -4,7 +4,7 @@ const { Collection } = require('@discordjs/collection'); const { Routes, PermissionFlagsBits } = require('discord-api-types/v10'); const BaseGuildEmojiManager = require('./BaseGuildEmojiManager'); const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); /** * Manages API methods for GuildEmojis and stores their cache. @@ -50,7 +50,7 @@ class GuildEmojiManager extends BaseGuildEmojiManager { * .catch(console.error); */ async create({ attachment, name, roles, reason }) { - attachment = await DataResolver.resolveImage(attachment); + attachment = await resolveImage(attachment); if (!attachment) throw new DiscordjsTypeError(ErrorCodes.ReqResourceType); const body = { image: attachment, name }; diff --git a/packages/discord.js/src/managers/GuildInviteManager.js b/packages/discord.js/src/managers/GuildInviteManager.js index b7fe71bed..93b0c973a 100644 --- a/packages/discord.js/src/managers/GuildInviteManager.js +++ b/packages/discord.js/src/managers/GuildInviteManager.js @@ -5,7 +5,7 @@ const { Routes } = require('discord-api-types/v10'); const CachedManager = require('./CachedManager'); const { DiscordjsError, ErrorCodes } = require('../errors'); const Invite = require('../structures/Invite'); -const DataResolver = require('../util/DataResolver'); +const { resolveInviteCode } = require('../util/DataResolver'); /** * Manages API methods for GuildInvites and stores their cache. @@ -124,7 +124,7 @@ class GuildInviteManager extends CachedManager { fetch(options) { if (!options) return this._fetchMany(); if (typeof options === 'string') { - const code = DataResolver.resolveInviteCode(options); + const code = resolveInviteCode(options); if (!code) return Promise.reject(new DiscordjsError(ErrorCodes.InviteResolveCode)); return this._fetchSingle({ code, cache: true }); } @@ -140,7 +140,7 @@ class GuildInviteManager extends CachedManager { } return this._fetchSingle({ ...options, - code: DataResolver.resolveInviteCode(options.code), + code: resolveInviteCode(options.code), }); } @@ -206,7 +206,7 @@ class GuildInviteManager extends CachedManager { * @returns {Promise} */ async delete(invite, reason) { - const code = DataResolver.resolveInviteCode(invite); + const code = resolveInviteCode(invite); await this.client.rest.delete(Routes.invite(code), { reason }); } diff --git a/packages/discord.js/src/managers/GuildManager.js b/packages/discord.js/src/managers/GuildManager.js index 447391cc2..48853cd89 100644 --- a/packages/discord.js/src/managers/GuildManager.js +++ b/packages/discord.js/src/managers/GuildManager.js @@ -14,7 +14,7 @@ const { GuildMember } = require('../structures/GuildMember'); const Invite = require('../structures/Invite'); const OAuth2Guild = require('../structures/OAuth2Guild'); const { Role } = require('../structures/Role'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); const Events = require('../util/Events'); const PermissionsBitField = require('../util/PermissionsBitField'); const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField'); @@ -179,7 +179,7 @@ class GuildManager extends CachedManager { const data = await this.client.rest.post(Routes.guilds(), { body: { name, - icon: icon && (await DataResolver.resolveImage(icon)), + icon: icon && (await resolveImage(icon)), verification_level: verificationLevel, default_message_notifications: defaultMessageNotifications, explicit_content_filter: explicitContentFilter, diff --git a/packages/discord.js/src/managers/GuildScheduledEventManager.js b/packages/discord.js/src/managers/GuildScheduledEventManager.js index 9071b60a7..a0d0c8375 100644 --- a/packages/discord.js/src/managers/GuildScheduledEventManager.js +++ b/packages/discord.js/src/managers/GuildScheduledEventManager.js @@ -6,7 +6,7 @@ const { GuildScheduledEventEntityType, Routes } = require('discord-api-types/v10 const CachedManager = require('./CachedManager'); const { DiscordjsTypeError, DiscordjsError, ErrorCodes } = require('../errors'); const { GuildScheduledEvent } = require('../structures/GuildScheduledEvent'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); /** * Manages API methods for GuildScheduledEvents and stores their cache. @@ -103,7 +103,7 @@ class GuildScheduledEventManager extends CachedManager { description, entity_type: entityType, entity_metadata, - image: image && (await DataResolver.resolveImage(image)), + image: image && (await resolveImage(image)), }, reason, }); @@ -222,7 +222,7 @@ class GuildScheduledEventManager extends CachedManager { description, entity_type: entityType, status, - image: image && (await DataResolver.resolveImage(image)), + image: image && (await resolveImage(image)), entity_metadata, }, reason, diff --git a/packages/discord.js/src/managers/RoleManager.js b/packages/discord.js/src/managers/RoleManager.js index 63b76faa2..5c06613b0 100644 --- a/packages/discord.js/src/managers/RoleManager.js +++ b/packages/discord.js/src/managers/RoleManager.js @@ -6,7 +6,7 @@ const { Routes } = require('discord-api-types/v10'); const CachedManager = require('./CachedManager'); const { DiscordjsTypeError, ErrorCodes } = require('../errors'); const { Role } = require('../structures/Role'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); const PermissionsBitField = require('../util/PermissionsBitField'); const { setPosition, resolveColor } = require('../util/Util'); @@ -140,7 +140,7 @@ class RoleManager extends CachedManager { if (permissions !== undefined) permissions = new PermissionsBitField(permissions); if (icon) { const guildEmojiURL = this.guild.emojis.resolve(icon)?.imageURL(); - icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon); + icon = guildEmojiURL ? await resolveImage(guildEmojiURL) : await resolveImage(icon); if (typeof icon !== 'string') icon = undefined; } @@ -192,7 +192,7 @@ class RoleManager extends CachedManager { let icon = options.icon; if (icon) { const guildEmojiURL = this.guild.emojis.resolve(icon)?.imageURL(); - icon = guildEmojiURL ? await DataResolver.resolveImage(guildEmojiURL) : await DataResolver.resolveImage(icon); + icon = guildEmojiURL ? await resolveImage(guildEmojiURL) : await resolveImage(icon); if (typeof icon !== 'string') icon = undefined; } diff --git a/packages/discord.js/src/structures/ClientApplication.js b/packages/discord.js/src/structures/ClientApplication.js index 4821515dd..0c9588bad 100644 --- a/packages/discord.js/src/structures/ClientApplication.js +++ b/packages/discord.js/src/structures/ClientApplication.js @@ -6,7 +6,7 @@ const Team = require('./Team'); const Application = require('./interfaces/Application'); const ApplicationCommandManager = require('../managers/ApplicationCommandManager'); const ApplicationFlagsBitField = require('../util/ApplicationFlagsBitField'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); const PermissionsBitField = require('../util/PermissionsBitField'); /** @@ -227,8 +227,8 @@ class ClientApplication extends Application { role_connections_verification_url: roleConnectionsVerificationURL, install_params: installParams, flags: flags === undefined ? undefined : ApplicationFlagsBitField.resolve(flags), - icon: icon && (await DataResolver.resolveImage(icon)), - cover_image: coverImage && (await DataResolver.resolveImage(coverImage)), + icon: icon && (await resolveImage(icon)), + cover_image: coverImage && (await resolveImage(coverImage)), interactions_endpoint_url: interactionsEndpointURL, tags, }, diff --git a/packages/discord.js/src/structures/ClientUser.js b/packages/discord.js/src/structures/ClientUser.js index b93904cf4..ce48ef78c 100644 --- a/packages/discord.js/src/structures/ClientUser.js +++ b/packages/discord.js/src/structures/ClientUser.js @@ -2,7 +2,7 @@ const { Routes } = require('discord-api-types/v10'); const User = require('./User'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); /** * Represents the logged in client's Discord user. @@ -56,7 +56,7 @@ class ClientUser extends User { */ async edit({ username, avatar }) { const data = await this.client.rest.patch(Routes.user(), { - body: { username, avatar: avatar && (await DataResolver.resolveImage(avatar)) }, + body: { username, avatar: avatar && (await resolveImage(avatar)) }, }); this.client.token = data.token; diff --git a/packages/discord.js/src/structures/Guild.js b/packages/discord.js/src/structures/Guild.js index 51a26750a..9f7320762 100644 --- a/packages/discord.js/src/structures/Guild.js +++ b/packages/discord.js/src/structures/Guild.js @@ -26,7 +26,7 @@ const PresenceManager = require('../managers/PresenceManager'); const RoleManager = require('../managers/RoleManager'); const StageInstanceManager = require('../managers/StageInstanceManager'); const VoiceStateManager = require('../managers/VoiceStateManager'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); const Status = require('../util/Status'); const SystemChannelFlagsBitField = require('../util/SystemChannelFlagsBitField'); const { discordSort, getSortableGroupTypes, resolvePartialEmoji } = require('../util/Util'); @@ -862,11 +862,11 @@ class Guild extends AnonymousGuild { explicit_content_filter: explicitContentFilter, afk_channel_id: afkChannel && this.client.channels.resolveId(afkChannel), afk_timeout: afkTimeout, - icon: icon && (await DataResolver.resolveImage(icon)), + icon: icon && (await resolveImage(icon)), owner_id: owner && this.client.users.resolveId(owner), - splash: splash && (await DataResolver.resolveImage(splash)), - discovery_splash: discoverySplash && (await DataResolver.resolveImage(discoverySplash)), - banner: banner && (await DataResolver.resolveImage(banner)), + splash: splash && (await resolveImage(splash)), + discovery_splash: discoverySplash && (await resolveImage(discoverySplash)), + banner: banner && (await resolveImage(banner)), system_channel_id: systemChannel && this.client.channels.resolveId(systemChannel), system_channel_flags: systemChannelFlags === undefined ? undefined : SystemChannelFlagsBitField.resolve(systemChannelFlags), diff --git a/packages/discord.js/src/structures/GuildTemplate.js b/packages/discord.js/src/structures/GuildTemplate.js index c1e219b5e..7806867b5 100644 --- a/packages/discord.js/src/structures/GuildTemplate.js +++ b/packages/discord.js/src/structures/GuildTemplate.js @@ -3,7 +3,7 @@ const { setTimeout, clearTimeout } = require('node:timers'); const { RouteBases, Routes } = require('discord-api-types/v10'); const Base = require('./Base'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); const Events = require('../util/Events'); /** @@ -126,7 +126,7 @@ class GuildTemplate extends Base { const data = await client.rest.post(Routes.template(this.code), { body: { name, - icon: await DataResolver.resolveImage(icon), + icon: await resolveImage(icon), }, }); diff --git a/packages/discord.js/src/structures/MessagePayload.js b/packages/discord.js/src/structures/MessagePayload.js index 43f6615fe..38e8957b8 100644 --- a/packages/discord.js/src/structures/MessagePayload.js +++ b/packages/discord.js/src/structures/MessagePayload.js @@ -5,7 +5,7 @@ const { lazy, isJSONEncodable } = require('@discordjs/util'); const { MessageFlags } = require('discord-api-types/v10'); const ActionRowBuilder = require('./ActionRowBuilder'); const { DiscordjsRangeError, ErrorCodes } = require('../errors'); -const DataResolver = require('../util/DataResolver'); +const { resolveFile } = require('../util/DataResolver'); const MessageFlagsBitField = require('../util/MessageFlagsBitField'); const { basename, verifyString } = require('../util/Util'); @@ -257,7 +257,7 @@ class MessagePayload { name = fileLike.name ?? findName(attachment); } - const { data, contentType } = await DataResolver.resolveFile(attachment); + const { data, contentType } = await resolveFile(attachment); return { data, name, contentType }; } diff --git a/packages/discord.js/src/structures/Webhook.js b/packages/discord.js/src/structures/Webhook.js index 1257c3162..266cee864 100644 --- a/packages/discord.js/src/structures/Webhook.js +++ b/packages/discord.js/src/structures/Webhook.js @@ -6,7 +6,7 @@ const { DiscordSnowflake } = require('@sapphire/snowflake'); const { Routes, WebhookType } = require('discord-api-types/v10'); const MessagePayload = require('./MessagePayload'); const { DiscordjsError, ErrorCodes } = require('../errors'); -const DataResolver = require('../util/DataResolver'); +const { resolveImage } = require('../util/DataResolver'); const getMessage = lazy(() => require('./Message').Message); @@ -276,7 +276,7 @@ class Webhook { */ async edit({ name = this.name, avatar, channel, reason }) { if (avatar && !(typeof avatar === 'string' && avatar.startsWith('data:'))) { - avatar = await DataResolver.resolveImage(avatar); + avatar = await resolveImage(avatar); } channel &&= channel.id ?? channel; const data = await this.client.rest.patch(Routes.webhook(this.id, channel ? undefined : this.token), { diff --git a/packages/discord.js/src/util/Channels.js b/packages/discord.js/src/util/Channels.js index daf336e17..aaf2152a1 100644 --- a/packages/discord.js/src/util/Channels.js +++ b/packages/discord.js/src/util/Channels.js @@ -15,12 +15,19 @@ const getPartialGroupDMChannel = lazy(() => require('../structures/PartialGroupD const getForumChannel = lazy(() => require('../structures/ForumChannel')); const getMediaChannel = lazy(() => require('../structures/MediaChannel')); +/** + * Extra options for creating a channel. + * @typedef {Object} CreateChannelOptions + * @property {boolean} [allowFromUnknownGuild] Whether to allow creating a channel from an unknown guild + * @private + */ + /** * Creates a discord.js channel from data received from the API. * @param {Client} client The client * @param {APIChannel} data The data of the channel to create * @param {Guild} [guild] The guild where this channel belongs - * @param {Object} [extras] Extra information to supply for creating this channel + * @param {CreateChannelOptions} [extras] Extra information to supply for creating this channel * @returns {BaseChannel} Any kind of channel. * @ignore */ diff --git a/packages/discord.js/src/util/DataResolver.js b/packages/discord.js/src/util/DataResolver.js index 6b8a64cb7..681a0bf91 100644 --- a/packages/discord.js/src/util/DataResolver.js +++ b/packages/discord.js/src/util/DataResolver.js @@ -8,133 +8,134 @@ const { DiscordjsError, DiscordjsTypeError, ErrorCodes } = require('../errors'); const Invite = require('../structures/Invite'); /** - * The DataResolver identifies different objects and tries to resolve a specific piece of information from them. + * Data that can be resolved to give an invite code. This can be: + * * An invite code + * * An invite URL + * @typedef {string} InviteResolvable + */ + +/** + * Data that can be resolved to give a template code. This can be: + * * A template code + * * A template URL + * @typedef {string} GuildTemplateResolvable + */ + +/** + * Resolves the string to a code based on the passed regex. + * @param {string} data The string to resolve + * @param {RegExp} regex The RegExp used to extract the code + * @returns {string} * @private */ -class DataResolver extends null { - /** - * Data that can be resolved to give an invite code. This can be: - * * An invite code - * * An invite URL - * @typedef {string} InviteResolvable - */ - - /** - * Data that can be resolved to give a template code. This can be: - * * A template code - * * A template URL - * @typedef {string} GuildTemplateResolvable - */ - - /** - * Resolves the string to a code based on the passed regex. - * @param {string} data The string to resolve - * @param {RegExp} regex The RegExp used to extract the code - * @returns {string} - */ - static resolveCode(data, regex) { - return regex.exec(data)?.[1] ?? data; - } - - /** - * Resolves InviteResolvable to an invite code. - * @param {InviteResolvable} data The invite resolvable to resolve - * @returns {string} - */ - static resolveInviteCode(data) { - return this.resolveCode(data, Invite.InvitesPattern); - } - - /** - * Resolves GuildTemplateResolvable to a template code. - * @param {GuildTemplateResolvable} data The template resolvable to resolve - * @returns {string} - */ - static resolveGuildTemplateCode(data) { - const GuildTemplate = require('../structures/GuildTemplate'); - return this.resolveCode(data, GuildTemplate.GuildTemplatesPattern); - } - - /** - * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. - * @param {BufferResolvable|Base64Resolvable} image The image to be resolved - * @returns {Promise} - */ - static async resolveImage(image) { - if (!image) return null; - if (typeof image === 'string' && image.startsWith('data:')) { - return image; - } - const file = await this.resolveFile(image); - return this.resolveBase64(file.data); - } - - /** - * Data that resolves to give a Base64 string, typically for image uploading. This can be: - * * A Buffer - * * A base64 string - * @typedef {Buffer|string} Base64Resolvable - */ - - /** - * Resolves a Base64Resolvable to a Base 64 image. - * @param {Base64Resolvable} data The base 64 resolvable you want to resolve - * @returns {?string} - */ - static resolveBase64(data) { - if (Buffer.isBuffer(data)) return `data:image/jpg;base64,${data.toString('base64')}`; - return data; - } - - /** - * Data that can be resolved to give a Buffer. This can be: - * * A Buffer - * * The path to a local file - * * A URL When provided a URL, discord.js will fetch the URL internally in order to create a Buffer. - * This can pose a security risk when the URL has not been sanitized - * @typedef {string|Buffer} BufferResolvable - */ - - /** - * @external Stream - * @see {@link https://nodejs.org/api/stream.html} - */ - - /** - * @typedef {Object} ResolvedFile - * @property {Buffer} data Buffer containing the file data - * @property {string} [contentType] Content type of the file - */ - - /** - * Resolves a BufferResolvable to a Buffer. - * @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve - * @returns {Promise} - */ - static async resolveFile(resource) { - if (Buffer.isBuffer(resource)) return { data: resource }; - - if (typeof resource[Symbol.asyncIterator] === 'function') { - const buffers = []; - for await (const data of resource) buffers.push(Buffer.from(data)); - return { data: Buffer.concat(buffers) }; - } - - if (typeof resource === 'string') { - if (/^https?:\/\//.test(resource)) { - const res = await fetch(resource); - return { data: Buffer.from(await res.arrayBuffer()), contentType: res.headers.get('content-type') }; - } - - const file = path.resolve(resource); - - const stats = await fs.stat(file); - if (!stats.isFile()) throw new DiscordjsError(ErrorCodes.FileNotFound, file); - return { data: await fs.readFile(file) }; - } - - throw new DiscordjsTypeError(ErrorCodes.ReqResourceType); - } +function resolveCode(data, regex) { + return regex.exec(data)?.[1] ?? data; } -module.exports = DataResolver; +/** + * Resolves InviteResolvable to an invite code. + * @param {InviteResolvable} data The invite resolvable to resolve + * @returns {string} + * @private + */ +function resolveInviteCode(data) { + return resolveCode(data, Invite.InvitesPattern); +} + +/** + * Resolves GuildTemplateResolvable to a template code. + * @param {GuildTemplateResolvable} data The template resolvable to resolve + * @returns {string} + * @private + */ +function resolveGuildTemplateCode(data) { + const GuildTemplate = require('../structures/GuildTemplate'); + return resolveCode(data, GuildTemplate.GuildTemplatesPattern); +} + +/** + * Data that can be resolved to give a Buffer. This can be: + * * A Buffer + * * The path to a local file + * * A URL When provided a URL, discord.js will fetch the URL internally in order to create a Buffer. + * This can pose a security risk when the URL has not been sanitized + * @typedef {string|Buffer} BufferResolvable + */ + +/** + * @external Stream + * @see {@link https://nodejs.org/api/stream.html} + */ + +/** + * @typedef {Object} ResolvedFile + * @property {Buffer} data Buffer containing the file data + * @property {string} [contentType] Content-Type of the file + * @private + */ + +/** + * Resolves a BufferResolvable to a Buffer. + * @param {BufferResolvable|Stream} resource The buffer or stream resolvable to resolve + * @returns {Promise} + * @private + */ +async function resolveFile(resource) { + if (Buffer.isBuffer(resource)) return { data: resource }; + + if (typeof resource[Symbol.asyncIterator] === 'function') { + const buffers = []; + for await (const data of resource) buffers.push(Buffer.from(data)); + return { data: Buffer.concat(buffers) }; + } + + if (typeof resource === 'string') { + if (/^https?:\/\//.test(resource)) { + const res = await fetch(resource); + return { data: Buffer.from(await res.arrayBuffer()), contentType: res.headers.get('content-type') }; + } + + const file = path.resolve(resource); + + const stats = await fs.stat(file); + if (!stats.isFile()) throw new DiscordjsError(ErrorCodes.FileNotFound, file); + return { data: await fs.readFile(file) }; + } + + throw new DiscordjsTypeError(ErrorCodes.ReqResourceType); +} + +/** + * Data that resolves to give a Base64 string, typically for image uploading. This can be: + * * A Buffer + * * A base64 string + * @typedef {Buffer|string} Base64Resolvable + */ + +/** + * Resolves a Base64Resolvable to a Base 64 image. + * @param {Base64Resolvable} data The base 64 resolvable you want to resolve + * @returns {?string} + * @private + */ +function resolveBase64(data) { + if (Buffer.isBuffer(data)) return `data:image/jpg;base64,${data.toString('base64')}`; + return data; +} + +/** + * Resolves a Base64Resolvable, a string, or a BufferResolvable to a Base 64 image. + * @param {BufferResolvable|Base64Resolvable} image The image to be resolved + * @returns {Promise} + * @private + */ +async function resolveImage(image) { + if (!image) return null; + if (typeof image === 'string' && image.startsWith('data:')) { + return image; + } + const file = await resolveFile(image); + return resolveBase64(file.data); +} + +module.exports = { resolveCode, resolveInviteCode, resolveGuildTemplateCode, resolveImage, resolveBase64, resolveFile }; diff --git a/packages/discord.js/src/util/Util.js b/packages/discord.js/src/util/Util.js index 0f8ff0a3c..277a5baca 100644 --- a/packages/discord.js/src/util/Util.js +++ b/packages/discord.js/src/util/Util.js @@ -128,6 +128,7 @@ function resolvePartialEmoji(emoji) { * @property {string} name Error type * @property {string} message Message for the error * @property {string} stack Stack for the error + * @private */ /** diff --git a/packages/discord.js/test/resolveGuildTemplateCode.test.js b/packages/discord.js/test/resolveGuildTemplateCode.test.js index f6b8e3c16..39eff58ce 100644 --- a/packages/discord.js/test/resolveGuildTemplateCode.test.js +++ b/packages/discord.js/test/resolveGuildTemplateCode.test.js @@ -2,10 +2,10 @@ /* eslint-env jest */ -const { DataResolver } = require('../src'); +const { resolveGuildTemplateCode } = require('../src'); describe('resolveGuildTemplateCode', () => { test('basic', () => { - expect(DataResolver.resolveGuildTemplateCode('https://discord.new/abc')).toEqual('abc'); + expect(resolveGuildTemplateCode('https://discord.new/abc')).toEqual('abc'); }); }); diff --git a/packages/discord.js/typings/index.d.ts b/packages/discord.js/typings/index.d.ts index 6897c7849..975578bd5 100644 --- a/packages/discord.js/typings/index.d.ts +++ b/packages/discord.js/typings/index.d.ts @@ -1278,21 +1278,12 @@ export class ContextMenuCommandInteraction private resolveContextMenuOptions(data: APIApplicationCommandInteractionData): CommandInteractionOption[]; } +/** @internal */ export interface ResolvedFile { data: Buffer; contentType?: string; } -export class DataResolver extends null { - private constructor(); - public static resolveBase64(data: Base64Resolvable): string; - public static resolveCode(data: string, regex: RegExp): string; - public static resolveFile(resource: BufferResolvable | Stream): Promise; - public static resolveImage(resource: BufferResolvable | Base64Resolvable): Promise; - public static resolveInviteCode(data: InviteResolvable): string; - public static resolveGuildTemplateCode(data: GuildTemplateResolvable): string; -} - export class DMChannel extends TextBasedChannelMixin(BaseChannel, false, [ 'bulkDelete', 'fetchWebhooks', @@ -3266,6 +3257,7 @@ export class UserFlagsBitField extends BitField { public static resolve(bit?: BitFieldResolvable): number; } +/** @internal */ export function basename(path: string, ext?: string): string; export function cleanContent(str: string, channel: TextBasedChannel): string; export function discordSort( @@ -3274,14 +3266,19 @@ export function discordSort; export function flatten(obj: unknown, ...props: Record[]): unknown; +/** @internal */ export function makeError(obj: MakeErrorOptions): Error; +/** @internal */ export function makePlainError(err: Error): MakeErrorOptions; +/** @internal */ export function moveElementInArray(array: unknown[], element: unknown, newIndex: number, offset?: boolean): number; export function parseEmoji(text: string): PartialEmoji | null; export function resolveColor(color: ColorResolvable): number; +/** @internal */ export function resolvePartialEmoji(emoji: Snowflake): PartialEmojiOnlyId; export function resolvePartialEmoji(emoji: Emoji | EmojiIdentifierResolvable): PartialEmoji | null; export function verifyString(data: string, error?: typeof Error, errorMessage?: string, allowEmpty?: boolean): string; +/** @internal */ export function setPosition( item: Item, position: number, @@ -3292,6 +3289,7 @@ export function setPosition( reason?: string, ): Promise<{ id: Snowflake; position: number }[]>; export function parseWebhookURL(url: string): WebhookClientDataIdWithToken | null; +/** @internal */ export function transformResolved( supportingData: SupportingInteractionResolvedData, data?: APIApplicationCommandInteractionData['resolved'], @@ -3319,11 +3317,18 @@ export interface MappedComponentTypes { [ComponentType.TextInput]: TextInputComponent; } -export interface ChannelCreateOptions { +/** @internal */ +export interface CreateChannelOptions { allowFromUnknownGuild?: boolean; } -export function createChannel(client: Client, data: APIChannel, options?: ChannelCreateOptions): Channel; +/** @internal */ +export function createChannel( + client: Client, + data: APIChannel, + guild?: Guild, + extras?: CreateChannelOptions, +): Channel; export function createComponent( data: APIMessageComponent & { type: Type }, @@ -3374,6 +3379,19 @@ export class Formatters extends null { public static userMention: typeof userMention; } +/** @internal */ +export function resolveBase64(data: Base64Resolvable): string; +/** @internal */ +export function resolveCode(data: string, regex: RegExp): string; +/** @internal */ +export function resolveFile(resource: BufferResolvable | Stream): Promise; +/** @internal */ +export function resolveImage(resource: BufferResolvable | Base64Resolvable): Promise; +/** @internal */ +export function resolveInviteCode(data: InviteResolvable): string; +/** @internal */ +export function resolveGuildTemplateCode(data: GuildTemplateResolvable): string; + export type ComponentData = | MessageActionRowComponentData | ModalActionRowComponentData @@ -3816,11 +3834,13 @@ export enum DiscordjsErrorCodes { GuildForumMessageRequired = 'GuildForumMessageRequired', } +/** @internal */ export interface DiscordjsErrorFields { readonly name: `${Name} [${DiscordjsErrorCodes}]`; get code(): DiscordjsErrorCodes; } +/** @internal */ export function DiscordjsErrorMixin( Base: Constructable, name: Name, @@ -4369,10 +4389,13 @@ export class VoiceStateManager extends CachedManager = abstract new (...args: any[]) => Entity; + +/** @internal */ export function PartialTextBasedChannel( Base?: Constructable, ): Constructable>; +/** @internal */ export function TextBasedChannelMixin< Entity, InGuild extends boolean = boolean, @@ -4413,9 +4436,12 @@ export interface TextBasedChannelFields setNSFW(nsfw?: boolean, reason?: string): Promise; } +/** @internal */ export function PartialWebhookMixin(Base?: Constructable): Constructable; +/** @internal */ export function WebhookMixin(Base?: Constructable): Constructable; +/** @internal */ export interface PartialWebhookFields { id: Snowflake; get url(): string; @@ -4430,6 +4456,7 @@ export interface PartialWebhookFields { ): Promise; } +/** @internal */ export interface WebhookFields extends PartialWebhookFields { get createdAt(): Date; get createdTimestamp(): number; @@ -5979,6 +6006,7 @@ export interface LifetimeFilterOptions { lifetime?: number; } +/** @internal */ export interface MakeErrorOptions { name: string; message: string; @@ -6433,6 +6461,7 @@ export interface StageInstanceEditOptions { privacyLevel?: StageInstancePrivacyLevel; } +/** @internal */ export interface SupportingInteractionResolvedData { client: Client; guild?: Guild;