mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-05-12 02:20:12 +08:00
feat: telegram bot TelegramSettings && webhook (#244)
* feat: telegram bot TelegramSettings * feat: webhook
This commit is contained in:
@@ -5,6 +5,7 @@ import { newAddress, handleListQuery } from '../common'
|
||||
import { CONSTANTS } from '../constants'
|
||||
import cleanup_api from './cleanup_api'
|
||||
import admin_user_api from './admin_user_api'
|
||||
import webhook_settings from './webhook_settings'
|
||||
|
||||
const api = new Hono()
|
||||
|
||||
@@ -178,17 +179,17 @@ api.get('/admin/sendbox', async (c) => {
|
||||
})
|
||||
|
||||
api.get('/admin/statistics', async (c) => {
|
||||
const { count: mailCount } = await c.env.DB.prepare(`
|
||||
SELECT count(*) as count FROM raw_mails`
|
||||
const { count: mailCount } = await c.env.DB.prepare(
|
||||
`SELECT count(*) as count FROM raw_mails`
|
||||
).first();
|
||||
const { count: addressCount } = await c.env.DB.prepare(`
|
||||
SELECT count(*) as count FROM address`
|
||||
const { count: addressCount } = await c.env.DB.prepare(
|
||||
`SELECT count(*) as count FROM address`
|
||||
).first();
|
||||
const { count: activeUserCount7days } = await c.env.DB.prepare(`
|
||||
SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
|
||||
const { count: activeUserCount7days } = await c.env.DB.prepare(
|
||||
`SELECT count(*) as count FROM address where updated_at > datetime('now', '-7 day')`
|
||||
).first();
|
||||
const { count: sendMailCount } = await c.env.DB.prepare(`
|
||||
SELECT count(*) as count FROM sendbox`
|
||||
const { count: sendMailCount } = await c.env.DB.prepare(
|
||||
`SELECT count(*) as count FROM sendbox`
|
||||
).first();
|
||||
return c.json({
|
||||
mailCount: mailCount,
|
||||
@@ -242,5 +243,7 @@ api.get('/admin/users', admin_user_api.getUsers)
|
||||
api.delete('/admin/users/:user_id', admin_user_api.deleteUser)
|
||||
api.post('/admin/users', admin_user_api.createUser)
|
||||
api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword)
|
||||
api.get("/admin/webhook/settings", webhook_settings.getWebhookSettings);
|
||||
api.post("/admin/webhook/settings", webhook_settings.saveWebhookSettings);
|
||||
|
||||
export { api }
|
||||
|
||||
22
worker/src/admin_api/webhook_settings.ts
Normal file
22
worker/src/admin_api/webhook_settings.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Context } from "hono";
|
||||
import { Bindings } from "../types";
|
||||
import { CONSTANTS } from "../constants";
|
||||
import { AdminWebhookSettings } from "../models/models";
|
||||
// @ts-ignore
|
||||
import { getBooleanValue } from "../utils";
|
||||
|
||||
async function getWebhookSettings(c: Context<{ Bindings: Bindings }>): Promise<Response> {
|
||||
const settings = await c.env.KV.get<AdminWebhookSettings>(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
|
||||
return c.json(settings || new AdminWebhookSettings([]));
|
||||
}
|
||||
|
||||
async function saveWebhookSettings(c: Context<{ Bindings: Bindings }>): Promise<Response> {
|
||||
const settings = await c.req.json<AdminWebhookSettings>();
|
||||
await c.env.KV.put(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, JSON.stringify(settings));
|
||||
return c.json({ success: true })
|
||||
}
|
||||
|
||||
export default {
|
||||
getWebhookSettings,
|
||||
saveWebhookSettings,
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Hono } from 'hono'
|
||||
|
||||
// @ts-ignore
|
||||
import { getDomains, getPasswords, getBooleanValue } from './utils';
|
||||
import { CONSTANTS } from './constants';
|
||||
import { Bindings } from './types';
|
||||
|
||||
const api = new Hono()
|
||||
const api = new Hono<{ Bindings: Bindings }>
|
||||
|
||||
api.get('/open_api/settings', async (c) => {
|
||||
// check header x-custom-auth
|
||||
@@ -24,6 +26,7 @@ api.get('/open_api/settings', async (c) => {
|
||||
"enableIndexAbout": getBooleanValue(c.env.ENABLE_INDEX_ABOUT),
|
||||
"copyright": c.env.COPYRIGHT,
|
||||
"cfTurnstileSiteKey": c.env.CF_TURNSTILE_SITE_KEY,
|
||||
"enableWebhook": getBooleanValue(c.env.ENABLE_WEBHOOK),
|
||||
"version": CONSTANTS.VERSION,
|
||||
});
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
export const CONSTANTS = {
|
||||
VERSION: 'v0.4.2',
|
||||
VERSION: 'v0.4.3',
|
||||
|
||||
// DB settings
|
||||
ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
|
||||
@@ -8,5 +8,8 @@ export const CONSTANTS = {
|
||||
USER_SETTINGS_KEY: 'user_settings',
|
||||
|
||||
// KV
|
||||
TG_KV_PREFIX: "temp-mail-telegram"
|
||||
TG_KV_PREFIX: "temp-mail-telegram",
|
||||
TG_KV_SETTINGS_KEY: "temp-mail-telegram-settings",
|
||||
WEBHOOK_KV_SETTINGS_KEY: "temp-mail-webhook-settings",
|
||||
WEBHOOK_KV_USER_SETTINGS_KEY: "temp-mail-webhook-user-settings",
|
||||
}
|
||||
|
||||
@@ -1,44 +1,19 @@
|
||||
import { createMimeMessage } from "mimetext";
|
||||
import { getBooleanValue } from "./utils";
|
||||
import { sendMailToTelegram } from "./telegram_api";
|
||||
// @ts-ignore
|
||||
import { getBooleanValue } from "../utils";
|
||||
import { Bindings } from "../types";
|
||||
|
||||
async function email(message, env, ctx) {
|
||||
if (env.BLACK_LIST && env.BLACK_LIST.split(",").some(word => message.from.includes(word))) {
|
||||
message.setReject("Missing from address");
|
||||
console.log(`Reject message from ${message.from} to ${message.to}`);
|
||||
return;
|
||||
}
|
||||
const rawEmail = await new Response(message.raw).text();
|
||||
export const auto_reply = async (message: ForwardableEmailMessage, env: Bindings): Promise<void> => {
|
||||
const message_id = message.headers.get("Message-ID");
|
||||
// save email
|
||||
const { success } = await env.DB.prepare(
|
||||
`INSERT INTO raw_mails (source, address, raw, message_id) VALUES (?, ?, ?, ?)`
|
||||
).bind(
|
||||
message.from, message.to, rawEmail, message_id
|
||||
).run();
|
||||
if (!success) {
|
||||
message.setReject(`Failed save message to ${message.to}`);
|
||||
console.log(`Failed save message from ${message.from} to ${message.to}`);
|
||||
}
|
||||
|
||||
// send email to telegram
|
||||
try {
|
||||
await sendMailToTelegram({
|
||||
env: env,
|
||||
}, message.to, rawEmail);
|
||||
} catch (error) {
|
||||
console.log("send mail to telegram error", error);
|
||||
}
|
||||
|
||||
// auto reply email
|
||||
if (getBooleanValue(env.ENABLE_AUTO_REPLY)) {
|
||||
if (getBooleanValue(env.ENABLE_AUTO_REPLY) && message_id) {
|
||||
try {
|
||||
const results = await env.DB.prepare(
|
||||
`SELECT * FROM auto_reply_mails where address = ? and enabled = 1`
|
||||
).bind(message.to).first();
|
||||
).bind(message.to).first<Record<string, string>>();
|
||||
if (results && results.source_prefix && message.from.startsWith(results.source_prefix)) {
|
||||
const msg = createMimeMessage();
|
||||
msg.setHeader("In-Reply-To", message.headers.get("Message-ID"));
|
||||
msg.setHeader("In-Reply-To", message_id);
|
||||
msg.setSender({
|
||||
name: results.name || results.address,
|
||||
addr: results.address
|
||||
@@ -55,6 +30,7 @@ async function email(message, env, ctx) {
|
||||
message.from,
|
||||
msg.asRaw()
|
||||
);
|
||||
// @ts-ignore
|
||||
await message.reply(replyMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -62,5 +38,3 @@ async function email(message, env, ctx) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { email }
|
||||
51
worker/src/email/index.ts
Normal file
51
worker/src/email/index.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Context } from "hono";
|
||||
|
||||
import { sendMailToTelegram } from "../telegram_api";
|
||||
import { Bindings } from "../types";
|
||||
import { auto_reply } from "./auto_reply";
|
||||
import { trigerWebhook } from "../mails_api/webhook_settings";
|
||||
|
||||
|
||||
async function email(message: ForwardableEmailMessage, env: Bindings, ctx: ExecutionContext) {
|
||||
if (env.BLACK_LIST && env.BLACK_LIST.split(",").some(word => message.from.includes(word))) {
|
||||
message.setReject("Missing from address");
|
||||
console.log(`Reject message from ${message.from} to ${message.to}`);
|
||||
return;
|
||||
}
|
||||
const rawEmail = await new Response(message.raw).text();
|
||||
const message_id = message.headers.get("Message-ID");
|
||||
// save email
|
||||
const { success } = await env.DB.prepare(
|
||||
`INSERT INTO raw_mails (source, address, raw, message_id) VALUES (?, ?, ?, ?)`
|
||||
).bind(
|
||||
message.from, message.to, rawEmail, message_id
|
||||
).run();
|
||||
if (!success) {
|
||||
message.setReject(`Failed save message to ${message.to}`);
|
||||
console.log(`Failed save message from ${message.from} to ${message.to}`);
|
||||
}
|
||||
|
||||
// send email to telegram
|
||||
try {
|
||||
await sendMailToTelegram(
|
||||
{ env: env } as Context<{ Bindings: Bindings }>,
|
||||
message.to, rawEmail);
|
||||
} catch (error) {
|
||||
console.log("send mail to telegram error", error);
|
||||
}
|
||||
|
||||
// send webhook
|
||||
try {
|
||||
await trigerWebhook(
|
||||
{ env: env } as Context<{ Bindings: Bindings }>,
|
||||
message.to, rawEmail
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("send webhook error", error);
|
||||
}
|
||||
|
||||
// auto reply email
|
||||
await auto_reply(message, env);
|
||||
}
|
||||
|
||||
export { email }
|
||||
@@ -4,11 +4,15 @@ import { getBooleanValue, getJsonSetting, checkCfTurnstile } from '../utils';
|
||||
import { newAddress, handleListQuery } from '../common'
|
||||
import { CONSTANTS } from '../constants'
|
||||
import auto_reply from './auto_reply'
|
||||
import webhook_settings from './webhook_settings';
|
||||
|
||||
const api = new Hono()
|
||||
|
||||
api.get('/api/auto_reply', auto_reply.getAutoReply)
|
||||
api.post('/api/auto_reply', auto_reply.saveAutoReply)
|
||||
api.get('/api/webhook/settings', webhook_settings.getWebhookSettings)
|
||||
api.post('/api/webhook/settings', webhook_settings.saveWebhookSettings)
|
||||
api.post('/api/webhook/test', webhook_settings.testWebhookSettings)
|
||||
|
||||
api.get('/api/mails', async (c) => {
|
||||
const { address } = c.get("jwtPayload")
|
||||
|
||||
135
worker/src/mails_api/webhook_settings.ts
Normal file
135
worker/src/mails_api/webhook_settings.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { Context } from "hono";
|
||||
import { Bindings, Variables } from "../types";
|
||||
import { CONSTANTS } from "../constants";
|
||||
import { AdminWebhookSettings, WebhookMail } from "../models/models";
|
||||
// @ts-ignore
|
||||
import { getBooleanValue } from "../utils";
|
||||
import PostalMime from 'postal-mime';
|
||||
|
||||
|
||||
class WebhookSettings {
|
||||
url: string = ''
|
||||
method: string = 'POST'
|
||||
headers: string = JSON.stringify({
|
||||
"Content-Type": "application/json"
|
||||
}, null, 2)
|
||||
body: string = JSON.stringify({
|
||||
"from": "${from}",
|
||||
"to": "${to}",
|
||||
"headers": "${headers}",
|
||||
"subject": "${subject}",
|
||||
"raw": "${raw}",
|
||||
"parsedText": "${parsedText}",
|
||||
}, null, 2)
|
||||
}
|
||||
|
||||
|
||||
async function getWebhookSettings(
|
||||
c: Context<{ Bindings: Bindings, Variables: Variables }>
|
||||
): Promise<Response> {
|
||||
if (!c.env.KV) {
|
||||
return c.text("KV is not available", 400);
|
||||
}
|
||||
if (!getBooleanValue(c.env.ENABLE_WEBHOOK)) {
|
||||
return c.text("Webhook is disabled", 403);
|
||||
}
|
||||
const { address } = c.get("jwtPayload")
|
||||
const adminSettings = await c.env.KV.get<AdminWebhookSettings>(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
|
||||
if (!adminSettings?.allowList.includes(address)) {
|
||||
return c.text("Webhook settings is not allowed for this user", 403);
|
||||
}
|
||||
const settings = await c.env.KV.get<WebhookSettings>(
|
||||
`${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json"
|
||||
) || new WebhookSettings();
|
||||
return c.json(settings);
|
||||
}
|
||||
|
||||
|
||||
async function saveWebhookSettings(
|
||||
c: Context<{ Bindings: Bindings, Variables: Variables }>
|
||||
): Promise<Response> {
|
||||
const { address } = c.get("jwtPayload")
|
||||
const adminSettings = await c.env.KV.get<AdminWebhookSettings>(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
|
||||
if (!adminSettings?.allowList.includes(address)) {
|
||||
return c.text("Webhook settings is not allowed for this user", 403);
|
||||
}
|
||||
const settings = await c.req.json<WebhookSettings>();
|
||||
await c.env.KV.put(
|
||||
`${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`,
|
||||
JSON.stringify(settings));
|
||||
return c.json({ success: true })
|
||||
}
|
||||
|
||||
async function sendWebhook(settings: WebhookSettings, formatMap: WebhookMail): Promise<{ success: boolean, message?: string }> {
|
||||
// send webhook
|
||||
let body = settings.body;
|
||||
for (const key of Object.keys(formatMap)) {
|
||||
body = body.replace(new RegExp(`\\$\\{${key}\\}`, "g"), formatMap[key as keyof WebhookMail]);
|
||||
}
|
||||
const response = await fetch(settings.url, {
|
||||
method: settings.method,
|
||||
headers: JSON.parse(settings.headers),
|
||||
body: body
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.log("send webhook error", response.status, response.statusText);
|
||||
return { success: false, message: `send webhook error: ${response.status} ${response.statusText}` };
|
||||
}
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function trigerWebhook(
|
||||
c: Context<{ Bindings: Bindings }>,
|
||||
address: string,
|
||||
raw_mail: string
|
||||
): Promise<void> {
|
||||
if (!c.env.KV || !getBooleanValue(c.env.ENABLE_WEBHOOK)) {
|
||||
return
|
||||
}
|
||||
const adminSettings = await c.env.KV.get<AdminWebhookSettings>(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
|
||||
if (!adminSettings?.allowList.includes(address)) {
|
||||
return;
|
||||
}
|
||||
const settings = await c.env.KV.get<WebhookSettings>(
|
||||
`${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json"
|
||||
);
|
||||
if (!settings) {
|
||||
return;
|
||||
}
|
||||
const parsedEmail = await PostalMime.parse(raw_mail);
|
||||
const res = await sendWebhook(settings, {
|
||||
from: parsedEmail.from.address || "",
|
||||
to: address,
|
||||
headers: JSON.stringify(parsedEmail.headers, null, 2),
|
||||
subject: parsedEmail.subject || "",
|
||||
raw: raw_mail,
|
||||
parsedText: parsedEmail.text || parsedEmail.html || ""
|
||||
});
|
||||
if (!res.success) {
|
||||
console.log(res.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function testWebhookSettings(
|
||||
c: Context<{ Bindings: Bindings, Variables: Variables }>
|
||||
): Promise<Response> {
|
||||
const settings = await c.req.json<WebhookSettings>();
|
||||
const res = await sendWebhook(settings, {
|
||||
from: "from@test.com",
|
||||
to: "to@test.com",
|
||||
headers: "headers",
|
||||
subject: "test",
|
||||
raw: "test",
|
||||
parsedText: "test"
|
||||
});
|
||||
if (!res.success) {
|
||||
return c.text(res.message || "send webhook error", 400);
|
||||
}
|
||||
return c.json({ success: true });
|
||||
}
|
||||
|
||||
export default {
|
||||
getWebhookSettings,
|
||||
saveWebhookSettings,
|
||||
testWebhookSettings,
|
||||
}
|
||||
16
worker/src/models/models.ts
Normal file
16
worker/src/models/models.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export class AdminWebhookSettings {
|
||||
allowList: string[];
|
||||
|
||||
constructor(allowList: string[]) {
|
||||
this.allowList = allowList;
|
||||
}
|
||||
}
|
||||
|
||||
export type WebhookMail = {
|
||||
from: string;
|
||||
to: string;
|
||||
headers: string;
|
||||
subject: string;
|
||||
raw: string;
|
||||
parsedText: string;
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
import { Hono, Context } from 'hono'
|
||||
import { Hono } from 'hono'
|
||||
import { ServerResponse } from 'node:http'
|
||||
import { Writable } from 'node:stream'
|
||||
import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram'
|
||||
|
||||
export const api = new Hono()
|
||||
import { Bindings } from '../types'
|
||||
import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram'
|
||||
import settings from './settings'
|
||||
|
||||
export const api = new Hono<{ Bindings: Bindings }>();
|
||||
export { sendMailToTelegram }
|
||||
|
||||
api.post("/telegram/webhook", async (c: Context) => {
|
||||
api.use("/telegram/*", async (c, next) => {
|
||||
if (!c.env.TELEGRAM_BOT_TOKEN) {
|
||||
return c.text("TELEGRAM_BOT_TOKEN is required", 400);
|
||||
}
|
||||
if (!c.env.KV) {
|
||||
return c.text("KV is required", 400);
|
||||
}
|
||||
return await next();
|
||||
});
|
||||
|
||||
api.use("/admin/telegram/*", async (c, next) => {
|
||||
if (!c.env.TELEGRAM_BOT_TOKEN) {
|
||||
return c.text("TELEGRAM_BOT_TOKEN is required", 400);
|
||||
}
|
||||
if (!c.env.KV) {
|
||||
return c.text("KV is required", 400);
|
||||
}
|
||||
return await next();
|
||||
});
|
||||
|
||||
api.post("/telegram/webhook", async (c) => {
|
||||
const token = c.env.TELEGRAM_BOT_TOKEN;
|
||||
const bot = newTelegramBot(c, token);
|
||||
let body = null;
|
||||
@@ -21,10 +44,7 @@ api.post("/telegram/webhook", async (c: Context) => {
|
||||
return c.body(body);
|
||||
});
|
||||
|
||||
api.post("/admin/telegram/init", async (c: Context) => {
|
||||
if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
|
||||
return c.text("TELEGRAM_BOT_TOKEN and KV are required", 400);
|
||||
}
|
||||
api.post("/admin/telegram/init", async (c) => {
|
||||
const domain = new URL(c.req.url).host;
|
||||
const token = c.env.TELEGRAM_BOT_TOKEN;
|
||||
const webhookUrl = `https://${domain}/telegram/webhook`;
|
||||
@@ -37,13 +57,13 @@ api.post("/admin/telegram/init", async (c: Context) => {
|
||||
});
|
||||
});
|
||||
|
||||
api.get("/admin/telegram/status", async (c: Context) => {
|
||||
if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
|
||||
return c.text("TELEGRAM_BOT_TOKEN and KV are required", 400);
|
||||
}
|
||||
api.get("/admin/telegram/status", async (c) => {
|
||||
const token = c.env.TELEGRAM_BOT_TOKEN;
|
||||
const bot = newTelegramBot(c, token);
|
||||
const info = await bot.telegram.getWebhookInfo()
|
||||
const commands = await bot.telegram.getMyCommands()
|
||||
return c.json({ info, commands });
|
||||
});
|
||||
|
||||
api.get("/admin/telegram/settings", settings.getTelegramSettings);
|
||||
api.post("/admin/telegram/settings", settings.saveTelegramSettings);
|
||||
|
||||
30
worker/src/telegram_api/settings.ts
Normal file
30
worker/src/telegram_api/settings.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Context } from "hono";
|
||||
import { Bindings } from "../types";
|
||||
import { CONSTANTS } from "../constants";
|
||||
|
||||
export class TelegramSettings {
|
||||
enableAllowList: boolean;
|
||||
allowList: string[];
|
||||
|
||||
constructor(enableAllowList: boolean, allowList: string[]) {
|
||||
this.enableAllowList = enableAllowList;
|
||||
this.allowList = allowList;
|
||||
}
|
||||
}
|
||||
|
||||
async function getTelegramSettings(c: Context<{ Bindings: Bindings }>): Promise<Response> {
|
||||
const settings = await c.env.KV.get<TelegramSettings>(CONSTANTS.TG_KV_SETTINGS_KEY, "json");
|
||||
return c.json(settings || new TelegramSettings(false, []));
|
||||
}
|
||||
|
||||
|
||||
async function saveTelegramSettings(c: Context<{ Bindings: Bindings }>): Promise<Response> {
|
||||
const settings = await c.req.json<TelegramSettings>();
|
||||
await c.env.KV.put(CONSTANTS.TG_KV_SETTINGS_KEY, JSON.stringify(settings));
|
||||
return c.json({ success: true })
|
||||
}
|
||||
|
||||
export default {
|
||||
getTelegramSettings,
|
||||
saveTelegramSettings,
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import { CONSTANTS } from "../constants";
|
||||
import { getIntValue, getDomains, getStringValue } from '../utils';
|
||||
// @ts-ignore
|
||||
import { newAddress } from '../common'
|
||||
import { Bindings } from "../types";
|
||||
import { TelegramSettings } from "./settings";
|
||||
|
||||
const COMMANDS = [
|
||||
{
|
||||
@@ -34,12 +36,30 @@ const COMMANDS = [
|
||||
},
|
||||
]
|
||||
|
||||
export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
export function newTelegramBot(c: Context<{ Bindings: Bindings }>, token: string): Telegraf {
|
||||
const bot = new Telegraf(token);
|
||||
bot.command("start", async (ctx: TgContext) => {
|
||||
|
||||
bot.use(async (ctx, next) => {
|
||||
if (ctx.chat?.type !== "private") {
|
||||
return await ctx.reply("请在私聊中使用");
|
||||
}
|
||||
|
||||
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("您没有权限使用此机器人");
|
||||
}
|
||||
|
||||
await next();
|
||||
})
|
||||
|
||||
bot.command("start", async (ctx: TgContext) => {
|
||||
const prefix = getStringValue(c.env.PREFIX)
|
||||
const domains = getDomains(c);
|
||||
return await ctx.reply(
|
||||
@@ -53,9 +73,6 @@ export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
);
|
||||
});
|
||||
bot.command("new", async (ctx: TgContext) => {
|
||||
if (ctx.chat?.type !== "private") {
|
||||
return await ctx.reply("请在私聊中使用");
|
||||
}
|
||||
const userId = ctx?.message?.from?.id;
|
||||
if (!userId) {
|
||||
return await ctx.reply("无法获取用户信息");
|
||||
@@ -72,14 +89,14 @@ export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
// @ts-ignore
|
||||
const address = ctx?.message?.text.slice("/new".length).trim() || Math.random().toString(36).substring(2, 15);
|
||||
const [name, domain] = address.includes("@") ? address.split("@") : [address, null];
|
||||
const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
|
||||
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
|
||||
if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) {
|
||||
return await ctx.reply("绑定地址数量已达上限");
|
||||
}
|
||||
const res = await newAddress(c, name, domain, true);
|
||||
// for mail push to telegram
|
||||
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, res.jwt]));
|
||||
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${res.address}`, userId);
|
||||
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${res.address}`, userId.toString());
|
||||
return await ctx.reply(`创建地址成功:\n`
|
||||
+ `地址: ${res.address}\n`
|
||||
+ `凭证: ${res.jwt}\n`
|
||||
@@ -90,9 +107,6 @@ export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
});
|
||||
|
||||
bot.command("bind", async (ctx: TgContext) => {
|
||||
if (ctx.chat?.type !== "private") {
|
||||
return await ctx.reply("请在私聊中使用");
|
||||
}
|
||||
const userId = ctx?.message?.from?.id;
|
||||
if (!userId) {
|
||||
return await ctx.reply("无法获取用户信息");
|
||||
@@ -107,13 +121,13 @@ export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
if (!address) {
|
||||
return await ctx.reply("凭证无效");
|
||||
}
|
||||
const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
|
||||
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
|
||||
if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) {
|
||||
return await ctx.reply("绑定地址数量已达上限");
|
||||
}
|
||||
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, jwt]));
|
||||
// for mail push to telegram
|
||||
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${address}`, userId);
|
||||
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${address}`, userId.toString());
|
||||
return await ctx.reply(`绑定成功:\n`
|
||||
+ `地址: ${address}`
|
||||
);
|
||||
@@ -123,16 +137,13 @@ export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
}
|
||||
});
|
||||
|
||||
bot.command("address", async (ctx: TgContext) => {
|
||||
if (ctx.chat?.type !== "private") {
|
||||
return await ctx.reply("请在私聊中使用");
|
||||
}
|
||||
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(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
|
||||
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
|
||||
const addressList = [];
|
||||
for (const jwt of jwtList) {
|
||||
try {
|
||||
@@ -156,7 +167,7 @@ export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
if (!userId) {
|
||||
return await ctx.reply("无法获取用户信息");
|
||||
}
|
||||
const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
|
||||
const jwtList = await c.env.KV.get<string[]>(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, 'json') || [];
|
||||
const addressList = [];
|
||||
for (const jwt of jwtList) {
|
||||
try {
|
||||
@@ -178,7 +189,7 @@ export function newTelegramBot(c: Context, token: string): Telegraf {
|
||||
+ ` order by id desc limit 1 offset ?`
|
||||
).bind(
|
||||
queryAddress, mailIndex
|
||||
).first("raw");
|
||||
).first<string>("raw");
|
||||
const { mail } = await parseMail(raw);
|
||||
if (edit) {
|
||||
return await ctx.editMessageText(mail || "无邮件",
|
||||
@@ -234,7 +245,7 @@ export async function initTelegramBotCommands(bot: Telegraf) {
|
||||
await bot.telegram.setMyCommands(COMMANDS);
|
||||
}
|
||||
|
||||
const parseMail = async (raw_mail: string) => {
|
||||
const parseMail = async (raw_mail: string | undefined | null) => {
|
||||
if (!raw_mail) {
|
||||
return {};
|
||||
}
|
||||
@@ -257,7 +268,7 @@ const parseMail = async (raw_mail: string) => {
|
||||
}
|
||||
|
||||
|
||||
export async function sendMailToTelegram(c: Context, address: string, raw_mail: string) {
|
||||
export async function sendMailToTelegram(c: Context<{ Bindings: Bindings }>, address: string, raw_mail: string) {
|
||||
if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
|
||||
return;
|
||||
}
|
||||
|
||||
42
worker/src/types.d.ts
vendored
Normal file
42
worker/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
export type Bindings = {
|
||||
// bindings
|
||||
DB: D1Database
|
||||
KV: KVNamespace
|
||||
RATE_LIMITER: any
|
||||
|
||||
// config
|
||||
PREFIX: string | undefined
|
||||
JWT_SECRET: string
|
||||
BLACK_LIST: string | undefined
|
||||
ENABLE_AUTO_REPLY: string | boolean | undefined
|
||||
ENABLE_WEBHOOK: string | boolean | undefined
|
||||
ENABLE_USER_CREATE_EMAIL: string | boolean | undefined
|
||||
ENABLE_USER_DELETE_EMAIL: string | boolean | undefined
|
||||
ENABLE_INDEX_ABOUT: string | boolean | undefined
|
||||
ADMIN_CONTACT: string | undefined
|
||||
COPYRIGHT: string | undefined
|
||||
|
||||
// cf turnstile
|
||||
CF_TURNSTILE_SITE_KEY: string | undefined
|
||||
|
||||
// telegram config
|
||||
TELEGRAM_BOT_TOKEN: string
|
||||
TG_MAX_ADDRESS: number | undefined
|
||||
}
|
||||
|
||||
type JwtPayload = {
|
||||
address: string
|
||||
address_id: number
|
||||
}
|
||||
|
||||
type UserPayload = {
|
||||
user_email: string
|
||||
user_id: number
|
||||
exp: number
|
||||
iat: number
|
||||
}
|
||||
|
||||
type Variables = {
|
||||
userPayload: UserPayload,
|
||||
jwtPayload: JwtPayload
|
||||
}
|
||||
@@ -3,19 +3,28 @@ import { cors } from 'hono/cors';
|
||||
import { jwt } from 'hono/jwt'
|
||||
import { Jwt } from 'hono/utils/jwt'
|
||||
|
||||
// @ts-ignore
|
||||
import { api as commonApi } from './commom_api';
|
||||
// @ts-ignore
|
||||
import { api as mailsApi } from './mails_api'
|
||||
// @ts-ignore
|
||||
import { api as userApi } from './user_api';
|
||||
// @ts-ignore
|
||||
import { api as adminApi } from './admin_api';
|
||||
// @ts-ignore
|
||||
import { api as apiV1 } from './deprecated';
|
||||
// @ts-ignore
|
||||
import { api as apiSendMail } from './mails_api/send_mail_api'
|
||||
import { api as telegramApi } from './telegram_api'
|
||||
|
||||
import { email } from './email';
|
||||
// @ts-ignore
|
||||
import { scheduled } from './scheduled';
|
||||
import { getAdminPasswords, getPasswords } from './utils';
|
||||
// @ts-ignore
|
||||
import { getAdminPasswords, getPasswords, getBooleanValue } from './utils';
|
||||
import { Bindings } from './types';
|
||||
|
||||
const app = new Hono()
|
||||
const app = new Hono<{ Bindings: Bindings }>()
|
||||
//cors
|
||||
app.use('/*', cors());
|
||||
// rate limit
|
||||
@@ -37,6 +46,17 @@ app.use('/*', async (c, next) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
c.req.path.startsWith("/api/webhook")
|
||||
|| c.req.path.startsWith("/admin/webhook")
|
||||
) {
|
||||
if (!c.env.KV) {
|
||||
return c.text("KV is not available", 400);
|
||||
}
|
||||
if (!getBooleanValue(c.env.ENABLE_WEBHOOK)) {
|
||||
return c.text("Webhook is disabled", 403);
|
||||
}
|
||||
}
|
||||
await next()
|
||||
});
|
||||
// api auth
|
||||
Reference in New Issue
Block a user