From 42e73e7718ea7e854cf4eab20be31e4eb1d5b57f Mon Sep 17 00:00:00 2001 From: ITOH <72305210+itohatweb@users.noreply.github.com> Date: Thu, 6 May 2021 14:48:09 +0200 Subject: [PATCH 1/4] Update edit_webhook_message.ts --- src/types/webhooks/edit_webhook_message.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/webhooks/edit_webhook_message.ts b/src/types/webhooks/edit_webhook_message.ts index ea18f54a5..dc883cb36 100644 --- a/src/types/webhooks/edit_webhook_message.ts +++ b/src/types/webhooks/edit_webhook_message.ts @@ -1,7 +1,8 @@ +import { FileContent } from "../discordeno/file_content.ts"; import { Embed } from "../embeds/embed.ts"; import { AllowedMentions } from "../messages/allowed_mentions.ts"; import { Attachment } from "../messages/attachment.ts"; -import { FileContent } from "../discordeno/file_content.ts"; +import { MessageComponents } from "../messages/components/message_components.ts"; /** https://discord.com/developers/docs/resources/webhook#edit-webhook-message-jsonform-params */ export interface EditWebhookMessage { @@ -15,4 +16,6 @@ export interface EditWebhookMessage { allowedMentions?: AllowedMentions | null; /** Attached files to keep */ attachments?: Attachment | null; + /** The components you would like to have sent in this message */ + components?: MessageComponents; } From 1555fc43e106a0bf5105b54f6767c541a7866b65 Mon Sep 17 00:00:00 2001 From: ITOH <72305210+itohatweb@users.noreply.github.com> Date: Thu, 6 May 2021 14:48:11 +0200 Subject: [PATCH 2/4] Update edit_message.ts --- src/types/messages/edit_message.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/types/messages/edit_message.ts b/src/types/messages/edit_message.ts index 7009b9a1c..f7919d905 100644 --- a/src/types/messages/edit_message.ts +++ b/src/types/messages/edit_message.ts @@ -2,6 +2,7 @@ import { FileContent } from "../discordeno/file_content.ts"; import { Embed } from "../embeds/embed.ts"; import { AllowedMentions } from "./allowed_mentions.ts"; import { Attachment } from "./attachment.ts"; +import { MessageComponents } from "./components/message_components.ts"; /** https://discord.com/developers/docs/resources/channel#edit-message-json-params */ export interface EditMessage { @@ -17,4 +18,6 @@ export interface EditMessage { allowedMentions?: AllowedMentions | null; /** Attached files to keep */ attachments?: Attachment | null; + /** The components you would like to have sent in this message */ + components?: MessageComponents; } From 5f6d556e1d3024e296505c2a630de68f8d891f23 Mon Sep 17 00:00:00 2001 From: ITOH <72305210+itohatweb@users.noreply.github.com> Date: Thu, 6 May 2021 14:53:24 +0200 Subject: [PATCH 3/4] add check --- src/helpers/messages/edit_message.ts | 7 ++- src/helpers/messages/send_message.ts | 49 +----------------- src/helpers/webhooks/edit_webhook_message.ts | 7 ++- src/util/utils.ts | 52 ++++++++++++++++++++ 4 files changed, 66 insertions(+), 49 deletions(-) diff --git a/src/helpers/messages/edit_message.ts b/src/helpers/messages/edit_message.ts index 637133b08..a74dd1aec 100644 --- a/src/helpers/messages/edit_message.ts +++ b/src/helpers/messages/edit_message.ts @@ -2,12 +2,13 @@ import { botId } from "../../bot.ts"; import { rest } from "../../rest/rest.ts"; import { DiscordenoMessage } from "../../structures/message.ts"; import { structures } from "../../structures/mod.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { EditMessage } from "../../types/messages/edit_message.ts"; import type { Message } from "../../types/messages/message.ts"; -import { Errors } from "../../types/discordeno/errors.ts"; import type { PermissionStrings } from "../../types/permissions/permission_strings.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; +import { validateComponents } from "../../util/utils.ts"; /** Edit the message. */ export async function editMessage( @@ -20,6 +21,10 @@ export async function editMessage( if (typeof content === "string") content = { content }; + if (content.components?.length) { + validateComponents(content.components); + } + const requiredPerms: PermissionStrings[] = ["SEND_MESSAGES"]; await requireBotChannelPermissions(message.channelId, requiredPerms); diff --git a/src/helpers/messages/send_message.ts b/src/helpers/messages/send_message.ts index 6f21f6912..9840e78e8 100644 --- a/src/helpers/messages/send_message.ts +++ b/src/helpers/messages/send_message.ts @@ -4,16 +4,13 @@ import { structures } from "../../structures/mod.ts"; import { DiscordChannelTypes } from "../../types/channels/channel_types.ts"; import { Errors } from "../../types/discordeno/errors.ts"; import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts"; -import { ButtonStyles } from "../../types/messages/components/button_styles.ts"; import type { CreateMessage } from "../../types/messages/create_message.ts"; import type { Message } from "../../types/messages/message.ts"; import type { PermissionStrings } from "../../types/permissions/permission_strings.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; -import { snakelize } from "../../util/utils.ts"; +import { snakelize, validateComponents } from "../../util/utils.ts"; import { validateLength } from "../../util/validate_length.ts"; -import { isActionRow } from "../type_guards/is_action_row.ts"; -import { isButton } from "../type_guards/is_button.ts"; /** Send a message to the channel. Requires SEND_MESSAGES permission. */ export async function sendMessage( @@ -100,49 +97,7 @@ export async function sendMessage( } if (content.components?.length) { - let actionRowCounter = 0; - - for (const component of content.components) { - // 5 Link buttons can not have a customId - if (isButton(component)) { - if ( - component.type === ButtonStyles.Link && - component.customId - ) { - throw new Error(Errors.LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID); - } - // Other buttons must have a customId - if ( - !component.customId && component.type !== ButtonStyles.Link - ) { - throw new Error(Errors.BUTTON_REQUIRES_CUSTOM_ID); - } - - if (!validateLength(component.label, { max: 80 })) { - throw new Error(Errors.COMPONENT_LABEL_TOO_BIG); - } - - if ( - component.customId && - !validateLength(component.customId, { max: 100 }) - ) { - throw new Error(Errors.COMPONENT_CUSTOM_ID_TOO_BIG); - } - } - - if (!isActionRow(component)) { - continue; - } - - actionRowCounter++; - // Max of 5 ActionRows per message - if (actionRowCounter > 5) throw new Error(Errors.TOO_MANY_ACTION_ROWS); - - // Max of 5 Buttons (or any component type) within an ActionRow - if (component.components?.length > 5) { - throw new Error(Errors.TOO_MANY_COMPONENTS); - } - } + validateComponents(content.components); } const result = await rest.runMethod( diff --git a/src/helpers/webhooks/edit_webhook_message.ts b/src/helpers/webhooks/edit_webhook_message.ts index c066d2933..6c6a0c1e4 100644 --- a/src/helpers/webhooks/edit_webhook_message.ts +++ b/src/helpers/webhooks/edit_webhook_message.ts @@ -1,10 +1,11 @@ import { rest } from "../../rest/rest.ts"; import { structures } from "../../structures/mod.ts"; +import { Errors } from "../../types/discordeno/errors.ts"; import { DiscordAllowedMentionsTypes } from "../../types/messages/allowed_mentions_types.ts"; import type { Message } from "../../types/messages/message.ts"; -import { Errors } from "../../types/discordeno/errors.ts"; import type { EditWebhookMessage } from "../../types/webhooks/edit_webhook_message.ts"; import { endpoints } from "../../util/constants.ts"; +import { validateComponents } from "../../util/utils.ts"; export async function editWebhookMessage( webhookId: bigint, @@ -59,6 +60,10 @@ export async function editWebhookMessage( } } + if (options.components?.length) { + validateComponents(options.components); + } + const result = await rest.runMethod( "patch", options.messageId diff --git a/src/util/utils.ts b/src/util/utils.ts index d936d291b..6058d542e 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -1,11 +1,15 @@ import { encode } from "../../deps.ts"; import { eventHandlers } from "../bot.ts"; +import { isActionRow } from "../helpers/type_guards/is_action_row.ts"; +import { isButton } from "../helpers/type_guards/is_button.ts"; import { Errors } from "../types/discordeno/errors.ts"; import type { ApplicationCommandOption } from "../types/interactions/commands/application_command_option.ts"; import type { ApplicationCommandOptionChoice } from "../types/interactions/commands/application_command_option_choice.ts"; import { DiscordApplicationCommandOptionTypes } from "../types/interactions/commands/application_command_option_types.ts"; import type { CreateGlobalApplicationCommand } from "../types/interactions/commands/create_global_application_command.ts"; import type { EditGlobalApplicationCommand } from "../types/interactions/commands/edit_global_application_command.ts"; +import { ButtonStyles } from "../types/messages/components/button_styles.ts"; +import type { MessageComponents } from "../types/messages/components/message_components.ts"; import type { DiscordImageFormat } from "../types/misc/image_format.ts"; import type { DiscordImageSize } from "../types/misc/image_size.ts"; import { SLASH_COMMANDS_NAME_REGEX } from "./constants.ts"; @@ -216,3 +220,51 @@ export function hasOwnProperty( // deno-lint-ignore no-prototype-builtins return obj.hasOwnProperty(prop); } + +export function validateComponents(components: MessageComponents) { + if (components?.length) { + let actionRowCounter = 0; + + for (const component of components) { + // 5 Link buttons can not have a customId + if (isButton(component)) { + if ( + component.type === ButtonStyles.Link && + component.customId + ) { + throw new Error(Errors.LINK_BUTTON_CANNOT_HAVE_CUSTOM_ID); + } + // Other buttons must have a customId + if ( + !component.customId && component.type !== ButtonStyles.Link + ) { + throw new Error(Errors.BUTTON_REQUIRES_CUSTOM_ID); + } + + if (!validateLength(component.label, { max: 80 })) { + throw new Error(Errors.COMPONENT_LABEL_TOO_BIG); + } + + if ( + component.customId && + !validateLength(component.customId, { max: 100 }) + ) { + throw new Error(Errors.COMPONENT_CUSTOM_ID_TOO_BIG); + } + } + + if (!isActionRow(component)) { + continue; + } + + actionRowCounter++; + // Max of 5 ActionRows per message + if (actionRowCounter > 5) throw new Error(Errors.TOO_MANY_ACTION_ROWS); + + // Max of 5 Buttons (or any component type) within an ActionRow + if (component.components?.length > 5) { + throw new Error(Errors.TOO_MANY_COMPONENTS); + } + } + } +} From fb8035c4eff3a42e531a8ebb0583636f6667debe Mon Sep 17 00:00:00 2001 From: ITOH <72305210+itohatweb@users.noreply.github.com> Date: Thu, 6 May 2021 15:28:42 +0200 Subject: [PATCH 4/4] change: move closeWS & sendShardMessage to ws object --- src/helpers/members/fetch_members.ts | 3 +-- src/helpers/misc/edit_bot_status.ts | 3 +-- src/ws/handle_on_message.ts | 3 +-- src/ws/heartbeat.ts | 3 +-- src/ws/identify.ts | 6 ++---- src/ws/resume.ts | 6 ++---- src/ws/ws.ts | 12 +++++++++--- tests/ws/ws_close.ts | 3 +-- 8 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/helpers/members/fetch_members.ts b/src/helpers/members/fetch_members.ts index 5f02762dd..6aa05964f 100644 --- a/src/helpers/members/fetch_members.ts +++ b/src/helpers/members/fetch_members.ts @@ -5,7 +5,6 @@ import { Errors } from "../../types/discordeno/errors.ts"; import { DiscordGatewayIntents } from "../../types/gateway/gateway_intents.ts"; import type { RequestGuildMembers } from "../../types/members/request_guild_members.ts"; import { Collection } from "../../util/collection.ts"; -import { sendShardMessage } from "../../ws/send_shard_message.ts"; import { ws } from "../../ws/ws.ts"; /** @@ -37,7 +36,7 @@ export function fetchMembers( const nonce = `${guildId}-${Date.now()}`; cache.fetchAllMembersProcessingRequests.set(nonce, resolve); - sendShardMessage(shardId, { + ws.sendShardMessage(shardId, { op: DiscordGatewayOpcodes.RequestGuildMembers, d: { guild_id: guildId, diff --git a/src/helpers/misc/edit_bot_status.ts b/src/helpers/misc/edit_bot_status.ts index bfed6b0bc..288f9df6b 100644 --- a/src/helpers/misc/edit_bot_status.ts +++ b/src/helpers/misc/edit_bot_status.ts @@ -1,7 +1,6 @@ import { eventHandlers } from "../../bot.ts"; import { DiscordGatewayOpcodes } from "../../types/codes/gateway_opcodes.ts"; import type { StatusUpdate } from "../../types/gateway/status_update.ts"; -import { sendShardMessage } from "../../ws/send_shard_message.ts"; import { ws } from "../../ws/ws.ts"; export function editBotStatus(data: Omit) { @@ -11,7 +10,7 @@ export function editBotStatus(data: Omit) { `Running forEach loop in editBotStatus function.`, ); - sendShardMessage(shard, { + ws.sendShardMessage(shard, { op: DiscordGatewayOpcodes.StatusUpdate, d: { since: null, diff --git a/src/ws/handle_on_message.ts b/src/ws/handle_on_message.ts index 33335c8da..4a3a3e0b2 100644 --- a/src/ws/handle_on_message.ts +++ b/src/ws/handle_on_message.ts @@ -8,7 +8,6 @@ import { camelize, delay } from "../util/utils.ts"; import { decompressWith } from "./deps.ts"; import { identify } from "./identify.ts"; import { resume } from "./resume.ts"; -import { sendShardMessage } from "./send_shard_message.ts"; import { ws } from "./ws.ts"; /** Handler for handling every message event from websocket. */ @@ -39,7 +38,7 @@ export async function handleOnMessage(message: any, shardId: number) { shard.heartbeat.lastSentAt = Date.now(); // Discord randomly sends this requiring an immediate heartbeat back - sendShardMessage(shard, { + ws.sendShardMessage(shard, { op: DiscordGatewayOpcodes.Heartbeat, d: shard?.previousSequenceNumber, }, true); diff --git a/src/ws/heartbeat.ts b/src/ws/heartbeat.ts index 133b95982..a04028f74 100644 --- a/src/ws/heartbeat.ts +++ b/src/ws/heartbeat.ts @@ -1,6 +1,5 @@ import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts"; import { delay } from "../util/utils.ts"; -import { closeWS } from "./close_ws.ts"; import { identify } from "./identify.ts"; import { ws } from "./ws.ts"; @@ -45,7 +44,7 @@ export async function heartbeat(shardId: number, interval: number) { } if (!currentShard.heartbeat.acknowledged) { - closeWS(currentShard.ws, 3066, "Did not receive an ACK in time."); + ws.closeWS(currentShard.ws, 3066, "Did not receive an ACK in time."); return identify(shardId, ws.maxShards); } diff --git a/src/ws/identify.ts b/src/ws/identify.ts index bcd8d2fa4..c645df9d1 100644 --- a/src/ws/identify.ts +++ b/src/ws/identify.ts @@ -1,6 +1,4 @@ import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts"; -import { closeWS } from "./close_ws.ts"; -import { sendShardMessage } from "./send_shard_message.ts"; import { ws } from "./ws.ts"; export async function identify(shardId: number, maxShards: number) { @@ -9,7 +7,7 @@ export async function identify(shardId: number, maxShards: number) { // Need to clear the old heartbeat interval const oldShard = ws.shards.get(shardId); if (oldShard) { - closeWS(oldShard.ws, 3065, "Reidentifying closure of old shard"); + ws.closeWS(oldShard.ws, 3065, "Reidentifying closure of old shard"); clearInterval(oldShard.heartbeat.intervalId); } @@ -41,7 +39,7 @@ export async function identify(shardId: number, maxShards: number) { }); socket.onopen = () => { - sendShardMessage(shardId, { + ws.sendShardMessage(shardId, { op: DiscordGatewayOpcodes.Identify, d: { ...ws.identifyPayload, shard: [shardId, maxShards] }, }, true); diff --git a/src/ws/resume.ts b/src/ws/resume.ts index 4be26a0ac..d1efa4e7b 100644 --- a/src/ws/resume.ts +++ b/src/ws/resume.ts @@ -1,6 +1,4 @@ import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts"; -import { closeWS } from "./close_ws.ts"; -import { sendShardMessage } from "./send_shard_message.ts"; import { ws } from "./ws.ts"; export async function resume(shardId: number) { @@ -12,7 +10,7 @@ export async function resume(shardId: number) { if (oldShard) { // HOW TO CLOSE OLD SHARD SOCKET!!! - closeWS(oldShard.ws, 3064, "Resuming the shard, closing old shard."); + ws.closeWS(oldShard.ws, 3064, "Resuming the shard, closing old shard."); // STOP OLD HEARTBEAT clearInterval(oldShard.heartbeat.intervalId); } @@ -48,7 +46,7 @@ export async function resume(shardId: number) { // Resume on open socket.onopen = () => { - sendShardMessage(shardId, { + ws.sendShardMessage(shardId, { op: DiscordGatewayOpcodes.Resume, d: { token: ws.identifyPayload.token, diff --git a/src/ws/ws.ts b/src/ws/ws.ts index 2ff84d92b..76519d39f 100644 --- a/src/ws/ws.ts +++ b/src/ws/ws.ts @@ -1,6 +1,7 @@ import { DiscordGatewayOpcodes } from "../types/codes/gateway_opcodes.ts"; import { Collection } from "../util/collection.ts"; import { cleanupLoadingShards } from "./cleanup_loading_shards.ts"; +import { closeWS } from "./close_ws.ts"; import { createShard } from "./create_shard.ts"; import { log } from "./events.ts"; import { handleDiscordPayload } from "./handle_discord_payload.ts"; @@ -9,6 +10,7 @@ import { heartbeat } from "./heartbeat.ts"; import { identify } from "./identify.ts"; import { processQueue } from "./process_queue.ts"; import { resharder } from "./resharder.ts"; +import { sendShardMessage } from "./send_shard_message.ts"; import { spawnShards } from "./spawn_shards.ts"; import { startGateway } from "./start_gateway.ts"; import { tellClusterToIdentify } from "./tell_cluster_to_identify.ts"; @@ -92,7 +94,7 @@ export const ws = { spawnShards, /** Create the websocket and adds the proper handlers to the websocket. */ createShard, - /** Begins identification of the shard to discord */ + /** Begins identification of the shard to discord. */ identify, /** Begins heartbeating of the shard to keep it alive */ heartbeat, @@ -106,10 +108,14 @@ export const ws = { resharder, /** Cleanups loading shards that were unable to load. */ cleanupLoadingShards, - /** Handles the message events from websocket */ + /** Handles the message events from websocket. */ handleOnMessage, - /** Handles processing queue of requests send to this shard */ + /** Handles processing queue of requests send to this shard. */ processQueue, + /** Closes shard WebSocket connection properly. */ + closeWS, + /** Properly adds a message to the shards queue */ + sendShardMessage, }; export interface DiscordenoShard { diff --git a/tests/ws/ws_close.ts b/tests/ws/ws_close.ts index 1a1ce6111..fdc7d2493 100644 --- a/tests/ws/ws_close.ts +++ b/tests/ws/ws_close.ts @@ -1,5 +1,4 @@ import { delay } from "../../src/util/utils.ts"; -import { closeWS } from "../../src/ws/close_ws.ts"; import { ws } from "../../src/ws/ws.ts"; import { defaultTestOptions } from "./start_bot.ts"; @@ -9,7 +8,7 @@ Deno.test({ async fn() { ws.shards.forEach((shard) => { clearInterval(shard.heartbeat.intervalId); - closeWS(shard.ws, 3061, "Discordeno Testing Finished! Do Not RESUME!"); + ws.closeWS(shard.ws, 3061, "Discordeno Testing Finished! Do Not RESUME!"); }); await delay(3000);