diff --git a/DOCUMENT.md b/DOCUMENT.md index 15e7b95..66a3eee 100644 --- a/DOCUMENT.md +++ b/DOCUMENT.md @@ -285,6 +285,53 @@ await message.contextMenu(botID, commandName); - Credit: [Here](https://www.reddit.com/r/Discord_selfbots/comments/tczprx/discum_help_creating_a_selfbot_that_can_do_ping/) +## MessageEmbed ? +- Because Discord has removed the ability to send Embeds in its API, that means MessageEmbed is unusable. But I have created a constructor that uses oEmbed from [page](https://embed.benny.fun/) (known to this site by module py discord.py-self_embed, thanks) + +
+Click to show + + +Code: +```js +const Discord = require('discord.js-selfbot-v13'); +const w = new Discord.WebEmbed() + .setAuthor({ name: 'hello', url: 'https://google.com' }) + .setColor('RED') + .setDescription('description uh') + .setProvider({ name: 'provider', url: 'https://google.com' }) + .setTitle('This is Title') + .setImage( + 'https://cdn.discordapp.com/attachments/820557032016969751/959093026695835648/unknown.png', + ) + .setVideo( + 'https://cdn.discordapp.com/attachments/877060758092021801/957691816143097936/The_Quintessential_Quintuplets_And_Rick_Astley_Autotune_Remix.mp4', + ); +/** + * w.toMessage(hidden: true | false, shorten: true | false) => Promise +*/ +// Normal mode (Auto shorten) +message.channel.send({ content: `${await w.toMessage()}` }) +// Normal mode (Not shorten) +message.channel.send({ content: `${await w.toMessage(false, false)}` }) +// Hidden mode (with shorten) +message.channel.send({ content: `${await w.toMessage(true, true)}` }) +// Hidden mode (no shorten) +message.channel.send({ content: `${await w.toMessage(true, false)}` }) +// Custom content + Shorten + Hidden +message.channel.send({ content: `Hello world ${await w.toMessage(true, true)}` }) +// etc ... + +``` +### Features & Issues +- No Timestamp, Footer, Thumbnail (but embed video, thumbnail working), Fields, Author iconURL +- Video with Embed working +- Description limit 350 characters +- If you use hidden mode you must make sure your custom content is less than 1000 characters without nitro (because hidden mode uses 1000 characters + URL) + +
+ + ## User & ClientUser Method
Click to show diff --git a/src/errors/Messages.js b/src/errors/Messages.js index 28fa948..331e265 100644 --- a/src/errors/Messages.js +++ b/src/errors/Messages.js @@ -3,165 +3,203 @@ const { register } = require('./DJSError'); const Messages = { - CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`, - CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.', - CLIENT_MISSING_INTENTS: 'Valid intents must be provided for the Client.', - CLIENT_NOT_READY: action => `The client needs to be logged in to ${action}.`, + CLIENT_INVALID_OPTION: (prop, must) => `The ${prop} option must be ${must}`, + CLIENT_INVALID_PROVIDED_SHARDS: 'None of the provided shards were valid.', + CLIENT_MISSING_INTENTS: 'Valid intents must be provided for the Client.', + CLIENT_NOT_READY: (action) => + `The client needs to be logged in to ${action}.`, - TOKEN_INVALID: 'An invalid token was provided.', - TOKEN_MISSING: 'Request to use token, but token was unavailable to the client.', + TOKEN_INVALID: 'An invalid token was provided.', + TOKEN_MISSING: + 'Request to use token, but token was unavailable to the client.', - WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.', - WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.', - WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`, - MANAGER_DESTROYED: 'Manager was destroyed.', + WS_CLOSE_REQUESTED: 'WebSocket closed due to user request.', + WS_CONNECTION_EXISTS: 'There is already an existing WebSocket connection.', + WS_NOT_OPEN: (data = 'data') => `WebSocket not open to send ${data}`, + MANAGER_DESTROYED: 'Manager was destroyed.', - BITFIELD_INVALID: bit => `Invalid bitfield flag or number: ${bit}.`, + BITFIELD_INVALID: (bit) => `Invalid bitfield flag or number: ${bit}.`, - SHARDING_INVALID: 'Invalid shard settings were provided.', - SHARDING_REQUIRED: 'This session would have handled too many guilds - Sharding is required.', - INVALID_INTENTS: 'Invalid intent provided for WebSocket intents.', - DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.', - SHARDING_NO_SHARDS: 'No shards have been spawned.', - SHARDING_IN_PROCESS: 'Shards are still being spawned.', - SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function', - SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`, - SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`, - SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`, - SHARDING_WORKER_EXISTS: id => `Shard ${id} already has an active worker.`, - SHARDING_READY_TIMEOUT: id => `Shard ${id}'s Client took too long to become ready.`, - SHARDING_READY_DISCONNECTED: id => `Shard ${id}'s Client disconnected before becoming ready.`, - SHARDING_READY_DIED: id => `Shard ${id}'s process exited before its Client became ready.`, - SHARDING_NO_CHILD_EXISTS: id => `Shard ${id} has no active process or worker.`, - SHARDING_SHARD_MISCALCULATION: (shard, guild, count) => - `Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`, + SHARDING_INVALID: 'Invalid shard settings were provided.', + SHARDING_REQUIRED: + 'This session would have handled too many guilds - Sharding is required.', + INVALID_INTENTS: 'Invalid intent provided for WebSocket intents.', + DISALLOWED_INTENTS: + 'Privileged intent provided is not enabled or whitelisted.', + SHARDING_NO_SHARDS: 'No shards have been spawned.', + SHARDING_IN_PROCESS: 'Shards are still being spawned.', + SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function', + SHARDING_SHARD_NOT_FOUND: (id) => `Shard ${id} could not be found.`, + SHARDING_ALREADY_SPAWNED: (count) => `Already spawned ${count} shards.`, + SHARDING_PROCESS_EXISTS: (id) => `Shard ${id} already has an active process.`, + SHARDING_WORKER_EXISTS: (id) => `Shard ${id} already has an active worker.`, + SHARDING_READY_TIMEOUT: (id) => + `Shard ${id}'s Client took too long to become ready.`, + SHARDING_READY_DISCONNECTED: (id) => + `Shard ${id}'s Client disconnected before becoming ready.`, + SHARDING_READY_DIED: (id) => + `Shard ${id}'s process exited before its Client became ready.`, + SHARDING_NO_CHILD_EXISTS: (id) => + `Shard ${id} has no active process or worker.`, + SHARDING_SHARD_MISCALCULATION: (shard, guild, count) => + `Calculated invalid shard ${shard} for guild ${guild} with ${count} shards.`, - COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).', - COLOR_CONVERT: 'Unable to convert color to a number.', + COLOR_RANGE: 'Color must be within the range 0 - 16777215 (0xFFFFFF).', + COLOR_CONVERT: 'Unable to convert color to a number.', - INVITE_OPTIONS_MISSING_CHANNEL: 'A valid guild channel must be provided when GuildScheduledEvent is EXTERNAL.', + INVITE_OPTIONS_MISSING_CHANNEL: + 'A valid guild channel must be provided when GuildScheduledEvent is EXTERNAL.', - EMBED_TITLE: 'MessageEmbed title must be a string.', - EMBED_FIELD_NAME: 'MessageEmbed field names must be non-empty strings.', - EMBED_FIELD_VALUE: 'MessageEmbed field values must be non-empty strings.', - EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.', - EMBED_DESCRIPTION: 'MessageEmbed description must be a string.', - EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.', + EMBED_TITLE: 'MessageEmbed title must be a string.', + EMBED_FIELD_NAME: 'MessageEmbed field names must be non-empty strings.', + EMBED_FIELD_VALUE: 'MessageEmbed field values must be non-empty strings.', + EMBED_FOOTER_TEXT: 'MessageEmbed footer text must be a string.', + EMBED_DESCRIPTION: 'MessageEmbed description must be a string.', + EMBED_AUTHOR_NAME: 'MessageEmbed author name must be a string.', + /* add */ + EMBED_PROVIDER_NAME: 'MessageEmbed provider name must be a string.', - BUTTON_LABEL: 'MessageButton label must be a string', - BUTTON_URL: 'MessageButton URL must be a string', - BUTTON_CUSTOM_ID: 'MessageButton customId must be a string', + BUTTON_LABEL: 'MessageButton label must be a string', + BUTTON_URL: 'MessageButton URL must be a string', + BUTTON_CUSTOM_ID: 'MessageButton customId must be a string', - SELECT_MENU_CUSTOM_ID: 'MessageSelectMenu customId must be a string', - SELECT_MENU_PLACEHOLDER: 'MessageSelectMenu placeholder must be a string', - SELECT_OPTION_LABEL: 'MessageSelectOption label must be a string', - SELECT_OPTION_VALUE: 'MessageSelectOption value must be a string', - SELECT_OPTION_DESCRIPTION: 'MessageSelectOption description must be a string', + SELECT_MENU_CUSTOM_ID: 'MessageSelectMenu customId must be a string', + SELECT_MENU_PLACEHOLDER: 'MessageSelectMenu placeholder must be a string', + SELECT_OPTION_LABEL: 'MessageSelectOption label must be a string', + SELECT_OPTION_VALUE: 'MessageSelectOption value must be a string', + SELECT_OPTION_DESCRIPTION: 'MessageSelectOption description must be a string', - INTERACTION_COLLECTOR_ERROR: reason => `Collector received no interactions before ending with reason: ${reason}`, + INTERACTION_COLLECTOR_ERROR: (reason) => + `Collector received no interactions before ending with reason: ${reason}`, - FILE_NOT_FOUND: file => `File could not be found: ${file}`, + FILE_NOT_FOUND: (file) => `File could not be found: ${file}`, - USER_BANNER_NOT_FETCHED: "You must fetch this user's banner before trying to generate its URL!", - USER_NO_DM_CHANNEL: 'No DM Channel exists!', + USER_BANNER_NOT_FETCHED: + "You must fetch this user's banner before trying to generate its URL!", + USER_NO_DM_CHANNEL: 'No DM Channel exists!', - VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.', + VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.', - VOICE_STATE_NOT_OWN: - 'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.', - VOICE_STATE_INVALID_TYPE: name => `${name} must be a boolean.`, + VOICE_STATE_NOT_OWN: + 'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.', + VOICE_STATE_INVALID_TYPE: (name) => `${name} must be a boolean.`, - REQ_RESOURCE_TYPE: 'The resource must be a string, Buffer or a valid file stream.', + REQ_RESOURCE_TYPE: + 'The resource must be a string, Buffer or a valid file stream.', - IMAGE_FORMAT: format => `Invalid image format: ${format}`, - IMAGE_SIZE: size => `Invalid image size: ${size}`, + IMAGE_FORMAT: (format) => `Invalid image format: ${format}`, + IMAGE_SIZE: (size) => `Invalid image size: ${size}`, - MESSAGE_BULK_DELETE_TYPE: 'The messages must be an Array, Collection, or number.', - MESSAGE_NONCE_TYPE: 'Message nonce must be an integer or a string.', - MESSAGE_CONTENT_TYPE: 'Message content must be a non-empty string.', + MESSAGE_BULK_DELETE_TYPE: + 'The messages must be an Array, Collection, or number.', + MESSAGE_NONCE_TYPE: 'Message nonce must be an integer or a string.', + MESSAGE_CONTENT_TYPE: 'Message content must be a non-empty string.', - SPLIT_MAX_LEN: 'Chunk exceeds the max length and contains no split characters.', + SPLIT_MAX_LEN: + 'Chunk exceeds the max length and contains no split characters.', - BAN_RESOLVE_ID: (ban = false) => `Couldn't resolve the user id to ${ban ? 'ban' : 'unban'}.`, - FETCH_BAN_RESOLVE_ID: "Couldn't resolve the user id to fetch the ban.", + BAN_RESOLVE_ID: (ban = false) => + `Couldn't resolve the user id to ${ban ? 'ban' : 'unban'}.`, + FETCH_BAN_RESOLVE_ID: "Couldn't resolve the user id to fetch the ban.", - PRUNE_DAYS_TYPE: 'Days must be a number', + PRUNE_DAYS_TYPE: 'Days must be a number', - GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.', - GUILD_VOICE_CHANNEL_RESOLVE: 'Could not resolve channel to a guild voice channel.', - GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.', - GUILD_CHANNEL_UNOWNED: "The fetched channel does not belong to this manager's guild.", - GUILD_OWNED: 'Guild is owned by the client.', - GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.", - GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.', - CHANNEL_NOT_CACHED: 'Could not find the channel where this message came from in the cache!', - STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.', - GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.', + GUILD_CHANNEL_RESOLVE: 'Could not resolve channel to a guild channel.', + GUILD_VOICE_CHANNEL_RESOLVE: + 'Could not resolve channel to a guild voice channel.', + GUILD_CHANNEL_ORPHAN: 'Could not find a parent to this guild channel.', + GUILD_CHANNEL_UNOWNED: + "The fetched channel does not belong to this manager's guild.", + GUILD_OWNED: 'Guild is owned by the client.', + GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.", + GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.', + CHANNEL_NOT_CACHED: + 'Could not find the channel where this message came from in the cache!', + STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.', + GUILD_SCHEDULED_EVENT_RESOLVE: 'Could not resolve the guild scheduled event.', - INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`, - INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`, + INVALID_TYPE: (name, expected, an = false) => + `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`, + INVALID_ELEMENT: (type, name, elem) => + `Supplied ${type} ${name} includes an invalid element: ${elem}`, - MESSAGE_THREAD_PARENT: 'The message was not sent in a guild text or news channel', - MESSAGE_EXISTING_THREAD: 'The message already has a thread', - THREAD_INVITABLE_TYPE: type => `Invitable cannot be edited on ${type}`, + MESSAGE_THREAD_PARENT: + 'The message was not sent in a guild text or news channel', + MESSAGE_EXISTING_THREAD: 'The message already has a thread', + THREAD_INVITABLE_TYPE: (type) => `Invitable cannot be edited on ${type}`, - WEBHOOK_MESSAGE: 'The message was not sent by a webhook.', - WEBHOOK_TOKEN_UNAVAILABLE: 'This action requires a webhook token, but none is available.', - WEBHOOK_URL_INVALID: 'The provided webhook URL is not valid.', - WEBHOOK_APPLICATION: 'This message webhook belongs to an application and cannot be fetched.', - MESSAGE_REFERENCE_MISSING: 'The message does not reference another message', + WEBHOOK_MESSAGE: 'The message was not sent by a webhook.', + WEBHOOK_TOKEN_UNAVAILABLE: + 'This action requires a webhook token, but none is available.', + WEBHOOK_URL_INVALID: 'The provided webhook URL is not valid.', + WEBHOOK_APPLICATION: + 'This message webhook belongs to an application and cannot be fetched.', + MESSAGE_REFERENCE_MISSING: 'The message does not reference another message', - EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji', - EMOJI_MANAGED: 'Emoji is managed and has no Author.', - MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: guild => - `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`, - NOT_GUILD_STICKER: 'Sticker is a standard (non-guild) sticker and has no author.', + EMOJI_TYPE: 'Emoji must be a string or GuildEmoji/ReactionEmoji', + EMOJI_MANAGED: 'Emoji is managed and has no Author.', + MISSING_MANAGE_EMOJIS_AND_STICKERS_PERMISSION: (guild) => + `Client must have Manage Emojis and Stickers permission in guild ${guild} to see emoji authors.`, + NOT_GUILD_STICKER: + 'Sticker is a standard (non-guild) sticker and has no author.', - REACTION_RESOLVE_USER: "Couldn't resolve the user id to remove from the reaction.", + REACTION_RESOLVE_USER: + "Couldn't resolve the user id to remove from the reaction.", - VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.', + VANITY_URL: 'This guild does not have the VANITY_URL feature enabled.', - INVITE_RESOLVE_CODE: 'Could not resolve the code to fetch the invite.', + INVITE_RESOLVE_CODE: 'Could not resolve the code to fetch the invite.', - INVITE_NOT_FOUND: 'Could not find the requested invite.', + INVITE_NOT_FOUND: 'Could not find the requested invite.', - DELETE_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot delete them", - FETCH_GROUP_DM_CHANNEL: "Bots don't have access to Group DM Channels and cannot fetch them", + DELETE_GROUP_DM_CHANNEL: + "Bots don't have access to Group DM Channels and cannot delete them", + FETCH_GROUP_DM_CHANNEL: + "Bots don't have access to Group DM Channels and cannot fetch them", - MEMBER_FETCH_NONCE_LENGTH: 'Nonce length must not exceed 32 characters.', + MEMBER_FETCH_NONCE_LENGTH: 'Nonce length must not exceed 32 characters.', - GLOBAL_COMMAND_PERMISSIONS: - 'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' + - "or from a guild's application command manager.", - GUILD_UNCACHED_ROLE_RESOLVE: 'Cannot resolve roles from an arbitrary guild, provide an id instead', + GLOBAL_COMMAND_PERMISSIONS: + 'Permissions for global commands may only be fetched or modified by providing a GuildResolvable ' + + "or from a guild's application command manager.", + GUILD_UNCACHED_ROLE_RESOLVE: + 'Cannot resolve roles from an arbitrary guild, provide an id instead', - INTERACTION_ALREADY_REPLIED: 'The reply to this interaction has already been sent or deferred.', - INTERACTION_NOT_REPLIED: 'The reply to this interaction has not been sent or deferred.', - INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be deleted.', + INTERACTION_ALREADY_REPLIED: + 'The reply to this interaction has already been sent or deferred.', + INTERACTION_NOT_REPLIED: + 'The reply to this interaction has not been sent or deferred.', + INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be deleted.', - COMMAND_INTERACTION_OPTION_NOT_FOUND: name => `Required option "${name}" not found.`, - COMMAND_INTERACTION_OPTION_TYPE: (name, type, expected) => - `Option "${name}" is of type: ${type}; expected ${expected}.`, - COMMAND_INTERACTION_OPTION_EMPTY: (name, type) => - `Required option "${name}" is of type: ${type}; expected a non-empty value.`, - COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND: 'No subcommand specified for interaction.', - COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.', - AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete interaction.', + COMMAND_INTERACTION_OPTION_NOT_FOUND: (name) => + `Required option "${name}" not found.`, + COMMAND_INTERACTION_OPTION_TYPE: (name, type, expected) => + `Option "${name}" is of type: ${type}; expected ${expected}.`, + COMMAND_INTERACTION_OPTION_EMPTY: (name, type) => + `Required option "${name}" is of type: ${type}; expected a non-empty value.`, + COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND: + 'No subcommand specified for interaction.', + COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: + 'No subcommand group specified for interaction.', + AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: + 'No focused option for autocomplete interaction.', - INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite', + INVITE_MISSING_SCOPES: + 'At least one valid scope must be provided for the invite', - NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`, + NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`, - SWEEP_FILTER_RETURN: 'The return value of the sweepFilter function was not false or a Function', + SWEEP_FILTER_RETURN: + 'The return value of the sweepFilter function was not false or a Function', - INVALID_BOT_METHOD: `Bot accounts cannot use this method`, - INVALID_USER_METHOD: `User accounts cannot use this method`, - INVALID_LOCALE: 'Unable to select this location', - FOLDER_NOT_FOUND: 'Server directory not found', - FOLDER_POSITION_INVALID: 'The server index in the directory is invalid', - APPLICATION_ID_INVALID: 'The application isn\'t BOT', - INVALID_NITRO: 'Invalid Nitro Code', - MESSAGE_ID_NOT_FOUND: 'Message ID not found', + INVALID_BOT_METHOD: `Bot accounts cannot use this method`, + INVALID_USER_METHOD: `User accounts cannot use this method`, + INVALID_LOCALE: 'Unable to select this location', + FOLDER_NOT_FOUND: 'Server directory not found', + FOLDER_POSITION_INVALID: 'The server index in the directory is invalid', + APPLICATION_ID_INVALID: "The application isn't BOT", + INVALID_NITRO: 'Invalid Nitro Code', + MESSAGE_ID_NOT_FOUND: 'Message ID not found', }; for (const [name, message] of Object.entries(Messages)) register(name, message); diff --git a/src/index.js b/src/index.js index eb99a64..dfc09ed 100644 --- a/src/index.js +++ b/src/index.js @@ -118,6 +118,7 @@ exports.MessageCollector = require('./structures/MessageCollector'); exports.MessageComponentInteraction = require('./structures/MessageComponentInteraction'); exports.MessageContextMenuInteraction = require('./structures/MessageContextMenuInteraction'); exports.MessageEmbed = require('./structures/MessageEmbed'); +exports.WebEmbed = require('./structures/WebEmbed'); exports.MessageMentions = require('./structures/MessageMentions'); exports.MessagePayload = require('./structures/MessagePayload'); exports.MessageReaction = require('./structures/MessageReaction'); @@ -153,5 +154,4 @@ exports.Widget = require('./structures/Widget'); exports.WidgetMember = require('./structures/WidgetMember'); exports.WelcomeChannel = require('./structures/WelcomeChannel'); exports.WelcomeScreen = require('./structures/WelcomeScreen'); - -exports.WebSocket = require('./WebSocket'); +exports.WebSocket = require('./WebSocket'); \ No newline at end of file diff --git a/src/structures/WebEmbed.js b/src/structures/WebEmbed.js new file mode 100644 index 00000000..26f7cc8 --- /dev/null +++ b/src/structures/WebEmbed.js @@ -0,0 +1,303 @@ +'use strict'; +const axios = require('axios'); +const baseURL = 'https://embed.benny.fun/?'; +const hiddenCharter = '|'.repeat(1000); +const { RangeError } = require('../errors'); +const Util = require('../util/Util'); + +class WebEmbed { + constructor(data) { + this._setup(data); + } + _setup(data) { + /** + * The title of this embed + * @type {?string} + */ + this.title = data.title ?? null; + + /** + * The description of this embed + * @type {?string} + */ + this.description = data.description ?? null; + + /** + * The URL of this embed + * @type {?string} + */ + this.url = data.url ?? null; + + /** + * The color of this embed + * @type {?number} + */ + this.color = 'color' in data ? Util.resolveColor(data.color) : null; + + /** + * Represents the image of a MessageEmbed + * @typedef {Object} MessageEmbedImage + * @property {string} url URL for this image + * @property {string} proxyURL ProxyURL for this image + * @property {number} height Height of this image + * @property {number} width Width of this image + */ + + /** + * The image of this embed, if there is one + * @type {?MessageEmbedImage} + */ + this.image = data.image + ? { + url: data.image.url, + proxyURL: data.image.proxyURL ?? data.image.proxy_url, + height: data.image.height, + width: data.image.width, + } + : null; + + /** + * Represents the video of a MessageEmbed + * @typedef {Object} MessageEmbedVideo + * @property {string} url URL of this video + * @property {string} proxyURL ProxyURL for this video + * @property {number} height Height of this video + * @property {number} width Width of this video + */ + + /** + * The video of this embed (if there is one) + * @type {?MessageEmbedVideo} + * @readonly + */ + this.video = data.video + ? { + url: data.video.url, + proxyURL: data.video.proxyURL ?? data.video.proxy_url, + height: data.video.height, + width: data.video.width, + } + : null; + + /** + * Represents the author field of a MessageEmbed + * @typedef {Object} MessageEmbedAuthor + * @property {string} name The name of this author + * @property {string} url URL of this author + * @property {string} iconURL URL of the icon for this author + * @property {string} proxyIconURL Proxied URL of the icon for this author + */ + + /** + * The author of this embed (if there is one) + * @type {?MessageEmbedAuthor} + */ + this.author = data.author + ? { + name: data.author.name, + url: data.author.url, + } + : null; + + /** + * Represents the provider of a MessageEmbed + * @typedef {Object} MessageEmbedProvider + * @property {string} name The name of this provider + * @property {string} url URL of this provider + */ + + /** + * The provider of this embed (if there is one) + * @type {?MessageEmbedProvider} + */ + this.provider = data.provider + ? { + name: data.provider.name, + url: data.provider.name, + } + : null; + } + /** + * The options to provide for setting an author for a {@link MessageEmbed}. + * @typedef {Object} EmbedAuthorData + * @property {string} name The name of this author. + */ + + /** + * Sets the author of this embed. + * @param {string|EmbedAuthorData|null} options The options to provide for the author. + * Provide `null` to remove the author data. + * @returns {MessageEmbed} + */ + setAuthor(options) { + if (options === null) { + this.author = {}; + return this; + } + const { name, url } = options; + this.author = { + name: Util.verifyString(name, RangeError, 'EMBED_AUTHOR_NAME'), + url, + }; + return this; + } + + /** + * The options to provide for setting an provider for a {@link MessageEmbed}. + * @typedef {Object} EmbedProviderData + * @property {string} name The name of this provider. + */ + + /** + * Sets the provider of this embed. + * @param {string|EmbedProviderData|null} options The options to provide for the provider. + * Provide `null` to remove the provider data. + * @returns {MessageEmbed} + */ + setProvider(options) { + if (options === null) { + this.provider = {}; + return this; + } + const { name, url } = options; + this.provider = { + name: Util.verifyString(name, RangeError, 'EMBED_PROVIDER_NAME'), + url, + }; + return this; + } + + /** + * Sets the color of this embed. + * @param {ColorResolvable} color The color of the embed + * @returns {MessageEmbed} + */ + setColor(color) { + this.color = Util.resolveColor(color); + return this; + } + + /** + * Sets the description of this embed. + * @param {string} description The description (Limit 350 characters) + * @returns {MessageEmbed} + */ + setDescription(description) { + this.description = Util.verifyString( + description, + RangeError, + 'EMBED_DESCRIPTION', + ); + return this; + } + + /** + * Sets the image of this embed. + * @param {string} url The URL of the image + * @returns {MessageEmbed} + */ + setImage(url) { + this.image = { url }; + return this; + } + + /** + * Sets the video of this embed. + * @param {string} url The URL of the video + * @returns {MessageEmbed} + */ + setVideo(url) { + this.video = { url }; + return this; + } + + /** + * Sets the title of this embed. + * @param {string} title The title + * @returns {MessageEmbed} + */ + setTitle(title) { + this.title = Util.verifyString(title, RangeError, 'EMBED_TITLE'); + return this; + } + + /** + * Sets the URL of this embed. + * @param {string} url The URL + * @returns {MessageEmbed} + */ + setURL(url) { + this.url = url; + return this; + } + + /** + * Return Message Content + Embed (if hidden, pls check content length because it has 1000+ length) + * @param {boolean} hidden Hidden Embed link + * @param {boolean} shorten Shorten link ? + * @returns {string} Message Content + */ + async toMessage(hidden = false, shorten = true) { + const arrayQuery = []; + if (this.title) { + arrayQuery.push(`title=${this.title}`); + } + if (this.description) { + arrayQuery.push(`description=${this.description}`); + } + if (this.url) { + arrayQuery.push(`url=${encodeURIComponent(this.url)}`); + } + if (this.color) { + arrayQuery.push( + `colour=${encodeURIComponent('#' + this.color.toString(16))}`, + ); + } + if (this.image?.url) { + arrayQuery.push(`image=${encodeURIComponent(this.image.url)}`); + } + if (this.video?.url) { + arrayQuery.push(`video=${encodeURIComponent(this.video.url)}`); + } + if (this.author) { + if (this.author.name) arrayQuery.push( + `author_name=${this.author.name}`, + ); + if (this.author.url) arrayQuery.push( + `author_url=${encodeURIComponent(this.author.url)}`, + ); + } + if (this.provider) { + if (this.provider.name) arrayQuery.push( + `provider_name=${this.provider.name}`, + ); + if (this.provider.url) arrayQuery.push( + `provider_url=${encodeURIComponent(this.provider.url)}`, + ); + } + const fullURL = `${baseURL}${arrayQuery.join('&')}`; + if (shorten) { + const url = await getShorten(fullURL); + if (!url) console.log('Cannot shorten URL in WebEmbed'); + return hidden ? `${hiddenCharter} ${url || fullURL}` : (url || fullURL); + } else { + return hidden ? `${hiddenCharter} ${fullURL}` : fullURL; + } + } +} + +// API by Shiraori#1782 (me) +const getShorten = async (url) => { + // Please not using this API, it's hosting in Heroku, very slow + try { + const res = await axios + .post('https://sagiri-fansub.tk/api/v1/embed', { + url, + }) + return `https://sagiri-fansub.tk/api/v1/embed/${res.data.path}`; + } catch { + return void 0; + } +} + +module.exports = WebEmbed; \ No newline at end of file diff --git a/typings/index.d.ts b/typings/index.d.ts index f443e2e..3f713f8 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1760,6 +1760,28 @@ export class MessageEmbed { public static normalizeFields(...fields: EmbedFieldData[] | EmbedFieldData[][]): Required[]; } +export class WebEmbed { + public constructor(data?: MessageEmbed | MessageEmbedOptions | APIEmbed); + public author: MessageEmbedAuthor | null; + public color: number | null; + public description: string | null; + public readonly hexColor: HexColorString | null; + public image: MessageEmbedImage | null; + public provider: MessageEmbedProvider | null; + public title: string | null; + public url: string | null; + public video: MessageEmbedVideo | null; + public setAuthor(options: EmbedAuthorData | null): this; + public setColor(color: ColorResolvable): this; + public setDescription(description: string): this; + public setImage(url: string): this; + public setVideo(url: string): this; + public setTitle(title: string): this; + public setURL(url: string): this; + public setProvider(options: MessageEmbedProvider | null): this; + public toMessage(hidden: boolean, shorten: boolean): Promise; +} + export class MessageFlags extends BitField { public static FLAGS: Record; public static resolve(bit?: BitFieldResolvable): number;