diff --git a/src/helpers/messages/send_message.ts b/src/helpers/messages/send_message.ts index 16b1ef2a8..802e876c4 100644 --- a/src/helpers/messages/send_message.ts +++ b/src/helpers/messages/send_message.ts @@ -10,6 +10,7 @@ import { PermissionStrings } from "../../types/permissions/permission_strings.ts import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; import { camelKeysToSnakeCase } from "../../util/utils.ts"; +import { validateLength } from "../../util/validate_length.ts"; /** Send a message to the channel. Requires SEND_MESSAGES permission. */ export async function sendMessage( @@ -48,7 +49,7 @@ export async function sendMessage( } // Use ... for content length due to unicode characters and js .length handling - if (content.content && [...content.content].length > 2000) { + if (content.content && !validateLength(content.content, { max: 2000 })) { throw new Error(Errors.MESSAGE_MAX_LENGTH); } diff --git a/src/helpers/webhooks/create_webhook.ts b/src/helpers/webhooks/create_webhook.ts index c8173d433..0f1e344c8 100644 --- a/src/helpers/webhooks/create_webhook.ts +++ b/src/helpers/webhooks/create_webhook.ts @@ -6,6 +6,7 @@ import { DiscordWebhook } from "../../types/webhooks/webhook.ts"; import { endpoints } from "../../util/constants.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts"; import { snakeKeysToCamelCase, urlToBase64 } from "../../util/utils.ts"; +import { validateLength } from "../../util/validate_length.ts"; /** * Create a new webhook. Requires the MANAGE_WEBHOOKS permission. Returns a webhook object on success. Webhook names follow our naming restrictions that can be found in our Usernames and Nicknames documentation, with the following additional stipulations: @@ -21,9 +22,7 @@ export async function createWebhook( if ( // Specific usernames that discord does not allow options.name === "clyde" || - // Character limit checks. [...] checks are because of js unicode length handling - [...options.name].length < 2 || - [...options.name].length > 32 + !validateLength(options.name, { min: 2, max: 32 }) ) { throw new Error(Errors.INVALID_WEBHOOK_NAME); } diff --git a/src/util/utils.ts b/src/util/utils.ts index 5bb7da7b6..6a2b9a240 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -9,6 +9,7 @@ import { DiscordImageFormat } from "../types/misc/image_format.ts"; import { DiscordImageSize } from "../types/misc/image_size.ts"; import { EditGlobalApplicationCommand } from "../types/mod.ts"; import { SLASH_COMMANDS_NAME_REGEX } from "./constants.ts"; +import { validateLength } from "./validate_length.ts"; export async function urlToBase64(url: string) { const buffer = await fetch(url).then((res) => res.arrayBuffer()); @@ -53,7 +54,9 @@ function snakeToCamelCase(text: string) { function isConvertableObject(obj: unknown) { return ( - obj === Object(obj) && !Array.isArray(obj) && typeof obj !== "function" && + obj === Object(obj) && + !Array.isArray(obj) && + typeof obj !== "function" && !(obj instanceof Blob) ); } @@ -122,7 +125,7 @@ function validateSlashOptionChoices( "loop", `Running for of loop in validateSlashOptionChoices function.`, ); - if ([...choice.name].length < 1 || [...choice.name].length > 100) { + if (!validateLength(choice.name, { min: 1, max: 100 })) { throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES); } @@ -156,10 +159,8 @@ function validateSlashOptions(options: ApplicationCommandOption[]) { } if ( - [...option.name].length < 1 || - [...option.name].length > 32 || - [...option.description].length < 1 || - [...option.description].length > 100 + !validateLength(option.name, { min: 1, max: 32 }) || + !validateLength(option.description, { min: 1, max: 100 }) ) { throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES); } @@ -188,8 +189,7 @@ export function validateSlashCommands( if ( (command.description && - ([...command.description].length < 1 || - [...command.description].length > 100)) || + !validateLength(command.description, { min: 1, max: 100 })) || (create && !command.description) ) { throw new Error(Errors.INVALID_SLASH_DESCRIPTION); diff --git a/src/util/validate_length.ts b/src/util/validate_length.ts new file mode 100644 index 000000000..0bd1d852d --- /dev/null +++ b/src/util/validate_length.ts @@ -0,0 +1,14 @@ +/** Validates the length of a string in JS. Certain characters in JS can have multiple numbers in length in unicode and discords api is in python which treats length differently. */ +export function validateLength( + text: string, + options: { max?: number; min?: number }, +) { + const length = [...text].length; + + // Text is too long + if (options.max && length > options.max) return false; + // Text is too short + if (options.min && length < options.min) return false; + + return true; +} diff --git a/tests/mod.ts b/tests/mod.ts index 3cf2a56f8..daa91c90f 100644 --- a/tests/mod.ts +++ b/tests/mod.ts @@ -4,6 +4,7 @@ // First complete non-api reliant testing. // Don't waste api rate limits if a early test fails. import "./util/utils.ts"; +import "./util/validate_length.ts"; // API TESTING BELOW diff --git a/tests/util/validate_length.ts b/tests/util/validate_length.ts new file mode 100644 index 000000000..aab8eeb19 --- /dev/null +++ b/tests/util/validate_length.ts @@ -0,0 +1,44 @@ +import { validateLength } from "../../src/util/validate_length.ts"; +import { assertEquals } from "../deps.ts"; + +Deno.test({ + name: "[utils] Validate length is too low", + fn() { + assertEquals(validateLength("test", { min: 5 }), false); + }, +}); + +Deno.test({ + name: "[utils] Validate length is too high", + fn() { + assertEquals(validateLength("test", { max: 3 }), false); + }, +}); + +Deno.test({ + name: "[utils] Validate length is NOT just right in between.", + fn() { + assertEquals(validateLength("test", { min: 5, max: 3 }), false); + }, +}); + +Deno.test({ + name: "[utils] Validate length is NOT too low", + fn() { + assertEquals(validateLength("test", { min: 3 }), true); + }, +}); + +Deno.test({ + name: "[utils] Validate length is NOT too high", + fn() { + assertEquals(validateLength("test", { max: 5 }), true); + }, +}); + +Deno.test({ + name: "[utils] Validate length is just right in between.", + fn() { + assertEquals(validateLength("test", { min: 3, max: 6 }), true); + }, +});