Skip to content

Commit 49ddc63

Browse files
committed
Include an icon for scheduled webhook messages
1 parent c013fb6 commit 49ddc63

11 files changed

+82
-11
lines changed

.dev.vars.example

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ DISCORD_CLIENT_SECRET=<discord_client_secret>
44
# DISCORD_GUILD_ID=<discord_guild_id>
55
# DISCORD_SUMMARY_WEBHOOK=<discord_webhook_url>
66
# DISCORD_MILESTONE_WEBHOOK=<discord_webhook_url>
7+
# WORKER_BASE_URL=<worker_base_url>
78

89
STATS_API_ENDPOINT=https://dashboard.jinglejam.co.uk/api/tiltify
910
# STATS_API_ENDPOINT=https://develop.jingle-jam-tracker.pages.dev/api/tiltify

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Ensure that the environment in `wrangler.toml` has been updated with your chosen
2323

2424
Ensure that the KV namespaces are created for staging/production environments and are configured in `wrangler.toml`. Use `npx wrangler kv:namespace create "STORE" -e <staging/production>`.
2525

26-
You'll also want to set `DISCORD_PUBLIC_KEY` + `STATS_API_ENDPOINT` + `DISCORD_SUMMARY_WEBHOOK` + `DISCORD_MILESTONE_WEBHOOK` as secrets for the worker, which you can do with `npx wrangler secret put <var name> -e <staging/production>` (the webhook secrets can contain multiple webhooks, separated by a comma).
26+
You'll also want to set `DISCORD_CLIENT_ID` + `DISCORD_PUBLIC_KEY` + `STATS_API_ENDPOINT` (optionally, `DISCORD_SUMMARY_WEBHOOK` + `DISCORD_MILESTONE_WEBHOOK` + `WORKER_BASE_URL`) as secrets for the worker, which you can do with `npx wrangler secret put <var name> -e <staging/production>` (the webhook secrets can contain multiple webhooks, separated by a comma).
2727

2828
If you're deploying for local, make sure that you've got the appropriate environment variables set for `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET` + `DISCORD_GUILD_ID` (otherwise, they'll default to the values in `.dev.vars`).
2929

src/assets/assets.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
declare module "*.png" {
2+
const content: Uint8Array;
3+
export default content;
4+
}
5+
6+
declare module "*.svg" {
7+
const content: string;
8+
export default content;
9+
}

src/assets/icon.png

51.1 KB
Loading

src/assets/icon.svg

+16
Loading

src/env.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
export interface Env {
2+
DISCORD_CLIENT_ID: string;
23
DISCORD_PUBLIC_KEY: string;
34
STATS_API_ENDPOINT: string;
45
DISCORD_SUMMARY_WEBHOOK?: string;
56
DISCORD_MILESTONE_WEBHOOK?: string;
7+
WORKER_BASE_URL?: string;
68
STORE: KVNamespace;
79
}
810

src/index.ts

+25
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import totalCommand from "./commands/total";
55
import causesCommand from "./commands/causes";
66
import summaryScheduled from "./scheduled/summary";
77
import milestoneScheduled from "./scheduled/milestone";
8+
import iconAssetPNG from "./assets/icon.png";
9+
import iconAssetSVG from "./assets/icon.svg";
810
import type { CtxWithEnv, Env } from "./env";
911

1012
let handler: ReturnType<typeof createHandler<CtxWithEnv>>;
@@ -24,6 +26,29 @@ const worker: ExportedHandler<Env> = {
2426
const resp = await handler(request, ctx as CtxWithEnv);
2527
if (resp) return resp;
2628

29+
// Parse the URL
30+
const url = new URL(request.url);
31+
32+
// Provide a direct link to invite the app
33+
if (request.method === "GET" && url.pathname === "/invite") {
34+
return Response.redirect(
35+
`https://discord.com/oauth2/authorize?client_id=${env.DISCORD_CLIENT_ID}&scope=applications.commands`,
36+
302,
37+
);
38+
}
39+
40+
// Serve the icon for the scheduled webhook messages
41+
if (request.method === "GET" && url.pathname === "/icon.png") {
42+
return new Response(iconAssetPNG, {
43+
headers: { "Content-Type": "image/png" },
44+
});
45+
}
46+
if (request.method === "GET" && url.pathname === "/icon.svg") {
47+
return new Response(iconAssetSVG, {
48+
headers: { "Content-Type": "image/svg+xml" },
49+
});
50+
}
51+
2752
// Fallback for any requests not handled by the handler
2853
return new Response("Not found", { status: 404 });
2954
},

src/scheduled/milestone.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,13 @@ const milestoneScheduled = async (
6161
ctx.waitUntil(
6262
Promise.all(
6363
webhooks.map((webhook) =>
64-
sendWebhook(webhook, { content }).catch(console.error),
64+
sendWebhook(webhook, {
65+
content,
66+
username: "JingleBot",
67+
avatar_url: env.WORKER_BASE_URL
68+
? `${env.WORKER_BASE_URL}/icon.png`
69+
: undefined,
70+
}).catch(console.error),
6571
),
6672
),
6773
);

src/scheduled/summary.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,13 @@ const summaryScheduled = async (
8484
ctx.waitUntil(
8585
Promise.all(
8686
webhooks.map((webhook) =>
87-
sendWebhook(webhook, { content }).catch(console.error),
87+
sendWebhook(webhook, {
88+
content,
89+
username: "JingleBot",
90+
avatar_url: env.WORKER_BASE_URL
91+
? `${env.WORKER_BASE_URL}/icon.png`
92+
: undefined,
93+
}).catch(console.error),
8894
),
8995
),
9096
);

src/util/webhook.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,13 @@ const sendWebhook = async (
88
const url = new URL(hook);
99
url.searchParams.set("wait", "true");
1010

11-
// Force a standard username for the webhook
12-
const payload: RESTPostAPIWebhookWithTokenJSONBody = {
13-
...data,
14-
username: "JingleBot",
15-
// TODO: avatar_url
16-
};
17-
1811
// Make the request and check for any errors
1912
const res = await fetch(url, {
2013
method: "POST",
2114
headers: {
2215
"Content-Type": "application/json",
2316
},
24-
body: JSON.stringify(payload),
17+
body: JSON.stringify(data),
2518
});
2619

2720
if (!res.ok) {

tsup.config.ts

+13
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ import causesCommand from "./src/commands/causes";
99
dotenv.config({ path: ".dev.vars" });
1010

1111
export default defineConfig({
12+
// Generate a single ESM file for the worker
13+
// Include type definitions so we check them when building
14+
// Include source maps to help with debugging in development
1215
entry: ["src/index.ts"],
1316
format: ["esm"],
1417
dts: true,
1518
sourcemap: true,
1619
clean: true,
1720
outDir: "dist",
1821
outExtension: () => ({ js: ".js" }),
22+
// Register the commands once the worker is built
1923
onSuccess: async () => {
2024
await registerCommands(
2125
process.env.DISCORD_CLIENT_ID!,
@@ -25,4 +29,13 @@ export default defineConfig({
2529
process.env.DISCORD_GUILD_ID,
2630
);
2731
},
32+
// Allow importing of PNG and SVG files
33+
// Set platform to browser so esbuild uses `atob`, not `Buffer`
34+
loader: {
35+
".png": "binary",
36+
".svg": "text",
37+
},
38+
esbuildOptions: (options) => {
39+
options.platform = "browser";
40+
},
2841
});

0 commit comments

Comments
 (0)