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": {
"build": "node scripts/build.mjs",
"dev": "miniflare --watch --debug --port 8787",
"dev:tunnel": "cloudflared tunnel --url localhost:8787/",
"deploy": "cross-env NODE_ENV=production wrangler publish"
},
"keywords": [],

View file

@ -1,15 +1,10 @@
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",
},
run: async (ctx) => {
await ctx.editReply({
content: "Setup",
});
},
});

View file

@ -9,17 +9,19 @@ import {
import { COMMANDS, COMPONENTS } from "./registers";
import { verify } from "./utils/verify";
import respond from "./utils/respond";
import { CommandContext } from "./structs/contexts/CommandContext";
import { ComponentContext } from "./structs/contexts/ComponentContext";
console.log(COMMANDS);
export default {
fetch: async (request: Request) => {
fetch: async (request: Request, env: Env) => {
if (
!request.headers.get("X-Signature-Ed25519") ||
!request.headers.get("X-Signature-Timestamp")
)
return Response.redirect("https://xhyrom.dev");
if (!(await verify(request)))
if (!(await verify(request, env)))
return new Response("Invalid request signature", { status: 401 });
const interaction = (await request.json()) as
@ -38,7 +40,14 @@ export default {
);
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(
@ -46,6 +55,13 @@ export default {
);
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 { CommandContext } from "./contexts/CommandContext";
interface CommandOptions {
name: string;
run: (interaction: APIApplicationCommandInteraction) => Promise<Response>;
run: (interaction: CommandContext) => void;
}
export class Command {
public name: string;
public run: (
interaction: APIApplicationCommandInteraction,
) => Promise<Response>;
public run: (interaction: CommandContext) => void;
constructor(options: CommandOptions) {
this.name = options.name;

View file

@ -1,16 +1,14 @@
import { APIMessageComponentInteraction } from "discord-api-types/v10";
import { registerComponent } from "../registers";
import { ComponentContext } from "./contexts/ComponentContext";
interface ComponentOptions {
id: string;
run: (interaction: APIMessageComponentInteraction) => Promise<Response>;
run: (interaction: ComponentContext) => void;
}
export class Component {
public id: string;
public run: (
interaction: APIMessageComponentInteraction,
) => Promise<Response>;
public run: (interaction: ComponentContext) => void;
constructor(options: ComponentOptions) {
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>
declare const publicKey: string;
declare const token: string;
declare let MINIFLARE; // just check because algorithm is different
declare interface Env {
publicKey: string;
token: string;
}

View file

@ -10,28 +10,28 @@ function hex2bin(hex: string) {
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) {
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
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,
typeof MINIFLARE !== "undefined" ? "Ed25519" : "NODE-ED25519",
subtle,
signature,
encoder.encode(timestamp + unknown),
);

View file

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

View file

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