From d41a26896f5870654713b85b0bb63b73516c808b Mon Sep 17 00:00:00 2001 From: "Zaid Arshad (Nico)" Date: Tue, 9 Apr 2024 17:36:17 -0400 Subject: [PATCH] feat: more responses --- packages/gil/lib/arguments/ArgumentParser.ts | 4 +- .../lib/listeners/CommandMessageListener.ts | 7 +- packages/gil/lib/structures/Command.ts | 4 +- packages/gil/lib/structures/Responses.ts | 92 +++++++++++++++++++ packages/gil/lib/utils/string.ts | 14 +++ 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 packages/gil/lib/utils/string.ts diff --git a/packages/gil/lib/arguments/ArgumentParser.ts b/packages/gil/lib/arguments/ArgumentParser.ts index ea755d98..4938ff7c 100644 --- a/packages/gil/lib/arguments/ArgumentParser.ts +++ b/packages/gil/lib/arguments/ArgumentParser.ts @@ -8,7 +8,7 @@ import number from "./args/number"; import role from "./args/role"; import string from "./args/string"; -export type Result = ({ error: false } & T) | { error: true; reason_code: string }; +export type Result = ({ error: false } & T) | { error: true; reason_code: string; extra_info?: unknown }; export type CommandArgument = string | number | boolean | PartialMember | Message | Channel | Role | null; export type CommandArgumentType = "string" | "number" | "boolean" | "member" | "channel" | "role"; export type CommandArgumentValidator = { @@ -55,6 +55,8 @@ export async function convertArguments(params: { castedArguments[currentArg.name] = null; continue; } + + return { error: true, reason_code: "MISSING_ARGUMENT", extra_info: { argument: currentArg } }; } const validator = validators[currentArg.type].validate; diff --git a/packages/gil/lib/listeners/CommandMessageListener.ts b/packages/gil/lib/listeners/CommandMessageListener.ts index 0a6cd7e5..98d02722 100644 --- a/packages/gil/lib/listeners/CommandMessageListener.ts +++ b/packages/gil/lib/listeners/CommandMessageListener.ts @@ -73,7 +73,12 @@ export default class CommandMessageListener extends Listener { if (attemptConvertArguments.error) { this.gil.logger.debug(`Error converting arguments for command ${name}, reason: ${attemptConvertArguments.reason_code}`, params.message.id); - // TODO: in-depth error messages for users + await this.gil.send(params.message, "invalidArguments", { + args: { + reason_code: attemptConvertArguments.reason_code, + extra_info: attemptConvertArguments.extra_info, + }, + }); return; } diff --git a/packages/gil/lib/structures/Command.ts b/packages/gil/lib/structures/Command.ts index 38bbe4db..3c8bf3b3 100644 --- a/packages/gil/lib/structures/Command.ts +++ b/packages/gil/lib/structures/Command.ts @@ -13,7 +13,7 @@ export interface CommandOptions { // A brief description of the command description?: string; // The arguments this command takes - args?: { name: string; type: CommandArgumentType; optional?: boolean }[]; + args?: CommandArg[]; // The category the command belongs to category?: string; // The command's aliases @@ -37,6 +37,8 @@ export interface CommandOptions { // The premium level the user must have to run this command premiumUserLevel?: string; } +export type CommandArg = { name: string; type: CommandArgumentType; optional?: boolean }; + export abstract class Command { public constructor( public readonly gil: GilClient, diff --git a/packages/gil/lib/structures/Responses.ts b/packages/gil/lib/structures/Responses.ts index 81c937a7..e30db2b0 100644 --- a/packages/gil/lib/structures/Responses.ts +++ b/packages/gil/lib/structures/Responses.ts @@ -1,4 +1,6 @@ import { Embed, MessageContent } from "guilded.js"; +import { strip } from "../utils/string"; +import { CommandArg } from "./Command"; export type Response = (...args: any[]) => MessageContent; export type ParamsObject = T extends (...args: infer P) => any ? { [K in keyof P]: P[K] } : never; @@ -12,6 +14,96 @@ export const defaultResponses = { userNotPremium: (p: { tier: string }) => new Embed().setTitle("You are not premium").setDescription(`You do not have premium. To use this command, you must be on the ${p.tier} tier.`), userMissingRole: (p: { requiredRole: string[] }) => new Embed().setTitle("You can't run this!").setDescription(`You do not have a role with the ${inlineCode(p.requiredRole.join(", "))} permission.`), + invalidArguments: (p: { reason_code: string; extra_info: unknown }) => { + const embed = new Embed().setTitle("Invalid Usage!").setColor("RED"); + + switch (p.reason_code) { + case "MISSING_ARGUMENT": { + const extra_info = p.extra_info as { argument: CommandArg }; + embed.setDescription(strip` + You are missing the required argument: ${inlineCode(extra_info.argument.name)}. + + It should be of type ${inlineCode(extra_info.argument.type)}. + `); + break; + } + case "INVALID_NUMBER": + embed.setDescription("I was unable to understand the number you provided.\n\nPlease ensure your numbers are formatted like so: `123`, `111`, or `1e4`. Do not include commands or decimals."); + break; + case "NUMBER_OUT_OF_RANGE": + embed.setDescription("The number you provided is out of acceptable range.\n\nPlease make sure your number is between `-2,147,483,648` and `2,147,483,647`."); + break; + case "BAD_STRING": + embed.setDescription("You provided an invalid string."); + break; + case "INVALID_MEMBER_INPUT": + embed.setDescription(strip` + I was expecting a mention or ID of a user. It may look something like this: \`@user\` or \`pmbOB8VA\` + + This user **must** currently be in the server. + Don't know how to get IDs? Refer to this [Guilded Post](https://support.guilded.gg/hc/en-us/articles/6183962129303-Developer-mode#:~:text=Once%20you've%20enabled%20Developer,by%20right%2Dclicking%20on%20it.). + `); + break; + case "MEMBER_NOT_FOUND": + embed.setDescription(strip` + The user you provided was not found. It may look something like this: \`@user\` or \`pmbOB8VA\` + + This user **must** currently be in the server. + `); + break; + case "NO_USER_IN_MENTIONS": + embed.setDescription("You did not mention a user."); + break; + case "INVALID_ROLE_INPUT": + embed.setDescription(strip` + I was expecting the mention or ID of a role in this server. It may look something like this: \`@role\` or \`28086957\` + + Don't know how to get IDs? Refer to this [Guilded Post](https://support.guilded.gg/hc/en-us/articles/6183962129303-Developer-mode#:~:text=Once%20you've%20enabled%20Developer,by%20right%2Dclicking%20on%20it.). + `); + break; + case "ROLE_NOT_FOUND": + embed.setDescription(strip` + The role you provided was not found. It may look something like this: \`@role\` or \`28086957\` + + This role **must** exist in the server. + `); + break; + case "NO_ROLE_IN_MENTIONS": + embed.setDescription("You did not mention a role."); + break; + case "INVALID_CHANNEL_ETC": + embed.setDescription(strip` + I was expecting either a mention or ID of a channel. It may look something like this: \`#channel\` or \`8942a219-6fde-49f0-8d11-13974df4681c\` + + **The bot must have read, send, & manage permission on the channel** + Ensure **none** of the bot's roles deny these permissions. + + Don't know how to get IDs? Refer to this [Guilded Post](https://support.guilded.gg/hc/en-us/articles/6183962129303-Developer-mode#:~:text=Once%20you've%20enabled%20Developer,by%20right%2Dclicking%20on%20it.). + `); + break; + case "CHANNEL_NOT_FOUND": + embed.setDescription(strip` + The channel you provided was not found. It may look something like this: \`#channel\` or \`8942a219-6fde-49f0-8d11-13974df4681c\` + + This channel **must** exist in the server. + `); + break; + case "NO_CHANNEL_IN_MENTIONS": + embed.setDescription("You did not mention a channel."); + break; + case "INVALID_TIME": + embed.setDescription("You provided an invalid time. Please ensure your time is formatted like so: `1d`, `1h`, `1m`, or `1s`."); + break; + case "INVALID_BOOLEAN": + embed.setDescription("You provided an invalid boolean. Please ensure your input is either `true` or `false`."); + break; + default: + embed.setDescription("You provided invalid arguments."); + break; + } + + return embed; + }, noop: () => "", } as const; diff --git a/packages/gil/lib/utils/string.ts b/packages/gil/lib/utils/string.ts new file mode 100644 index 00000000..a667214e --- /dev/null +++ b/packages/gil/lib/utils/string.ts @@ -0,0 +1,14 @@ +export function strip(strings: TemplateStringsArray, ...values: any[]): string { + let slices: string[] = strings.map((x) => x); + if (strings[0] === "") { + slices = strings.slice(1); + } + const fullString = slices.reduce((acc, str, i) => acc + str + (values[i] || ""), ""); + const trimmedLines = fullString.split("\n").map((x) => x.trim()); + + if (trimmedLines[0] === "") { + trimmedLines.shift(); + } + + return trimmedLines.join("\n"); +}