feat: add validateLength util

This commit is contained in:
Skillz4Killz
2021-04-13 16:45:39 +00:00
committed by GitHub
parent 16a5ac684a
commit d746622681
6 changed files with 88 additions and 29 deletions
+2 -1
View File
@@ -10,6 +10,7 @@ import { PermissionStrings } from "../../types/permissions/permission_strings.ts
import { endpoints } from "../../util/constants.ts"; import { endpoints } from "../../util/constants.ts";
import { requireBotChannelPermissions } from "../../util/permissions.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts";
import { camelKeysToSnakeCase } from "../../util/utils.ts"; import { camelKeysToSnakeCase } from "../../util/utils.ts";
import { validateLength } from "../../util/validate_length.ts";
/** Send a message to the channel. Requires SEND_MESSAGES permission. */ /** Send a message to the channel. Requires SEND_MESSAGES permission. */
export async function sendMessage( export async function sendMessage(
@@ -48,7 +49,7 @@ export async function sendMessage(
} }
// Use ... for content length due to unicode characters and js .length handling // 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); throw new Error(Errors.MESSAGE_MAX_LENGTH);
} }
+2 -3
View File
@@ -6,6 +6,7 @@ import { DiscordWebhook } from "../../types/webhooks/webhook.ts";
import { endpoints } from "../../util/constants.ts"; import { endpoints } from "../../util/constants.ts";
import { requireBotChannelPermissions } from "../../util/permissions.ts"; import { requireBotChannelPermissions } from "../../util/permissions.ts";
import { snakeKeysToCamelCase, urlToBase64 } from "../../util/utils.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: * 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 ( if (
// Specific usernames that discord does not allow // Specific usernames that discord does not allow
options.name === "clyde" || options.name === "clyde" ||
// Character limit checks. [...] checks are because of js unicode length handling !validateLength(options.name, { min: 2, max: 32 })
[...options.name].length < 2 ||
[...options.name].length > 32
) { ) {
throw new Error(Errors.INVALID_WEBHOOK_NAME); throw new Error(Errors.INVALID_WEBHOOK_NAME);
} }
+25 -25
View File
@@ -9,6 +9,7 @@ import { DiscordImageFormat } from "../types/misc/image_format.ts";
import { DiscordImageSize } from "../types/misc/image_size.ts"; import { DiscordImageSize } from "../types/misc/image_size.ts";
import { EditGlobalApplicationCommand } from "../types/mod.ts"; import { EditGlobalApplicationCommand } from "../types/mod.ts";
import { SLASH_COMMANDS_NAME_REGEX } from "./constants.ts"; import { SLASH_COMMANDS_NAME_REGEX } from "./constants.ts";
import { validateLength } from "./validate_length.ts";
export async function urlToBase64(url: string) { export async function urlToBase64(url: string) {
const buffer = await fetch(url).then((res) => res.arrayBuffer()); const buffer = await fetch(url).then((res) => res.arrayBuffer());
@@ -34,10 +35,11 @@ export function delay(ms: number): Promise<void> {
export const formatImageURL = ( export const formatImageURL = (
url: string, url: string,
size: DiscordImageSize = 128, size: DiscordImageSize = 128,
format?: DiscordImageFormat, format?: DiscordImageFormat
) => { ) => {
return `${url}.${format || return `${url}.${
(url.includes("/a_") ? "gif" : "jpg")}?size=${size}`; format || (url.includes("/a_") ? "gif" : "jpg")
}?size=${size}`;
}; };
function camelToSnakeCase(text: string) { function camelToSnakeCase(text: string) {
@@ -45,22 +47,23 @@ function camelToSnakeCase(text: string) {
} }
function snakeToCamelCase(text: string) { function snakeToCamelCase(text: string) {
return text.replace( return text.replace(/([-_][a-z])/gi, ($1) =>
/([-_][a-z])/gi, $1.toUpperCase().replace("_", "")
($1) => $1.toUpperCase().replace("_", ""),
); );
} }
function isConvertableObject(obj: unknown) { function isConvertableObject(obj: unknown) {
return ( return (
obj === Object(obj) && !Array.isArray(obj) && typeof obj !== "function" && obj === Object(obj) &&
!Array.isArray(obj) &&
typeof obj !== "function" &&
!(obj instanceof Blob) !(obj instanceof Blob)
); );
} }
export function camelKeysToSnakeCase<T>( export function camelKeysToSnakeCase<T>(
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
obj: Record<string, any> | Record<string, any>[], obj: Record<string, any> | Record<string, any>[]
): T { ): T {
if (isConvertableObject(obj)) { if (isConvertableObject(obj)) {
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
@@ -69,11 +72,11 @@ export function camelKeysToSnakeCase<T>(
Object.keys(obj).forEach((key) => { Object.keys(obj).forEach((key) => {
eventHandlers.debug?.( eventHandlers.debug?.(
"loop", "loop",
`Running forEach loop in camelKeysToSnakeCase function.`, `Running forEach loop in camelKeysToSnakeCase function.`
); );
convertedObject[camelToSnakeCase(key)] = camelKeysToSnakeCase( convertedObject[camelToSnakeCase(key)] = camelKeysToSnakeCase(
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
(obj as Record<string, any>)[key], (obj as Record<string, any>)[key]
); );
}); });
@@ -87,7 +90,7 @@ export function camelKeysToSnakeCase<T>(
export function snakeKeysToCamelCase<T>( export function snakeKeysToCamelCase<T>(
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
obj: Record<string, any> | Record<string, any>[], obj: Record<string, any> | Record<string, any>[]
): T { ): T {
if (isConvertableObject(obj)) { if (isConvertableObject(obj)) {
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
@@ -96,11 +99,11 @@ export function snakeKeysToCamelCase<T>(
Object.keys(obj).forEach((key) => { Object.keys(obj).forEach((key) => {
eventHandlers.debug?.( eventHandlers.debug?.(
"loop", "loop",
`Running forEach loop in snakeKeysToCamelCase function.`, `Running forEach loop in snakeKeysToCamelCase function.`
); );
convertedObject[snakeToCamelCase(key)] = snakeKeysToCamelCase( convertedObject[snakeToCamelCase(key)] = snakeKeysToCamelCase(
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
(obj as Record<string, any>)[key], (obj as Record<string, any>)[key]
); );
}); });
@@ -115,14 +118,14 @@ export function snakeKeysToCamelCase<T>(
/** @private */ /** @private */
function validateSlashOptionChoices( function validateSlashOptionChoices(
choices: ApplicationCommandOptionChoice[], choices: ApplicationCommandOptionChoice[],
optionType: DiscordApplicationCommandOptionTypes, optionType: DiscordApplicationCommandOptionTypes
) { ) {
for (const choice of choices) { for (const choice of choices) {
eventHandlers.debug?.( eventHandlers.debug?.(
"loop", "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); throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
} }
@@ -144,7 +147,7 @@ function validateSlashOptions(options: ApplicationCommandOption[]) {
for (const option of options) { for (const option of options) {
eventHandlers.debug?.( eventHandlers.debug?.(
"loop", "loop",
`Running for of loop in validateSlashOptions function.`, `Running for of loop in validateSlashOptions function.`
); );
if ( if (
option.choices?.length && option.choices?.length &&
@@ -156,10 +159,8 @@ function validateSlashOptions(options: ApplicationCommandOption[]) {
} }
if ( if (
[...option.name].length < 1 || !validateLength(option.name, { min: 1, max: 32 }) ||
[...option.name].length > 32 || !validateLength(option.description, { min: 1, max: 100 })
[...option.description].length < 1 ||
[...option.description].length > 100
) { ) {
throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES); throw new Error(Errors.INVALID_SLASH_OPTIONS_CHOICES);
} }
@@ -172,12 +173,12 @@ function validateSlashOptions(options: ApplicationCommandOption[]) {
export function validateSlashCommands( export function validateSlashCommands(
commands: (CreateGlobalApplicationCommand | EditGlobalApplicationCommand)[], commands: (CreateGlobalApplicationCommand | EditGlobalApplicationCommand)[],
create = false, create = false
) { ) {
for (const command of commands) { for (const command of commands) {
eventHandlers.debug?.( eventHandlers.debug?.(
"loop", "loop",
`Running for of loop in validateSlashCommands function.`, `Running for of loop in validateSlashCommands function.`
); );
if ( if (
(command.name && !SLASH_COMMANDS_NAME_REGEX.test(command.name)) || (command.name && !SLASH_COMMANDS_NAME_REGEX.test(command.name)) ||
@@ -188,8 +189,7 @@ export function validateSlashCommands(
if ( if (
(command.description && (command.description &&
([...command.description].length < 1 || !validateLength(command.description, { min: 1, max: 100 })) ||
[...command.description].length > 100)) ||
(create && !command.description) (create && !command.description)
) { ) {
throw new Error(Errors.INVALID_SLASH_DESCRIPTION); throw new Error(Errors.INVALID_SLASH_DESCRIPTION);
+14
View File
@@ -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;
}
+1
View File
@@ -4,6 +4,7 @@
// First complete non-api reliant testing. // First complete non-api reliant testing.
// Don't waste api rate limits if a early test fails. // Don't waste api rate limits if a early test fails.
import "./util/utils.ts"; import "./util/utils.ts";
import "./util/validate_length.ts";
// API TESTING BELOW // API TESTING BELOW
+44
View File
@@ -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);
},
});