diff --git a/biome.json b/biome.json index 88627a6a..7d5f322b 100644 --- a/biome.json +++ b/biome.json @@ -29,7 +29,8 @@ "noParameterAssign": "off" }, "suspicious": { - "noArrayIndexKey": "off" + "noArrayIndexKey": "off", + "noExplicitAny": "warn" } } } diff --git a/bun.lockb b/bun.lockb index 79fb99bf..9ec96b7a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/gil/__tests__/bot_sqlite/index.ts b/packages/gil/__tests__/bot_sqlite/index.ts index a0a46eb7..7911871f 100644 --- a/packages/gil/__tests__/bot_sqlite/index.ts +++ b/packages/gil/__tests__/bot_sqlite/index.ts @@ -4,15 +4,14 @@ import { GilClient } from "../../lib/GilClient"; import { BetterSQLite3Adapter } from "../../lib/adapters/db/BetterSQLite3Adapter"; import sqlite from "better-sqlite3"; -import pino from "pino"; -import { PinoAdapter } from "../../lib"; +import { ConsoleAdapter } from "../../lib"; const database = sqlite("test.db"); const YokiBot = new GilClient({ token: process.env.TOKEN!, commandDirectory: join(__dirname, "..", "shared", "commands"), listenerDirectory: join(__dirname, "listeners"), - loggingAdapter: new PinoAdapter(pino({ base: null })), + loggingAdapter: new ConsoleAdapter(), databaseAdapter: new BetterSQLite3Adapter({ sqliteInstance: database, serverTable: "servers", diff --git a/packages/gil/__tests__/shared/commands/Help.ts b/packages/gil/__tests__/shared/commands/Help.ts index 45776ee3..9d435634 100644 --- a/packages/gil/__tests__/shared/commands/Help.ts +++ b/packages/gil/__tests__/shared/commands/Help.ts @@ -1,20 +1,18 @@ -import { Command, CommandExecuteContext, GilClient } from "../../../lib"; +import { Command, CommandExecuteContext, CommandOptions, GilClient } from "../../../lib"; export default class Help extends Command { - public constructor(client: GilClient) { - super(client, { - name: "help", - description: "Shows this help message.", - aliases: ["h"], - args: [ - { - name: "command", - optional: true, - type: "string", - }, - ], - }); - } + options = { + name: "help", + description: "Shows this help message.", + aliases: ["h"], + args: [ + { + name: "command", + optional: true, + type: "string", + }, + ], + } satisfies CommandOptions; public async execute(ctx: CommandExecuteContext<{ command: string }>) { const { command } = ctx.args; diff --git a/packages/gil/__tests__/shared/commands/Ping.ts b/packages/gil/__tests__/shared/commands/Ping.ts index 5281aae0..45bd82fc 100644 --- a/packages/gil/__tests__/shared/commands/Ping.ts +++ b/packages/gil/__tests__/shared/commands/Ping.ts @@ -1,10 +1,11 @@ -import { Command } from "../../../lib"; +import { Command, CommandOptions, StoredRoleType } from "../../../lib"; export default class Ping extends Command { options = { name: "ping", description: "Tests the bot.", - }; + userRole: StoredRoleType.Admin, + } satisfies CommandOptions; public async execute() { return "Pong!"; diff --git a/packages/gil/lib/GilClient.ts b/packages/gil/lib/GilClient.ts index eb8f803e..449501ff 100644 --- a/packages/gil/lib/GilClient.ts +++ b/packages/gil/lib/GilClient.ts @@ -1,5 +1,5 @@ import EventEmitter from "node:events"; -import { Client, ClientOptions } from "guilded.js"; +import { Client, ClientOptions, Message } from "guilded.js"; import TypedEmitter from "typed-emitter"; import { DatabaseAdapter } from "./adapters/db/DatabaseAdapter"; import { ConsoleAdapter } from "./adapters/logging/ConsoleAdapter"; @@ -7,6 +7,7 @@ import { LoggerAdapter } from "./adapters/logging/LoggerAdapter"; import { GilEvents } from "./events"; import { CommandManager } from "./structures/Command"; import { ListenerManager } from "./structures/ListenerManager"; +import { DefaultResponseParams, defaultResponses } from "./structures/Responses"; import { TaskManager } from "./structures/Task"; import { CommandCustomContextFn, CommandErrorHandler } from "./types"; @@ -19,6 +20,7 @@ interface GilClientOptions { errorHandler?: { command: CommandErrorHandler; }; + responses?: typeof defaultResponses; // adapters loggingAdapter?: LoggerAdapter; databaseAdapter: DatabaseAdapter; @@ -36,11 +38,15 @@ export class GilClient { token: this.options.token, }); public readonly emitter = new EventEmitter() as TypedEmitter; - public readonly logger = this.options.loggingAdapter ?? new ConsoleAdapter(); - public readonly db = this.options.databaseAdapter; public readonly commands = new CommandManager(this); public readonly listeners = new ListenerManager(this); public readonly tasks = new TaskManager(this); + public readonly logger = this.options.loggingAdapter ?? new ConsoleAdapter(); + public readonly db = this.options.databaseAdapter; + public readonly responses = { + ...defaultResponses, + ...(this.options.responses ?? {}), + }; public constructor(public options: GilClientOptions) { if (!options.token) throw new Error("No token provided"); @@ -57,6 +63,19 @@ export class GilClient { await this.client.login(); } + public send( + channel: { channelId: string }, + key: T, + options?: { + args: DefaultResponseParams[T]["0"]; + }, + ): Promise { + const response = this.responses[key]; + const createdResponse = options?.args ? response(options.args as any) : response({} as any); + + return this.client.messages.send(channel.channelId, createdResponse); + } + private hookClientInternals() { this.client.ws.emitter.on("error", (reason, err, data) => { console.log(reason, err, data); diff --git a/packages/gil/lib/listeners/CommandMessageListener.ts b/packages/gil/lib/listeners/CommandMessageListener.ts index f658be59..bd37df73 100644 --- a/packages/gil/lib/listeners/CommandMessageListener.ts +++ b/packages/gil/lib/listeners/CommandMessageListener.ts @@ -1,4 +1,3 @@ -import { Message, Role } from "guilded.js"; import * as lexure from "lexure"; import { GilClient } from "../GilClient"; import { StoredRole, StoredRoleType } from "../adapters/db/DatabaseAdapter"; @@ -41,6 +40,11 @@ export default class CommandMessageListener extends Listener { if (!permissionsCheck) { this.gil.logger.debug(`User does not have permissions for command ${name}`, params.message.id); + await this.gil.send(params.message, "userMissingRole", { + args: { + requiredRole: [command.options.userRole!], + }, + }); return; } @@ -50,7 +54,7 @@ export default class CommandMessageListener extends Listener { if (serverPremiumPriority < requiredMinimumPriority) { this.gil.logger.debug(`Server does not have the required premium level for command ${name}`, params.message.id); - // todo: allow user to put premium prompt message + await this.gil.send(params.message, "serverNotPremium"); return; } } diff --git a/packages/gil/lib/structures/Responses.ts b/packages/gil/lib/structures/Responses.ts new file mode 100644 index 00000000..81c937a7 --- /dev/null +++ b/packages/gil/lib/structures/Responses.ts @@ -0,0 +1,20 @@ +import { Embed, MessageContent } from "guilded.js"; + +export type Response = (...args: any[]) => MessageContent; +export type ParamsObject = T extends (...args: infer P) => any ? { [K in keyof P]: P[K] } : never; +export type DefaultResponseParams = { + [K in keyof typeof defaultResponses]: ParamsObject<(typeof defaultResponses)[K]>; +}; + +export const defaultResponses = { + serverNotPremium: (p: { tier: string }) => + new Embed().setTitle("This server is not premium").setDescription(`This server does not have premium. To use this command, the server must be on the ${p.tier} tier.`), + 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.`), + noop: () => "", +} as const; + +export const inlineCode = (str: string) => { + return `\`${str}\``; +};