From d746622681cc981684861b1c2e1631ac7770738a Mon Sep 17 00:00:00 2001 From: Skillz4Killz <23035000+Skillz4Killz@users.noreply.github.com> Date: Tue, 13 Apr 2021 16:45:39 +0000 Subject: [PATCH 1/2] feat: add validateLength util --- src/helpers/messages/send_message.ts | 3 +- src/helpers/webhooks/create_webhook.ts | 5 ++- src/util/utils.ts | 50 +++++++++++++------------- src/util/validate_length.ts | 14 ++++++++ tests/mod.ts | 1 + tests/util/validate_length.ts | 44 +++++++++++++++++++++++ 6 files changed, 88 insertions(+), 29 deletions(-) create mode 100644 src/util/validate_length.ts create mode 100644 tests/util/validate_length.ts 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..81e301768 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()); @@ -34,10 +35,11 @@ export function delay(ms: number): Promise { export const formatImageURL = ( url: string, size: DiscordImageSize = 128, - format?: DiscordImageFormat, + format?: DiscordImageFormat ) => { - return `${url}.${format || - (url.includes("/a_") ? "gif" : "jpg")}?size=${size}`; + return `${url}.${ + format || (url.includes("/a_") ? "gif" : "jpg") + }?size=${size}`; }; function camelToSnakeCase(text: string) { @@ -45,22 +47,23 @@ function camelToSnakeCase(text: string) { } function snakeToCamelCase(text: string) { - return text.replace( - /([-_][a-z])/gi, - ($1) => $1.toUpperCase().replace("_", ""), + return text.replace(/([-_][a-z])/gi, ($1) => + $1.toUpperCase().replace("_", "") ); } 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) ); } export function camelKeysToSnakeCase( // deno-lint-ignore no-explicit-any - obj: Record | Record[], + obj: Record | Record[] ): T { if (isConvertableObject(obj)) { // deno-lint-ignore no-explicit-any @@ -69,11 +72,11 @@ export function camelKeysToSnakeCase( Object.keys(obj).forEach((key) => { eventHandlers.debug?.( "loop", - `Running forEach loop in camelKeysToSnakeCase function.`, + `Running forEach loop in camelKeysToSnakeCase function.` ); convertedObject[camelToSnakeCase(key)] = camelKeysToSnakeCase( // deno-lint-ignore no-explicit-any - (obj as Record)[key], + (obj as Record)[key] ); }); @@ -87,7 +90,7 @@ export function camelKeysToSnakeCase( export function snakeKeysToCamelCase( // deno-lint-ignore no-explicit-any - obj: Record | Record[], + obj: Record | Record[] ): T { if (isConvertableObject(obj)) { // deno-lint-ignore no-explicit-any @@ -96,11 +99,11 @@ export function snakeKeysToCamelCase( Object.keys(obj).forEach((key) => { eventHandlers.debug?.( "loop", - `Running forEach loop in snakeKeysToCamelCase function.`, + `Running forEach loop in snakeKeysToCamelCase function.` ); convertedObject[snakeToCamelCase(key)] = snakeKeysToCamelCase( // deno-lint-ignore no-explicit-any - (obj as Record)[key], + (obj as Record)[key] ); }); @@ -115,14 +118,14 @@ export function snakeKeysToCamelCase( /** @private */ function validateSlashOptionChoices( choices: ApplicationCommandOptionChoice[], - optionType: DiscordApplicationCommandOptionTypes, + optionType: DiscordApplicationCommandOptionTypes ) { for (const choice of choices) { eventHandlers.debug?.( "loop", - `Running for of loop in validateSlashOptionChoices function.`, + `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); } @@ -144,7 +147,7 @@ function validateSlashOptions(options: ApplicationCommandOption[]) { for (const option of options) { eventHandlers.debug?.( "loop", - `Running for of loop in validateSlashOptions function.`, + `Running for of loop in validateSlashOptions function.` ); if ( option.choices?.length && @@ -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); } @@ -172,12 +173,12 @@ function validateSlashOptions(options: ApplicationCommandOption[]) { export function validateSlashCommands( commands: (CreateGlobalApplicationCommand | EditGlobalApplicationCommand)[], - create = false, + create = false ) { for (const command of commands) { eventHandlers.debug?.( "loop", - `Running for of loop in validateSlashCommands function.`, + `Running for of loop in validateSlashCommands function.` ); if ( (command.name && !SLASH_COMMANDS_NAME_REGEX.test(command.name)) || @@ -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..5ae79cc4e --- /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); + }, +}); From b610d06c5f96731bdfe01f78e347efe8c43b2eb2 Mon Sep 17 00:00:00 2001 From: Skillz4Killz Date: Tue, 13 Apr 2021 16:46:32 +0000 Subject: [PATCH 2/2] --- src/util/utils.ts | 34 +++++++++++++++++----------------- src/util/validate_length.ts | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/util/utils.ts b/src/util/utils.ts index 81e301768..6a2b9a240 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -35,11 +35,10 @@ export function delay(ms: number): Promise { export const formatImageURL = ( url: string, size: DiscordImageSize = 128, - format?: DiscordImageFormat + format?: DiscordImageFormat, ) => { - return `${url}.${ - format || (url.includes("/a_") ? "gif" : "jpg") - }?size=${size}`; + return `${url}.${format || + (url.includes("/a_") ? "gif" : "jpg")}?size=${size}`; }; function camelToSnakeCase(text: string) { @@ -47,8 +46,9 @@ function camelToSnakeCase(text: string) { } function snakeToCamelCase(text: string) { - return text.replace(/([-_][a-z])/gi, ($1) => - $1.toUpperCase().replace("_", "") + return text.replace( + /([-_][a-z])/gi, + ($1) => $1.toUpperCase().replace("_", ""), ); } @@ -63,7 +63,7 @@ function isConvertableObject(obj: unknown) { export function camelKeysToSnakeCase( // deno-lint-ignore no-explicit-any - obj: Record | Record[] + obj: Record | Record[], ): T { if (isConvertableObject(obj)) { // deno-lint-ignore no-explicit-any @@ -72,11 +72,11 @@ export function camelKeysToSnakeCase( Object.keys(obj).forEach((key) => { eventHandlers.debug?.( "loop", - `Running forEach loop in camelKeysToSnakeCase function.` + `Running forEach loop in camelKeysToSnakeCase function.`, ); convertedObject[camelToSnakeCase(key)] = camelKeysToSnakeCase( // deno-lint-ignore no-explicit-any - (obj as Record)[key] + (obj as Record)[key], ); }); @@ -90,7 +90,7 @@ export function camelKeysToSnakeCase( export function snakeKeysToCamelCase( // deno-lint-ignore no-explicit-any - obj: Record | Record[] + obj: Record | Record[], ): T { if (isConvertableObject(obj)) { // deno-lint-ignore no-explicit-any @@ -99,11 +99,11 @@ export function snakeKeysToCamelCase( Object.keys(obj).forEach((key) => { eventHandlers.debug?.( "loop", - `Running forEach loop in snakeKeysToCamelCase function.` + `Running forEach loop in snakeKeysToCamelCase function.`, ); convertedObject[snakeToCamelCase(key)] = snakeKeysToCamelCase( // deno-lint-ignore no-explicit-any - (obj as Record)[key] + (obj as Record)[key], ); }); @@ -118,12 +118,12 @@ export function snakeKeysToCamelCase( /** @private */ function validateSlashOptionChoices( choices: ApplicationCommandOptionChoice[], - optionType: DiscordApplicationCommandOptionTypes + optionType: DiscordApplicationCommandOptionTypes, ) { for (const choice of choices) { eventHandlers.debug?.( "loop", - `Running for of loop in validateSlashOptionChoices function.` + `Running for of loop in validateSlashOptionChoices function.`, ); if (!validateLength(choice.name, { min: 1, max: 100 })) { throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES); @@ -147,7 +147,7 @@ function validateSlashOptions(options: ApplicationCommandOption[]) { for (const option of options) { eventHandlers.debug?.( "loop", - `Running for of loop in validateSlashOptions function.` + `Running for of loop in validateSlashOptions function.`, ); if ( option.choices?.length && @@ -173,12 +173,12 @@ function validateSlashOptions(options: ApplicationCommandOption[]) { export function validateSlashCommands( commands: (CreateGlobalApplicationCommand | EditGlobalApplicationCommand)[], - create = false + create = false, ) { for (const command of commands) { eventHandlers.debug?.( "loop", - `Running for of loop in validateSlashCommands function.` + `Running for of loop in validateSlashCommands function.`, ); if ( (command.name && !SLASH_COMMANDS_NAME_REGEX.test(command.name)) || diff --git a/src/util/validate_length.ts b/src/util/validate_length.ts index 5ae79cc4e..0bd1d852d 100644 --- a/src/util/validate_length.ts +++ b/src/util/validate_length.ts @@ -1,7 +1,7 @@ /** 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 } + options: { max?: number; min?: number }, ) { const length = [...text].length;