refactor: use env bindings instead of globals for Cloudflare Workers

Replace global declarations with Env interface and access environment
variables through context.environmentBindings for proper CF Workers
module format compatibility.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Kyle Fang
2025-12-07 14:45:35 +08:00
parent 82e95eb60e
commit 39f183e921

View File

@@ -14,14 +14,11 @@ import { get } from "./get";
// @ts-ignore // @ts-ignore
import indexHtml from './index.html'; import indexHtml from './index.html';
declare global { interface Env {
const BOT_TOKEN: string; BOT_TOKEN: string;
WEBHOOK_PREFIX: string;
const WEBHOOK_PREFIX: string; WEBHOOK_PASSWORD: string;
TG_GROUPS: KVNamespace;
const WEBHOOK_PASSWORD: string;
const TG_GROUPS: KVNamespace;
} }
// Your code here, but do not `bot.launch()` // Your code here, but do not `bot.launch()`
@@ -34,24 +31,25 @@ router.get("/", (context) => {
}); });
router.post("/bot", async (context) => { router.post("/bot", async (context) => {
const env = context.environmentBindings as Env;
// Check for authentication password in query parameter // Check for authentication password in query parameter
const password = context.req.url.searchParams.get('password'); const password = context.req.url.searchParams.get('password');
const expectedPassword = WEBHOOK_PASSWORD;
if (!password || password !== expectedPassword) { if (!password || password !== env.WEBHOOK_PASSWORD) {
context.res.status = 401; context.res.status = 401;
context.res.body = { ok: false, error: "Unauthorized" }; context.res.body = { ok: false, error: "Unauthorized" };
return; return;
} }
const result: Update = await context.req.body.json(); const result: Update = await context.req.body.json();
await processUpdate(result); await processUpdate(result, env);
console.log(JSON.stringify(result, null, 2)); console.log(JSON.stringify(result, null, 2));
context.res.body = { ok: true }; context.res.body = { ok: true };
}); });
router.post("/t/:webhookId/raw", async (context) => { router.post("/t/:webhookId/raw", async (context) => {
const chat = await TG_GROUPS.get( const env = context.environmentBindings as Env;
const chat = await env.TG_GROUPS.get(
`webhook-chat:${context.req.params.webhookId}` `webhook-chat:${context.req.params.webhookId}`
); );
if (chat == null) { if (chat == null) {
@@ -60,14 +58,15 @@ router.post("/t/:webhookId/raw", async (context) => {
return; return;
} }
const result = await context.req.body.text(); const result = await context.req.body.text();
await sendToChat(JSON.parse(chat), result, { await sendToChat(env, JSON.parse(chat), result, {
parseMode: context.req.url.searchParams.get('parseMode') as 'HTML' | undefined, parseMode: context.req.url.searchParams.get('parseMode') as 'HTML' | undefined,
}); });
context.res.body = { ok: true }; context.res.body = { ok: true };
}); });
router.post("/t/:webhookId/file", async (context) => { router.post("/t/:webhookId/file", async (context) => {
const chat = await TG_GROUPS.get( const env = context.environmentBindings as Env;
const chat = await env.TG_GROUPS.get(
`webhook-chat:${context.req.params.webhookId}` `webhook-chat:${context.req.params.webhookId}`
); );
if (chat == null) { if (chat == null) {
@@ -76,12 +75,13 @@ router.post("/t/:webhookId/file", async (context) => {
return; return;
} }
const result = await context.req.body.json(); const result = await context.req.body.json();
await uploadFileToChat(JSON.parse(chat), result.fileName, result.content); await uploadFileToChat(env, JSON.parse(chat), result.fileName, result.content);
context.res.body = { ok: true }; context.res.body = { ok: true };
}); });
router.post("/t/:webhookId", async (context) => { router.post("/t/:webhookId", async (context) => {
const chat = await TG_GROUPS.get( const env = context.environmentBindings as Env;
const chat = await env.TG_GROUPS.get(
`webhook-chat:${context.req.params.webhookId}` `webhook-chat:${context.req.params.webhookId}`
); );
if (chat == null) { if (chat == null) {
@@ -90,7 +90,7 @@ router.post("/t/:webhookId", async (context) => {
return; return;
} }
const result: RichMessage = await context.req.body.json(); const result: RichMessage = await context.req.body.json();
await sendToChat(JSON.parse(chat), formatRichMessage(result), { await sendToChat(env, JSON.parse(chat), formatRichMessage(result), {
parseMode: "HTML", parseMode: "HTML",
disableNotification: result.notify === false, disableNotification: result.notify === false,
}); });
@@ -98,7 +98,8 @@ router.post("/t/:webhookId", async (context) => {
}); });
router.get("/t/:webhookId", async (context) => { router.get("/t/:webhookId", async (context) => {
const chat = await TG_GROUPS.get( const env = context.environmentBindings as Env;
const chat = await env.TG_GROUPS.get(
`webhook-chat:${context.req.params.webhookId}` `webhook-chat:${context.req.params.webhookId}`
); );
if (chat == null) { if (chat == null) {
@@ -117,7 +118,7 @@ router.get("/t/:webhookId", async (context) => {
metadata: search.get("metadata") ? JSON.parse(search.get("metadata") || "{}") : undefined, metadata: search.get("metadata") ? JSON.parse(search.get("metadata") || "{}") : undefined,
}; };
await sendToChat(JSON.parse(chat), formatRichMessage(result), { await sendToChat(env, JSON.parse(chat), formatRichMessage(result), {
parseMode: "HTML", parseMode: "HTML",
disableNotification: result.notify === false, disableNotification: result.notify === false,
}); });
@@ -125,7 +126,8 @@ router.get("/t/:webhookId", async (context) => {
}); });
router.post("/t/:webhookId/map", async (context) => { router.post("/t/:webhookId/map", async (context) => {
const chat = await TG_GROUPS.get( const env = context.environmentBindings as Env;
const chat = await env.TG_GROUPS.get(
`webhook-chat:${context.req.params.webhookId}` `webhook-chat:${context.req.params.webhookId}`
); );
if (chat == null) { if (chat == null) {
@@ -163,7 +165,7 @@ router.post("/t/:webhookId/map", async (context) => {
]) ])
), ),
}; };
await sendToChat(JSON.parse(chat), formatRichMessage(result), { await sendToChat(env, JSON.parse(chat), formatRichMessage(result), {
parseMode: "HTML", parseMode: "HTML",
disableNotification: result.notify === false, disableNotification: result.notify === false,
}); });
@@ -193,7 +195,7 @@ export default {
app.handleRequest(request, env, ctx), app.handleRequest(request, env, ctx),
}; };
async function processUpdate(update: Update): Promise<void> { async function processUpdate(update: Update, env: Env): Promise<void> {
const message = update.message ?? update.channel_post; const message = update.message ?? update.channel_post;
if (message == null) { if (message == null) {
return; return;
@@ -209,7 +211,7 @@ async function processUpdate(update: Update): Promise<void> {
chatId: oldChatId, chatId: oldChatId,
messageThreadId: message.message_thread_id, messageThreadId: message.message_thread_id,
}); });
const webhookId = await TG_GROUPS.get(`chat-webhook:${oldChat}`); const webhookId = await env.TG_GROUPS.get(`chat-webhook:${oldChat}`);
if (webhookId) { if (webhookId) {
// Update the stored chat info with new chat ID // Update the stored chat info with new chat ID
@@ -217,11 +219,11 @@ async function processUpdate(update: Update): Promise<void> {
chatId: newChatId, chatId: newChatId,
messageThreadId: message.message_thread_id, messageThreadId: message.message_thread_id,
}); });
await TG_GROUPS.put(`chat-webhook:${newChat}`, webhookId); await env.TG_GROUPS.put(`chat-webhook:${newChat}`, webhookId);
await TG_GROUPS.put(`webhook-chat:${webhookId}`, newChat); await env.TG_GROUPS.put(`webhook-chat:${webhookId}`, newChat);
// Delete old mapping // Delete old mapping
await TG_GROUPS.delete(`chat-webhook:${oldChat}`); await env.TG_GROUPS.delete(`chat-webhook:${oldChat}`);
} }
return; return;
} }
@@ -234,18 +236,19 @@ async function processUpdate(update: Update): Promise<void> {
}; };
const chatKey = JSON.stringify(chat); const chatKey = JSON.stringify(chat);
const key = `chat-webhook:${chatKey}`; const key = `chat-webhook:${chatKey}`;
const result = await TG_GROUPS.get(key); const result = await env.TG_GROUPS.get(key);
let webhookUrl: string; let webhookUrl: string;
if (result == null) { if (result == null) {
const uuid = crypto.randomUUID(); const uuid = crypto.randomUUID();
await TG_GROUPS.put(key, uuid); await env.TG_GROUPS.put(key, uuid);
await TG_GROUPS.put(`webhook-chat:${uuid}`, chatKey); await env.TG_GROUPS.put(`webhook-chat:${uuid}`, chatKey);
webhookUrl = `${WEBHOOK_PREFIX}/t/${uuid}`; webhookUrl = `${env.WEBHOOK_PREFIX}/t/${uuid}`;
} else { } else {
await TG_GROUPS.put(`webhook-chat:${result}`, chatKey); await env.TG_GROUPS.put(`webhook-chat:${result}`, chatKey);
webhookUrl = `${WEBHOOK_PREFIX}/t/${result}`; webhookUrl = `${env.WEBHOOK_PREFIX}/t/${result}`;
} }
await sendToChat( await sendToChat(
env,
chat, chat,
`<code>${webhookUrl}</code> `<code>${webhookUrl}</code>
@@ -272,6 +275,7 @@ async function processUpdate(update: Update): Promise<void> {
} }
async function sendToChat( async function sendToChat(
env: Env,
chat: { chat: {
chatId: number; chatId: number;
messageThreadId?: number; messageThreadId?: number;
@@ -282,7 +286,7 @@ async function sendToChat(
disableNotification?: boolean; disableNotification?: boolean;
} }
): Promise<void> { ): Promise<void> {
await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, { await fetch(`https://api.telegram.org/bot${env.BOT_TOKEN}/sendMessage`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -298,6 +302,7 @@ async function sendToChat(
} }
async function uploadFileToChat( async function uploadFileToChat(
env: Env,
chat: { chat: {
chatId: number; chatId: number;
messageThreadId?: number; messageThreadId?: number;
@@ -305,7 +310,7 @@ async function uploadFileToChat(
fileName: string, fileName: string,
content: string content: string
) { ) {
await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendDocument`, { await fetch(`https://api.telegram.org/bot${env.BOT_TOKEN}/sendDocument`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "multipart/form-data; boundary=WebAppBoundary", "Content-Type": "multipart/form-data; boundary=WebAppBoundary",