Skip to content

Commit

Permalink
feat: more responses
Browse files Browse the repository at this point in the history
  • Loading branch information
zaida04 committed Apr 9, 2024
1 parent 1b4ad5a commit b8e15fe
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 9 deletions.
1 change: 0 additions & 1 deletion packages/gil/__tests__/shared/commands/Ping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export default class Ping extends Command {
options = {
name: "ping",
description: "Tests the bot.",
userRole: StoredRoleType.Admin,
} satisfies CommandOptions;

public async execute() {
Expand Down
5 changes: 4 additions & 1 deletion packages/gil/lib/GilClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { CommandCustomContextFn, CommandErrorHandler } from "./types";
interface GilClientOptions {
token: string;
clientOptions?: ClientOptions;
supportServer?: string;
contexts?: {
command: CommandCustomContextFn;
};
Expand All @@ -31,6 +32,7 @@ interface GilClientOptions {
// other
operators?: string[];
premiumPrioritys?: string[];
idGenerator?: () => string;
}
export class GilClient {
public readonly client = new Client({
Expand All @@ -47,6 +49,7 @@ export class GilClient {
...defaultResponses,
...(this.options.responses ?? {}),
};
public readonly idGenerator = this.options.idGenerator ?? (() => crypto.randomUUID());

public constructor(public options: GilClientOptions) {
if (!options.token) throw new Error("No token provided");
Expand All @@ -67,7 +70,7 @@ export class GilClient {
channel: { channelId: string },
key: T,
options?: {
args: DefaultResponseParams[T]["0"];
args: DefaultResponseParams[T][0];
},
): Promise<Message> {
const response = this.responses[key];
Expand Down
4 changes: 3 additions & 1 deletion packages/gil/lib/arguments/ArgumentParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import number from "./args/number";
import role from "./args/role";
import string from "./args/string";

export type Result<T> = ({ error: false } & T) | { error: true; reason_code: string };
export type Result<T> = ({ 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 = {
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 17 additions & 4 deletions packages/gil/lib/listeners/CommandMessageListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -98,14 +103,22 @@ export default class CommandMessageListener extends Listener {
...context,
});
} catch (e) {
// todo: user friendly error "something went wrong" message

this.gil.logger.error(e as Error);
const error_id = this.gil.idGenerator();

this.gil.send(params.message, "errorCommand", {
args: {
error_message: e instanceof Error ? e.message : "An unknown error occurred",
support_server: this.gil.options.supportServer,
error_id,
},
});
this.gil.logger.error(e as Error, error_id);
this.gil.logger.warn(`Error executing command ${name}`, params.message.id);
this.gil.options.errorHandler?.command?.(e as Error, {
message: params.message,
member: params.member,
server: params.server,
error_id,
command,
});
}
Expand Down
4 changes: 3 additions & 1 deletion packages/gil/lib/structures/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
101 changes: 101 additions & 0 deletions packages/gil/lib/structures/Responses.ts
Original file line number Diff line number Diff line change
@@ -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> = T extends (...args: infer P) => any ? { [K in keyof P]: P[K] } : never;
Expand All @@ -12,6 +14,105 @@ 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;
},
errorCommand: (p: { error_message: string; error_id: string; support_server?: string }) =>
new Embed()
.setTitle("An error occurred")
.setColor("RED")
.setDescription(strip`
There was an error while running this command. If this issue persists, please ${p.support_server ? `[report it here](${p.support_server})` : "report it to the bot owner"}.
When making your report, please provide this error ID: ${inlineCode(p.error_id)}
`),
noop: () => "",
} as const;

Expand Down
2 changes: 1 addition & 1 deletion packages/gil/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import type { StoredServer } from "./adapters/db/DatabaseAdapter";
import type { Command } from "./structures/Command";

export type CommandCustomContextFn = (data: { server: StoredServer; message: Message }) => Promise<Record<string, unknown>>;
export type CommandErrorHandler = (error: Error, additionalDetails: { message: Message; member: Member; server: StoredServer; command: Command }) => void;
export type CommandErrorHandler = (error: Error, additionalDetails: { message: Message; member: Member; server: StoredServer; command: Command; error_id: string }) => void;
14 changes: 14 additions & 0 deletions packages/gil/lib/utils/string.ts
Original file line number Diff line number Diff line change
@@ -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");
}

0 comments on commit b8e15fe

Please sign in to comment.