feat: add telegram mini app (#250)

This commit is contained in:
Dream Hunter
2024-05-19 00:35:10 +08:00
committed by GitHub
parent 46576316e6
commit 870b7b9198
12 changed files with 234 additions and 8 deletions

View File

@@ -5,6 +5,7 @@ import { Writable } from 'node:stream'
import { Bindings } from '../types'
import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram'
import settings from './settings'
import miniapp from './miniapp'
export const api = new Hono<{ Bindings: Bindings }>();
export { sendMailToTelegram }
@@ -67,3 +68,4 @@ api.get("/admin/telegram/status", async (c) => {
api.get("/admin/telegram/settings", settings.getTelegramSettings);
api.post("/admin/telegram/settings", settings.saveTelegramSettings);
api.post("/telegram/bind_address", miniapp.getTelegramBindAddress);

View File

@@ -0,0 +1,70 @@
import { Context } from "hono";
import { Jwt } from 'hono/utils/jwt'
import { Bindings } from "../types";
import { CONSTANTS } from "../constants";
const encoder = new TextEncoder();
async function getTelegramBindAddress(c: Context<{ Bindings: Bindings }>): Promise<Response> {
// check if the request is from telegram
const { initData } = await c.req.json();
const initDataObj = new URLSearchParams(initData);
initDataObj.sort()
const hash = initDataObj.get('hash');
initDataObj.delete("hash");
const dataToCheck = [...initDataObj.entries()].map(([key, value]) => key + "=" + value).join("\n");
const auth_date = Number(initDataObj.get('auth_date'));
// valid for 300 seconds
if (auth_date + 300 < (new Date().getTime() / 1000)) {
return c.text("OutDate initData", 400);
}
const user = initDataObj.get('user');
if (!hash || !user) {
return c.text("Invalid initData", 400);
}
const { id: userId } = JSON.parse(user);
const cryptoKey = await crypto.subtle.importKey(
"raw",
encoder.encode("WebAppData"),
{ name: "HMAC", hash: { name: "SHA-256" } },
false,
["sign"]
);
const secretKeyBuffer = await crypto.subtle.sign(
"HMAC", cryptoKey, encoder.encode(c.env.TELEGRAM_BOT_TOKEN)
);
const secretKey = await crypto.subtle.importKey(
"raw",
secretKeyBuffer,
{ name: "HMAC", hash: { name: "SHA-256" } },
false,
["sign", "verify"]
);
const calcHmac = await crypto.subtle.sign(
"HMAC", secretKey, encoder.encode(dataToCheck)
);
const calcHash = Array.from(new Uint8Array(calcHmac))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
if (calcHash != hash) {
return c.text("Invalid initData", 400);
}
// get the address list from the KV
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
const res = [];
for (const jwt of jwtList) {
try {
const { address } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
res.push({ address, jwt });
} catch (e) {
console.error(`failed to verify jwt with error: ${e}`)
continue;
}
}
return c.json(res);
}
export default {
getTelegramBindAddress
}

View File

@@ -63,7 +63,7 @@ export function newTelegramBot(c: Context<{ Bindings: Bindings }>, token: string
const prefix = getStringValue(c.env.PREFIX)
const domains = getDomains(c);
return await ctx.reply(
"欢迎使用本机器人\n\n"
"欢迎使用本机器人, 您可以点击左下角打开 mini app \n\n"
+ (prefix ? `当前已启用前缀: ${prefix}\n` : '')
+ "新建邮箱地址, 如果要自定义邮箱地址, "
+ "请输入 /new <name>@<domain>, name [a-zA-Z0-9.] 有效\n"
@@ -241,7 +241,6 @@ export function newTelegramBot(c: Context<{ Bindings: Bindings }>, token: string
export async function initTelegramBotCommands(bot: Telegraf) {
bot.telegram.sendMessage
await bot.telegram.setMyCommands(COMMANDS);
}
@@ -251,13 +250,16 @@ const parseMail = async (raw_mail: string | undefined | null) => {
}
try {
const parsedEmail = await PostalMime.parse(raw_mail);
if (parsedEmail?.text?.length && parsedEmail?.text?.length > 1000) {
parsedEmail.text = parsedEmail.text.substring(0, 1000) + "...";
}
return {
isHtml: false,
mail: `From: ${parsedEmail.from ? `${parsedEmail.from.name}[${parsedEmail.from.address}]` : "无发件人"}\n`
+ `To: ${parsedEmail.to?.map(t => `${t.name}[${t.address}]`).join(" ")}\n`
+ `Subject: ${parsedEmail.subject}\n`
+ `Date: ${parsedEmail.date}\n`
+ `Content:\n${parsedEmail.text?.substring(0, 100) || "解析失败"}`
+ `Content:\n${parsedEmail.text || "解析失败,请点击左下角打开 mini app 查看"}`
};
} catch (e) {
return {