feat: add contexts

This commit is contained in:
xHyroM 2023-04-07 21:51:25 +02:00
parent 05aa790303
commit 6cf73a07a5
No known key found for this signature in database
GPG key ID: BE0423F386C436AA
12 changed files with 131 additions and 57 deletions

View file

@ -5,6 +5,7 @@
"scripts": { "scripts": {
"build": "node scripts/build.mjs", "build": "node scripts/build.mjs",
"dev": "miniflare --watch --debug --port 8787", "dev": "miniflare --watch --debug --port 8787",
"dev:tunnel": "cloudflared tunnel --url localhost:8787/",
"deploy": "cross-env NODE_ENV=production wrangler publish" "deploy": "cross-env NODE_ENV=production wrangler publish"
}, },
"keywords": [], "keywords": [],

View file

@ -1,15 +1,10 @@
import { InteractionResponseType } from "discord-api-types/v10";
import { Command } from "../structs/Command"; import { Command } from "../structs/Command";
import respond from "../utils/respond";
new Command({ new Command({
name: "setup", name: "setup",
run: async () => { run: async (ctx) => {
return respond({ await ctx.editReply({
type: InteractionResponseType.ChannelMessageWithSource, content: "Setup",
data: {
content: "Setup",
},
}); });
}, },
}); });

View file

@ -9,17 +9,19 @@ import {
import { COMMANDS, COMPONENTS } from "./registers"; import { COMMANDS, COMPONENTS } from "./registers";
import { verify } from "./utils/verify"; import { verify } from "./utils/verify";
import respond from "./utils/respond"; import respond from "./utils/respond";
import { CommandContext } from "./structs/contexts/CommandContext";
import { ComponentContext } from "./structs/contexts/ComponentContext";
console.log(COMMANDS); console.log(COMMANDS);
export default { export default {
fetch: async (request: Request) => { fetch: async (request: Request, env: Env) => {
if ( if (
!request.headers.get("X-Signature-Ed25519") || !request.headers.get("X-Signature-Ed25519") ||
!request.headers.get("X-Signature-Timestamp") !request.headers.get("X-Signature-Timestamp")
) )
return Response.redirect("https://xhyrom.dev"); return Response.redirect("https://xhyrom.dev");
if (!(await verify(request))) if (!(await verify(request, env)))
return new Response("Invalid request signature", { status: 401 }); return new Response("Invalid request signature", { status: 401 });
const interaction = (await request.json()) as const interaction = (await request.json()) as
@ -38,7 +40,14 @@ export default {
); );
if (!command) return new Response("Unknown command", { status: 404 }); if (!command) return new Response("Unknown command", { status: 404 });
return command.run(interaction);
try {
return respond({
type: InteractionResponseType.DeferredChannelMessageWithSource,
});
} finally {
command.run(new CommandContext(interaction, env));
}
} }
const component = COMPONENTS.find( const component = COMPONENTS.find(
@ -46,6 +55,13 @@ export default {
); );
if (!component) return new Response("Unknown component", { status: 404 }); if (!component) return new Response("Unknown component", { status: 404 });
return component.run(interaction);
try {
return respond({
type: InteractionResponseType.DeferredChannelMessageWithSource,
});
} finally {
component.run(new ComponentContext(interaction, env));
}
}, },
}; };

View file

