feat: add basic implementation

This commit is contained in:
Kyle Fang
2023-05-01 13:39:29 +08:00
parent 10e3fe17ce
commit 8e9053cd4e
4 changed files with 121 additions and 41 deletions

View File

@@ -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"
}
}

34
pnpm-lock.yaml generated
View File

@@ -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}

View File

@@ -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<void> {
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<void> {
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,
}),
});
}

22
src/message.ts Normal file
View File

@@ -0,0 +1,22 @@
export type RichMessage = {
topic: string;
event: string;
text?: string;
emoji?: string;
metadata?: Record<string, string>;
};
export function formatRichMessage(message: RichMessage): string {
const metadata = Object.entries(message.metadata ?? {})
.map(([key, value]) => `#${key}: ${value}`)
.join("\n");
return `${message.emoji ? message.emoji + " • " : ""}<ins>#${
message.topic
}</ins>
<b>${message.event}</b>${message.text ? `
<code>${message.text}</code>` : ""}
${metadata}`;
}