mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-12 02:20:12 +08:00
344 lines
13 KiB
TypeScript
344 lines
13 KiB
TypeScript
|
|
import { Context } from "hono";
|
|
import { Telegraf, Context as TgContext, Markup } from "telegraf";
|
|
import { callbackQuery } from "telegraf/filters";
|
|
|
|
import { CONSTANTS } from "../constants";
|
|
import { getDomains, getStringValue } from '../utils';
|
|
import { HonoCustomType } from "../types";
|
|
import { TelegramSettings } from "./settings";
|
|
import { bindTelegramAddress, deleteTelegramAddress, jwtListToAddressData, tgUserNewAddress, unbindTelegramAddress, unbindTelegramByAddress } from "./common";
|
|
import { commonParseMail } from "../common";
|
|
|
|
|
|
const COMMANDS = [
|
|
{
|
|
command: "start",
|
|
description: "开始使用"
|
|
},
|
|
{
|
|
command: "new",
|
|
description: "新建邮箱地址, 如果要自定义邮箱地址, 请输入 /new <name>@<domain>, name [a-z0-9] 有效"
|
|
},
|
|
{
|
|
command: "address",
|
|
description: "查看邮箱地址列表"
|
|
},
|
|
{
|
|
command: "bind",
|
|
description: "绑定邮箱地址, 请输入 /bind <邮箱地址凭证>"
|
|
},
|
|
{
|
|
command: "unbind",
|
|
description: "解绑邮箱地址, 请输入 /unbind <邮箱地址>"
|
|
},
|
|
{
|
|
command: "delete",
|
|
description: "删除邮箱地址, 请输入 /delete <邮箱地址>"
|
|
},
|
|
{
|
|
command: "mails",
|
|
description: "查看邮件, 请输入 /mails <邮箱地址>, 不输入地址默认查看第一个地址"
|
|
},
|
|
]
|
|
|
|
export function newTelegramBot(c: Context<HonoCustomType>, token: string): Telegraf {
|
|
const bot = new Telegraf(token);
|
|
|
|
bot.use(async (ctx, next) => {
|
|
// check if in private chat
|
|
if (ctx.chat?.type !== "private") {
|
|
return;
|
|
}
|
|
|
|
const userId = ctx?.message?.from?.id || ctx.callbackQuery?.message?.chat?.id;
|
|
if (!userId) {
|
|
return await ctx.reply("无法获取用户信息");
|
|
}
|
|
|
|
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
|
|
if (settings?.enableAllowList && settings?.enableAllowList
|
|
&& !settings.allowList.includes(userId.toString())
|
|
) {
|
|
return await ctx.reply("您没有权限使用此机器人");
|
|
}
|
|
try {
|
|
await next();
|
|
} catch (error) {
|
|
console.error(`Error: ${error}`);
|
|
return await ctx.reply(`Error: ${error}`);
|
|
}
|
|
})
|
|
|
|
bot.command("start", async (ctx: TgContext) => {
|
|
const prefix = getStringValue(c.env.PREFIX)
|
|
const domains = getDomains(c);
|
|
return await ctx.reply(
|
|
"欢迎使用本机器人, 您可以打开 mini app \n\n"
|
|
+ (prefix ? `当前已启用前缀: ${prefix}\n` : '')
|
|
+ `当前可用域名: ${JSON.stringify(domains)}\n`
|
|
+ "请使用以下命令:\n"
|
|
+ COMMANDS.map(c => `/${c.command}: ${c.description}`).join("\n")
|
|
);
|
|
});
|
|
|
|
bot.command("new", async (ctx: TgContext) => {
|
|
const userId = ctx?.message?.from?.id;
|
|
if (!userId) {
|
|
return await ctx.reply("无法获取用户信息");
|
|
}
|
|
try {
|
|
// @ts-ignore
|
|
const address = ctx?.message?.text.slice("/new".length).trim();
|
|
const res = await tgUserNewAddress(c, userId.toString(), address);
|
|
return await ctx.reply(`创建地址成功:\n`
|
|
+ `地址: ${res.address}\n`
|
|
+ `凭证: ${res.jwt}\n`
|
|
);
|
|
} catch (e) {
|
|
return await ctx.reply(`创建地址失败: ${(e as Error).message}`);
|
|
}
|
|
});
|
|
|
|
bot.command("bind", async (ctx: TgContext) => {
|
|
const userId = ctx?.message?.from?.id;
|
|
if (!userId) {
|
|
return await ctx.reply("无法获取用户信息");
|
|
}
|
|
try {
|
|
// @ts-ignore
|
|
const jwt = ctx?.message?.text.slice("/bind".length).trim();
|
|
if (!jwt) {
|
|
return await ctx.reply("请输入凭证");
|
|
}
|
|
const address = await bindTelegramAddress(c, userId.toString(), jwt);
|
|
return await ctx.reply(`绑定成功:\n`
|
|
+ `地址: ${address}`
|
|
);
|
|
}
|
|
catch (e) {
|
|
return await ctx.reply(`绑定失败: ${(e as Error).message}`);
|
|
}
|
|
});
|
|
|
|
bot.command("unbind", async (ctx: TgContext) => {
|
|
const userId = ctx?.message?.from?.id;
|
|
if (!userId) {
|
|
return await ctx.reply("无法获取用户信息");
|
|
}
|
|
try {
|
|
// @ts-ignore
|
|
const address = ctx?.message?.text.slice("/unbind".length).trim();
|
|
if (!address) {
|
|
return await ctx.reply("请输入地址");
|
|
}
|
|
await unbindTelegramAddress(c, userId.toString(), address);
|
|
return await ctx.reply(`解绑成功:\n地址: ${address}`
|
|
);
|
|
}
|
|
catch (e) {
|
|
return await ctx.reply(`解绑失败: ${(e as Error).message}`);
|
|
}
|
|
})
|
|
|
|
bot.command("delete", async (ctx: TgContext) => {
|
|
const userId = ctx?.message?.from?.id;
|
|
if (!userId) {
|
|
return await ctx.reply("无法获取用户信息");
|
|
}
|
|
try {
|
|
// @ts-ignore
|
|
const address = ctx?.message?.text.slice("/unbind".length).trim();
|
|
if (!address) {
|
|
return await ctx.reply("请输入地址");
|
|
}
|
|
await deleteTelegramAddress(c, userId.toString(), address);
|
|
return await ctx.reply(`删除成功: ${address}`);
|
|
} catch (e) {
|
|
return await ctx.reply(`删除失败: ${(e as Error).message}`);
|
|
}
|
|
});
|
|
|
|
bot.command("address", async (ctx) => {
|
|
const userId = ctx?.message?.from?.id;
|
|
if (!userId) {
|
|
return await ctx.reply("无法获取用户信息");
|
|
}
|
|
try {
|
|
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
|
|
const { addressList } = await jwtListToAddressData(c, jwtList);
|
|
return await ctx.reply(`地址列表:\n\n`
|
|
+ addressList.map(a => `地址: ${a}`).join("\n")
|
|
);
|
|
} catch (e) {
|
|
return await ctx.reply(`获取地址列表失败: ${(e as Error).message}`);
|
|
}
|
|
});
|
|
|
|
const queryMail = async (ctx: TgContext, queryAddress: string, mailIndex: number, edit: boolean) => {
|
|
const userId = ctx?.message?.from?.id || ctx.callbackQuery?.message?.chat?.id;
|
|
if (!userId) {
|
|
return await ctx.reply("无法获取用户信息");
|
|
}
|
|
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
|
|
const { addressList, addressIdMap } = await jwtListToAddressData(c, jwtList);
|
|
if (!queryAddress && addressList.length > 0) {
|
|
queryAddress = addressList[0];
|
|
}
|
|
if (!(queryAddress in addressIdMap)) {
|
|
return await ctx.reply(`未绑定此地址 ${queryAddress}`);
|
|
}
|
|
const address_id = addressIdMap[queryAddress];
|
|
const db_address_id = await c.env.DB.prepare(
|
|
`SELECT id FROM address where id = ? `
|
|
).bind(address_id).first("id");
|
|
if (!db_address_id) {
|
|
return await ctx.reply("无效地址");
|
|
}
|
|
const { raw, id: mailId, created_at } = await c.env.DB.prepare(
|
|
`SELECT * FROM raw_mails where address = ? `
|
|
+ ` order by id desc limit 1 offset ?`
|
|
).bind(
|
|
queryAddress, mailIndex
|
|
).first<{ raw: string, id: string, created_at: string }>() || {};
|
|
const { mail } = raw ? await parseMail(raw, queryAddress, created_at) : { mail: "已经没有邮件了" };
|
|
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
|
|
const miniAppButtons = []
|
|
if (settings?.miniAppUrl && settings?.miniAppUrl?.length > 0 && mailId) {
|
|
const url = new URL(settings.miniAppUrl);
|
|
url.pathname = "/telegram_mail"
|
|
url.searchParams.set("mail_id", mailId);
|
|
miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString()));
|
|
}
|
|
if (edit) {
|
|
return await ctx.editMessageText(mail || "无邮件",
|
|
{
|
|
...Markup.inlineKeyboard([
|
|
Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0),
|
|
...miniAppButtons,
|
|
Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw),
|
|
])
|
|
},
|
|
);
|
|
}
|
|
return await ctx.reply(mail || "无邮件",
|
|
{
|
|
...Markup.inlineKeyboard([
|
|
Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0),
|
|
...miniAppButtons,
|
|
Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw),
|
|
])
|
|
},
|
|
);
|
|
}
|
|
|
|
bot.command("mails", async ctx => {
|
|
try {
|
|
const queryAddress = ctx?.message?.text.slice("/mails".length).trim();
|
|
return await queryMail(ctx, queryAddress, 0, false);
|
|
} catch (e) {
|
|
return await ctx.reply(`获取邮件失败: ${(e as Error).message}`);
|
|
}
|
|
});
|
|
|
|
bot.on(callbackQuery("data"), async ctx => {
|
|
// Use ctx.callbackQuery.data
|
|
try {
|
|
const data = ctx.callbackQuery.data;
|
|
if (data && data.startsWith("mail_") && data.split("_").length === 3) {
|
|
const [_, queryAddress, mailIndex] = data.split("_");
|
|
await queryMail(ctx, queryAddress, parseInt(mailIndex), true);
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log(`获取邮件失败: ${(e as Error).message}`, e);
|
|
return await ctx.answerCbQuery(`获取邮件失败: ${(e as Error).message}`);
|
|
}
|
|
await ctx.answerCbQuery();
|
|
});
|
|
|
|
return bot;
|
|
}
|
|
|
|
|
|
export async function initTelegramBotCommands(bot: Telegraf) {
|
|
await bot.telegram.setMyCommands(COMMANDS);
|
|
}
|
|
|
|
const parseMail = async (
|
|
raw_mail: string | undefined | null,
|
|
address: string, created_at: string | undefined | null
|
|
) => {
|
|
if (!raw_mail) {
|
|
return {};
|
|
}
|
|
try {
|
|
const parsedEmail = await commonParseMail(raw_mail);
|
|
let parsedText = parsedEmail?.text || "";
|
|
if (parsedText.length && parsedText.length > 1000) {
|
|
parsedText = parsedEmail?.text.substring(0, 1000) + "\n\n...\n消息过长请到miniapp查看";
|
|
}
|
|
return {
|
|
isHtml: false,
|
|
mail: `From: ${parsedEmail?.sender || "无发件人"}\n`
|
|
+ `To: ${address}\n`
|
|
+ (created_at ? `Date: ${created_at}\n` : "")
|
|
+ `Subject: ${parsedEmail?.subject}\n`
|
|
+ `Content:\n${parsedText || "解析失败,请打开 mini app 查看"}`
|
|
};
|
|
} catch (e) {
|
|
return {
|
|
isHtml: false,
|
|
mail: `解析邮件失败: ${(e as Error).message}`
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
export async function sendMailToTelegram(
|
|
c: Context<HonoCustomType>, address: string,
|
|
raw_mail: string, message_id: string | null
|
|
) {
|
|
if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
|
|
return;
|
|
}
|
|
const userId = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${address}`);
|
|
const { mail } = await parseMail(raw_mail, address, new Date().toUTCString());
|
|
if (!mail) {
|
|
return;
|
|
}
|
|
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
|
|
const golbalPush = settings?.enableGlobalMailPush && settings?.globalMailPushList;
|
|
if (!userId && !golbalPush) {
|
|
return;
|
|
}
|
|
const mailId = await c.env.DB.prepare(
|
|
`SELECT id FROM raw_mails where address = ? and message_id = ?`
|
|
).bind(address, message_id).first<string>("id");
|
|
const bot = newTelegramBot(c, c.env.TELEGRAM_BOT_TOKEN);
|
|
const miniAppButtons = []
|
|
if (settings?.miniAppUrl && settings?.miniAppUrl?.length > 0 && mailId) {
|
|
const url = new URL(settings.miniAppUrl);
|
|
url.pathname = "/telegram_mail"
|
|
url.searchParams.set("mail_id", mailId);
|
|
miniAppButtons.push(Markup.button.webApp("查看邮件", url.toString()));
|
|
}
|
|
if (golbalPush) {
|
|
for (const pushId of settings.globalMailPushList) {
|
|
await bot.telegram.sendMessage(pushId, mail, {
|
|
...Markup.inlineKeyboard([
|
|
...miniAppButtons,
|
|
])
|
|
});
|
|
}
|
|
}
|
|
if (!userId) {
|
|
return;
|
|
}
|
|
await bot.telegram.sendMessage(userId, mail, {
|
|
...Markup.inlineKeyboard([
|
|
...miniAppButtons,
|
|
])
|
|
});
|
|
}
|