@ -1,16 +1,14 @@
import { APIApplicationCommandInteraction } from "discord-api-types/v10";
import { registerCommand } from "../registers"; import { registerCommand } from "../registers";
import { CommandContext } from "./contexts/CommandContext";
interface CommandOptions { interface CommandOptions {
name: string; name: string;
run: (interaction: APIApplicationCommandInteraction) => Promise<Response>; run: (interaction: CommandContext) => void;
} }
export class Command { export class Command {
public name: string; public name: string;
public run: ( public run: (interaction: CommandContext) => void;
interaction: APIApplicationCommandInteraction,
) => Promise<Response>;
constructor(options: CommandOptions) { constructor(options: CommandOptions) {
this.name = options.name; this.name = options.name;

View file

@ -1,16 +1,14 @@
import { APIMessageComponentInteraction } from "discord-api-types/v10";
import { registerComponent } from "../registers"; import { registerComponent } from "../registers";
import { ComponentContext } from "./contexts/ComponentContext";
interface ComponentOptions { interface ComponentOptions {
id: string; id: string;
run: (interaction: APIMessageComponentInteraction) => Promise<Response>; run: (interaction: ComponentContext) => void;
} }
export class Component { export class Component {
public id: string; public id: string;
public run: ( public run: (interaction: ComponentContext) => void;
interaction: APIMessageComponentInteraction,
) => Promise<Response>;
constructor(options: ComponentOptions) { constructor(options: ComponentOptions) {
this.id = options.id; this.id = options.id;

View file

@ -0,0 +1,12 @@
import { APIApplicationCommandInteraction } from "discord-api-types/v10";
import { Context } from "./Context";
export class CommandContext extends Context {
public interaction: APIApplicationCommandInteraction;
constructor(interaction: APIApplicationCommandInteraction, env: Env) {
super(interaction, env);
this.interaction = interaction;
}
}

View file

@ -0,0 +1,12 @@
import { APIMessageComponentInteraction } from "discord-api-types/v10";
import { Context } from "./Context";
export class ComponentContext extends Context {
public interaction: APIMessageComponentInteraction;
constructor(interaction: APIMessageComponentInteraction, env: Env) {
super(interaction, env);
this.interaction = interaction;
}
}

View file

@ -0,0 +1,33 @@
import {
APIInteraction,
APIInteractionResponseCallbackData,
RouteBases,
Routes,
} from "discord-api-types/v10";
export class Context {
public interaction: APIInteraction;
public env: Env;
constructor(interaction: APIInteraction, env: Env) {
this.interaction = interaction;
this.env = env;
}
public async editReply(content: APIInteractionResponseCallbackData) {
return await fetch(
`${RouteBases.api}${Routes.webhookMessage(
this.interaction.application_id,
this.interaction.token,
)}`,
{
method: "PATCH",
body: JSON.stringify(content),
headers: {
"Content-Type": "application/json",
Authorization: `Bot ${this.env.token}`,
},
},
);
}
}

View file

@ -1,3 +1,7 @@
// secrets: wrangler secret put <name> // secrets: wrangler secret put <name>
declare const publicKey: string; declare let MINIFLARE; // just check because algorithm is different
declare const token: string;
declare interface Env {
publicKey: string;
token: string;
}

View file

@ -10,28 +10,28 @@ function hex2bin(hex: string) {
return buf; return buf;
} }
const PUBLIC_KEY = crypto.subtle.importKey(
"raw",
hex2bin(publicKey),
{
name: "NODE-ED25519",
namedCurve: "NODE-ED25519",
},
true,
["verify"],
);
const encoder = new TextEncoder(); const encoder = new TextEncoder();
export async function verify(request: Request) { export async function verify(request: Request, env: Env) {
const subtle = await crypto.subtle.importKey(
"raw",
hex2bin(env.publicKey),
{
name: typeof MINIFLARE !== "undefined" ? "Ed25519" : "NODE-ED25519",
namedCurve: typeof MINIFLARE !== "undefined" ? "Ed25519" : "NODE-ED25519",
},
true,
["verify"],
);
// rome-ignore lint/style/noNonNullAssertion: its fine // rome-ignore lint/style/noNonNullAssertion: its fine
const signature = hex2bin(request.headers.get("X-Signature-Ed25519")!); const signature = hex2bin(request.headers.get("X-Signature-Ed25519")!);
const timestamp = request.headers.get("X-Signature-Timestamp"); const timestamp = request.headers.get("X-Signature-Timestamp");
const unknown = await request.clone().text(); const unknown = await request.clone().text();
return await crypto.subtle.verify( return await crypto.subtle.verify(
"NODE-ED25519", typeof MINIFLARE !== "undefined" ? "Ed25519" : "NODE-ED25519",
await PUBLIC_KEY, subtle,
signature, signature,
encoder.encode(timestamp + unknown), encoder.encode(timestamp + unknown),
); );

View file

@ -3,6 +3,8 @@ type = "javascript"
account_id = "294bee38d448e390dab3757215c63f03" account_id = "294bee38d448e390dab3757215c63f03"
compatibility_date = "2022-07-12" compatibility_date = "2022-07-12"
main = "dist/worker.mjs"
[build] [build]
command = "pnpm build" command = "pnpm build"
[build.upload] [build.upload]

View file

@ -1,21 +1,24 @@
{ {
"$schema": "https://turbo.build/schema.json", "$schema": "https://turbo.build/schema.json",
"pipeline": { "pipeline": {
"build": { "build": {
"dependsOn": ["^build"], "dependsOn": ["^build"],
"outputs": ["dist"] "outputs": ["dist"]
}, },
"dev": { "dev": {
"outputs": ["dist"] "outputs": ["dist"]
}, },
"preview": { "dev:tunnel": {
"outputs": ["dist"] "outputs": ["dist"]
}, },
"deploy": { "preview": {
"dependsOn": ["build", "test", "lint"] "outputs": ["dist"]
}, },
"test": { "deploy": {
"dependsOn": ["^build"] "dependsOn": ["build", "test", "lint"]
} },
} "test": {
"dependsOn": ["^build"]
}
}
} }