Skip to content

Commit

Permalink
fix: extend on client class
Browse files Browse the repository at this point in the history
  • Loading branch information
Jabolol committed Sep 12, 2023
1 parent 72d0411 commit d589cc3
Showing 1 changed file with 29 additions and 89 deletions.
118 changes: 29 additions & 89 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import "reflect-metadata";
import nacl from "nacl";
import "$std/dotenv/load.ts";
import {
DiscordInteraction,
DiscordInteractionResponse,
InteractionResponseTypes,
InteractionTypes,
} from "discordeno";
import { Command } from "./decorators.ts";

export class App {
import { Client, Command } from "~/utils.ts";

class App extends Client {
constructor() {
super();
}

@Command("hello")
hello(interaction: DiscordInteraction): DiscordInteractionResponse {
return {
Expand All @@ -19,97 +23,33 @@ export class App {
};
}

isValidBody(
body: string | { error: string; status: number },
): body is string {
return typeof body === "string";
}

async validateRequest(request: Request) {
const REQUIRED_HEADERS = ["X-Signature-Ed25519", "X-Signature-Timestamp"];
if (request.method !== "POST") {
return { error: "Method not allowed", status: 405 };
}
if (!REQUIRED_HEADERS.every((header) => request.headers.has(header))) {
return { error: "Missing headers", status: 400 };
}
const { valid, body } = await this.verifySignature(request);
if (!valid) {
return { error: "Invalid signature", status: 401 };
}
return body;
}

handleInteraction(
@Command("placeholder")
async placeholder(
interaction: DiscordInteraction,
): DiscordInteractionResponse | { error: string; status: number } {
switch (interaction.type) {
case InteractionTypes.Ping: {
return {
type: InteractionResponseTypes.Pong,
};
}
case InteractionTypes.ApplicationCommand: {
const command:
| ((i: DiscordInteraction) => DiscordInteractionResponse)
| undefined = Reflect.getMetadata(
`command:${interaction.data!.name}`,
this,
);
if (!command) {
return { error: "Command not found", status: 404 };
}
return command(interaction);
}
default: {
return { error: "Command not found", status: 404 };
}
}
}
): Promise<DiscordInteractionResponse> {
const req = await fetch(
`https://jsonplaceholder.typicode.com/todos/${interaction.data?.options
?.find(({ name }) => name === "id")?.value}`,
);

bootstrap() {
const count = Reflect.getMetadata(`total`, this) || 0;
if (count === 0) {
throw "No commands registered";
if (!req.ok) {
return {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
content: `Something went wrong: \`${req.status}\``,
},
};
}

return async (request: Request) => {
const body = await this.validateRequest(request);
if (!this.isValidBody(body)) {
const { error, status } = body;
return new Response(JSON.stringify({ error }), { status });
}
const interaction: DiscordInteraction = JSON.parse(body);
const response = this.handleInteraction(interaction);

return new Response(
JSON.stringify(response),
{
headers: { "content-type": "application/json" },
},
);
return {
type: InteractionResponseTypes.ChannelMessageWithSource,
data: {
content: `\`\`\`json\n${
JSON.stringify(await req.json(), null, 2)
}\`\`\``,
},
};
}

async verifySignature(request: Request) {
const PUBLIC_KEY = Deno.env.get("DISCORD_PUBLIC_KEY")!;
const signature = request.headers.get("X-Signature-Ed25519")!;
const timestamp = request.headers.get("X-Signature-Timestamp")!;
const body = await request.text();
const valid = nacl.sign.detached.verify(
new TextEncoder().encode(timestamp + body),
this.hexToUint8Array(signature),
this.hexToUint8Array(PUBLIC_KEY),
);

return { valid, body };
}

hexToUint8Array(hex: string) {
return new Uint8Array(
hex.match(/.{1,2}/g)!.map((val) => parseInt(val, 16)),
);
}
}

const handler = new App().bootstrap();
Expand Down

0 comments on commit d589cc3

Please sign in to comment.