diff --git a/packages/bot/package.json b/packages/bot/package.json index 009cd33..c0a3645 100644 --- a/packages/bot/package.json +++ b/packages/bot/package.json @@ -13,11 +13,13 @@ "devDependencies": { "@cloudflare/workers-types": "^3.17.0", "@types/jest": "^29.1.2", - "discord-api-types": "^0.37.14", "esbuild": "^0.15.11", "eslint": "^8.25.0", "jest": "^29.2.0", "miniflare": "^2.10.0", "typescript": "^4.8.4" + }, + "dependencies": { + "discord-api-types": "^0.37.37" } } diff --git a/packages/bot/src/commands/setup.ts b/packages/bot/src/commands/setup.ts index 848b119..8c57b16 100644 --- a/packages/bot/src/commands/setup.ts +++ b/packages/bot/src/commands/setup.ts @@ -1,5 +1,15 @@ +import { InteractionResponseType } from "discord-api-types/v10"; import { Command } from "../structs/Command"; +import respond from "../utils/respond"; new Command({ name: "setup", + run: async () => { + return respond({ + type: InteractionResponseType.ChannelMessageWithSource, + data: { + content: "Setup", + }, + }); + }, }); diff --git a/packages/bot/src/index.ts b/packages/bot/src/index.ts index 0349ffb..6f6755f 100644 --- a/packages/bot/src/index.ts +++ b/packages/bot/src/index.ts @@ -1,11 +1,51 @@ import "./commands/setup"; -import { COMMANDS } from "./registers"; +import { + APIApplicationCommandInteraction, + APIMessageComponentInteraction, + APIPingInteraction, + InteractionResponseType, + InteractionType, +} from "discord-api-types/v10"; +import { COMMANDS, COMPONENTS } from "./registers"; +import { verify } from "./utils/verify"; +import respond from "./utils/respond"; console.log(COMMANDS); export default { - fetch: (request: Request) => { - console.log(request); - return new Response("asda"); + fetch: async (request: Request) => { + if ( + !request.headers.get("X-Signature-Ed25519") || + !request.headers.get("X-Signature-Timestamp") + ) + return Response.redirect("https://xhyrom.dev"); + if (!(await verify(request))) + return new Response("Invalid request signature", { status: 401 }); + + const interaction = (await request.json()) as + | APIPingInteraction + | APIApplicationCommandInteraction + | APIMessageComponentInteraction; + + if (interaction.type === InteractionType.Ping) + return respond({ + type: InteractionResponseType.Pong, + }); + + if (interaction.type === InteractionType.ApplicationCommand) { + const command = COMMANDS.find( + (cmd) => cmd.name === interaction.data.name, + ); + + if (!command) return new Response("Unknown command", { status: 404 }); + return command.run(interaction); + } + + const component = COMPONENTS.find( + (cmp) => cmp.id === interaction.data.custom_id, + ); + + if (!component) return new Response("Unknown component", { status: 404 }); + return component.run(interaction); }, }; diff --git a/packages/bot/src/registers.ts b/packages/bot/src/registers.ts index 47cb39d..59b7fe0 100644 --- a/packages/bot/src/registers.ts +++ b/packages/bot/src/registers.ts @@ -1,13 +1,13 @@ import { Command } from "./structs/Command"; -import { Listener } from "./structs/Listener"; +import { Component } from "./structs/Component"; export const COMMANDS: Command[] = []; -export const LISTENERS: Listener[] = []; +export const COMPONENTS: Component[] = []; export const registerCommand = (command: Command) => { COMMANDS.push(command); }; -export const registerListener = (listener: Listener) => { - LISTENERS.push(listener); +export const registerComponent = (component: Component) => { + COMPONENTS.push(component); }; diff --git a/packages/bot/src/structs/Command.ts b/packages/bot/src/structs/Command.ts index 1763af1..7dee36d 100644 --- a/packages/bot/src/structs/Command.ts +++ b/packages/bot/src/structs/Command.ts @@ -1,14 +1,20 @@ +import { APIApplicationCommandInteraction } from "discord-api-types/v10"; import { registerCommand } from "../registers"; interface CommandOptions { name: string; + run: (interaction: APIApplicationCommandInteraction) => Promise; } export class Command { public name: string; + public run: ( + interaction: APIApplicationCommandInteraction, + ) => Promise; constructor(options: CommandOptions) { this.name = options.name; + this.run = options.run; registerCommand(this); } diff --git a/packages/bot/src/structs/Component.ts b/packages/bot/src/structs/Component.ts new file mode 100644 index 0000000..1348049 --- /dev/null +++ b/packages/bot/src/structs/Component.ts @@ -0,0 +1,21 @@ +import { APIMessageComponentInteraction } from "discord-api-types/v10"; +import { registerComponent } from "../registers"; + +interface ComponentOptions { + id: string; + run: (interaction: APIMessageComponentInteraction) => Promise; +} + +export class Component { + public id: string; + public run: ( + interaction: APIMessageComponentInteraction, + ) => Promise; + + constructor(options: ComponentOptions) { + this.id = options.id; + this.run = options.run; + + registerComponent(this); + } +} diff --git a/packages/bot/src/structs/Listener.ts b/packages/bot/src/structs/Listener.ts deleted file mode 100644 index c6b1ff2..0000000 --- a/packages/bot/src/structs/Listener.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { registerListener } from "../registers"; - -interface ListenerOptions { - name: string; - once: boolean | undefined; -} - -export class Listener { - public name: string; - public once: boolean; - - constructor(options: ListenerOptions) { - this.name = options.name; - this.once = options.once ?? false; - - registerListener(this); - } -} diff --git a/packages/bot/src/types.d.ts b/packages/bot/src/types.d.ts new file mode 100644 index 0000000..6369f0e --- /dev/null +++ b/packages/bot/src/types.d.ts @@ -0,0 +1,3 @@ +// secrets: wrangler secret put +declare const publicKey: string; +declare const token: string; diff --git a/packages/bot/src/utils/respond.ts b/packages/bot/src/utils/respond.ts new file mode 100644 index 0000000..868b00c --- /dev/null +++ b/packages/bot/src/utils/respond.ts @@ -0,0 +1,7 @@ +import { APIInteractionResponse } from "discord-api-types/v10"; + +export default function (response: APIInteractionResponse) { + return new Response(JSON.stringify(response), { + headers: { "content-type": "application/json" }, + }); +} diff --git a/packages/bot/src/utils/verify.ts b/packages/bot/src/utils/verify.ts new file mode 100644 index 0000000..2bf0bfb --- /dev/null +++ b/packages/bot/src/utils/verify.ts @@ -0,0 +1,38 @@ +// from https://gist.github.com/devsnek/77275f6e3f810a9545440931ed314dc1 + +"use strict"; + +function hex2bin(hex: string) { + const buf = new Uint8Array(Math.ceil(hex.length / 2)); + for (let i = 0; i < buf.length; i++) { + buf[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16); + } + return buf; +} + +const PUBLIC_KEY = crypto.subtle.importKey( + "raw", + hex2bin(publicKey), + { + name: "NODE-ED25519", + namedCurve: "NODE-ED25519", + }, + true, + ["verify"], +); + +const encoder = new TextEncoder(); + +export async function verify(request: Request) { + // rome-ignore lint/style/noNonNullAssertion: its fine + const signature = hex2bin(request.headers.get("X-Signature-Ed25519")!); + const timestamp = request.headers.get("X-Signature-Timestamp"); + const unknown = await request.clone().text(); + + return await crypto.subtle.verify( + "NODE-ED25519", + await PUBLIC_KEY, + signature, + encoder.encode(timestamp + unknown), + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb944bc..bae8ab0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,10 @@ importers: version: 12.0.0 packages/bot: + dependencies: + discord-api-types: + specifier: ^0.37.37 + version: 0.37.37 devDependencies: '@cloudflare/workers-types': specifier: ^3.17.0 @@ -16,9 +20,6 @@ importers: '@types/jest': specifier: ^29.1.2 version: 29.1.2 - discord-api-types: - specifier: ^0.37.14 - version: 0.37.14 esbuild: specifier: ^0.15.11 version: 0.15.11 @@ -2324,9 +2325,9 @@ packages: path-type: 4.0.0 dev: true - /discord-api-types@0.37.14: - resolution: {integrity: sha512-byBH7SfDCMJwxdqeS8k5sihltH88/YPhuwx+vF2cftSxFLdxyHyU/ZxDL3bq+LB2c4ls/TymE76/ISlLfniUXg==} - dev: true + /discord-api-types@0.37.37: + resolution: {integrity: sha512-LDMBKzl/zbvHO/yCzno5hevuA6lFIXJwdKSJZQrB+1ToDpFfN9thK+xxgZNR4aVkI7GHRDja0p4Sl2oYVPnHYg==} + dev: false /dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} diff --git a/rome.json b/rome.json index b946f01..20f5771 100644 --- a/rome.json +++ b/rome.json @@ -1,12 +1,15 @@ { - "$schema": "./node_modules/rome/configuration_schema.json", - "organizeImports": { - "enabled": false - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - } + "$schema": "./node_modules/rome/configuration_schema.json", + "organizeImports": { + "enabled": false + }, + "files": { + "ignore": ["dist/**"] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } }