From 8e9053cd4e15e079320b95875c54208457f71eda Mon Sep 17 00:00:00 2001 From: Kyle Fang Date: Mon, 1 May 2023 13:39:29 +0800 Subject: [PATCH] feat: add basic implementation --- package.json | 3 +- pnpm-lock.yaml | 34 ++++++++-------- src/index.ts | 103 ++++++++++++++++++++++++++++++++++++++----------- src/message.ts | 22 +++++++++++ 4 files changed, 121 insertions(+), 41 deletions(-) create mode 100644 src/message.ts diff --git a/package.json b/package.json index 1fd3369..8391c8e 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "version": "0.0.0", "devDependencies": { "@cloudflare/workers-types": "^4.20230404.0", + "@grammyjs/types": "^3.1.1", + "prettier": "^2.8.8", "typescript": "^5.0.4", "vitest": "^0.30.1", "wrangler": "2.17.0" @@ -15,7 +17,6 @@ }, "dependencies": { "@cfworker/web": "^1.12.5", - "cfworker-middleware-telegraf": "^2.0.2", "telegraf": "^4.12.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0a8a10..8bd0b93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,6 @@ dependencies: '@cfworker/web': specifier: ^1.12.5 version: 1.12.5 - cfworker-middleware-telegraf: - specifier: ^2.0.2 - version: 2.0.2(@cfworker/web@1.12.5)(telegraf@4.12.2) telegraf: specifier: ^4.12.2 version: 4.12.2 @@ -15,6 +12,12 @@ devDependencies: '@cloudflare/workers-types': specifier: ^4.20230404.0 version: 4.20230404.0 + '@grammyjs/types': + specifier: ^3.1.1 + version: 3.1.1 + prettier: + specifier: ^2.8.8 + version: 2.8.8 typescript: specifier: ^5.0.4 version: 5.0.4 @@ -480,6 +483,10 @@ packages: dev: true optional: true + /@grammyjs/types@3.1.1: + resolution: {integrity: sha512-nTNYvk+Al/nXZAMTEzPYEyW6pfgYgNuf/gxfx17kCxjHpAYhV+pcmROuD9eOBDLaBDh/hZJkQTIY6S3CfTAC0Q==} + dev: true + /@iarna/toml@2.2.5: resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} dev: true @@ -741,10 +748,6 @@ packages: event-target-shim: 5.0.1 dev: false - /abortcontroller-polyfill@1.7.5: - resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} - dev: false - /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} @@ -835,17 +838,6 @@ packages: engines: {node: '>=8'} dev: true - /cfworker-middleware-telegraf@2.0.2(@cfworker/web@1.12.5)(telegraf@4.12.2): - resolution: {integrity: sha512-3tj4R60AyTioaxS88shO4AYcL4N6QLPEeU5kflF5zIXHc4EI3yzbZLUEphe7/ExTVVrLJI9IwPP3JfOUZltiJw==} - peerDependencies: - '@cfworker/web': ^1.12.0 - telegraf: ^4.6.0 - dependencies: - '@cfworker/web': 1.12.5 - abortcontroller-polyfill: 1.7.5 - telegraf: 4.12.2 - dev: false - /chai@4.3.7: resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} engines: {node: '>=4'} @@ -1378,6 +1370,12 @@ packages: source-map-js: 1.0.2 dev: true + /prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} diff --git a/src/index.ts b/src/index.ts index b61c0ef..65707fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,34 +7,93 @@ * * Learn more at https://developers.cloudflare.com/workers/ */ -import {Telegraf} from "telegraf"; -import {Application, Router} from "@cfworker/web"; -import createTelegrafMiddleware from 'cfworker-middleware-telegraf' - -export interface Env { - // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ - TG_GROUPS: KVNamespace; - // - // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ - // MY_DURABLE_OBJECT: DurableObjectNamespace; - // - // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ - // MY_BUCKET: R2Bucket; - // - // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ - // MY_SERVICE: Fetcher; -} +import { Application, Router } from "@cfworker/web"; +import type { Update } from "@grammyjs/types"; +import { formatRichMessage, RichMessage } from "./message"; declare global { - const BOT_TOKEN: string - const SECRET_PATH: string -} + const BOT_TOKEN: string; -const bot = new Telegraf(BOT_TOKEN); + const WEBHOOK_PREFIX: string; + + const TG_GROUPS: KVNamespace; +} // Your code here, but do not `bot.launch()` // Do not forget to set environment variables BOT_TOKEN and SECRET_PATH on your worker const router = new Router(); -router.post(`/${SECRET_PATH}`, createTelegrafMiddleware(bot)); +router.post("/bot", async (context) => { + const result: Update = await context.req.body.json(); + await processUpdate(result); + console.log(JSON.stringify(result, null, 2)); + context.res.body = { ok: true }; +}); + +router.post("/t/:webhookId/raw", async (context) => { + const chatId = await TG_GROUPS.get( + `webhook-chat:${context.req.params.webhookId}` + ); + if (chatId == null) { + context.res.body = { ok: false, error: "chatId not found" }; + context.res.status = 404; + } + const result = await context.req.body.text(); + await sendToChat(Number(chatId), result); + context.res.body = { ok: true }; +}); + +router.post("/t/:webhookId", async (context) => { + const chatId = await TG_GROUPS.get( + `webhook-chat:${context.req.params.webhookId}` + ); + if (chatId == null) { + context.res.body = { ok: false, error: "chatId not found" }; + context.res.status = 404; + } + const result: RichMessage = await context.req.body.json(); + await sendToChat(Number(chatId), formatRichMessage(result), "HTML"); + context.res.body = { ok: true }; +}); + new Application().use(router.middleware).listen(); + +async function processUpdate(update: Update): Promise { + if (update.message == null) { + return; + } + if (update.message.text === "/webhook") { + const chatId = update.message.chat.id; + const key = `chat-webhook:${chatId}`; + const result = await TG_GROUPS.get(key); + let webhookUrl: string; + if (result == null) { + const uuid = crypto.randomUUID(); + await TG_GROUPS.put(key, uuid); + await TG_GROUPS.put(`webhook-chat:${uuid}`, chatId.toString()); + webhookUrl = `${WEBHOOK_PREFIX}/t/${uuid}`; + } else { + await TG_GROUPS.put(`webhook-chat:${result}`, chatId.toString()); + webhookUrl = `${WEBHOOK_PREFIX}/t/${result}`; + } + await sendToChat(chatId, webhookUrl); + } +} + +async function sendToChat( + chatId: number, + text: string, + parseMode?: "HTML" +): Promise { + await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + chat_id: chatId, + text, + parse_mode: parseMode, + }), + }); +} diff --git a/src/message.ts b/src/message.ts new file mode 100644 index 0000000..e15af7e --- /dev/null +++ b/src/message.ts @@ -0,0 +1,22 @@ +export type RichMessage = { + topic: string; + event: string; + text?: string; + emoji?: string; + metadata?: Record; +}; + +export function formatRichMessage(message: RichMessage): string { + const metadata = Object.entries(message.metadata ?? {}) + .map(([key, value]) => `#${key}: ${value}`) + .join("\n"); + return `${message.emoji ? message.emoji + " • " : ""}#${ + message.topic + } + +${message.event}${message.text ? ` + +${message.text}` : ""} + +${metadata}`; +}