From d48b139aed7e3251fd5eb97373906db3d7e30570 Mon Sep 17 00:00:00 2001 From: eoao Date: Sun, 10 May 2026 00:26:45 +0800 Subject: [PATCH] feat: AI-powered email verification code recognition --- mail-worker/src/email/email.js | 3 ++ mail-worker/src/entity/email.js | 1 + mail-worker/src/init/init.js | 6 ++++ mail-worker/src/service/ai-service.js | 46 +++++++++++++++++++++++++++ mail-worker/wrangler-action.toml | 4 +++ mail-worker/wrangler-dev.toml | 4 +++ mail-worker/wrangler-test.toml | 4 +++ mail-worker/wrangler.toml | 6 +++- 8 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 mail-worker/src/service/ai-service.js diff --git a/mail-worker/src/email/email.js b/mail-worker/src/email/email.js index 26e6a90..41f62e3 100644 --- a/mail-worker/src/email/email.js +++ b/mail-worker/src/email/email.js @@ -10,6 +10,7 @@ import emailUtils from '../utils/email-utils'; import roleService from '../service/role-service'; import userService from '../service/user-service'; import telegramService from '../service/telegram-service'; +import aiService from '../service/ai-service'; export async function email(message, env, ctx) { @@ -89,6 +90,7 @@ export async function email(message, env, ctx) { } const toName = email.to.find(item => item.address === message.to)?.name || ''; + const code = await aiService.extractCode({ env }, email); const params = { toEmail: message.to, @@ -96,6 +98,7 @@ export async function email(message, env, ctx) { sendEmail: email.from.address, name: email.from.name || emailUtils.getName(email.from.address), subject: email.subject, + code, content: email.html, text: email.text, cc: email.cc ? JSON.stringify(email.cc) : '[]', diff --git a/mail-worker/src/entity/email.js b/mail-worker/src/entity/email.js index f6ffbeb..705a4a9 100644 --- a/mail-worker/src/entity/email.js +++ b/mail-worker/src/entity/email.js @@ -7,6 +7,7 @@ export const email = sqliteTable('email', { accountId: integer('account_id').notNull(), userId: integer('user_id').notNull(), subject: text('subject'), + code: text('code').default('').notNull(), text: text('text'), content: text('content'), cc: text('cc').default('[]'), diff --git a/mail-worker/src/init/init.js b/mail-worker/src/init/init.js index b03e1ac..7f65607 100644 --- a/mail-worker/src/init/init.js +++ b/mail-worker/src/init/init.js @@ -34,6 +34,12 @@ const dbInit = { }, async v3_0DB(c) { + try { + await c.env.db.prepare(`ALTER TABLE email ADD COLUMN code TEXT NOT NULL DEFAULT '';`).run(); + } catch (e) { + console.warn(`跳过字段:${e.message}`); + } + try { await c.env.db.batch([ c.env.db.prepare(`ALTER TABLE setting ADD COLUMN black_subject TEXT NOT NULL DEFAULT '';`), diff --git a/mail-worker/src/service/ai-service.js b/mail-worker/src/service/ai-service.js new file mode 100644 index 0000000..9f1124c --- /dev/null +++ b/mail-worker/src/service/ai-service.js @@ -0,0 +1,46 @@ +import emailUtils from '../utils/email-utils'; + +const aiService = { + async extractCode(c, email) { + const ai = c.env.AI || c.env.ai; + + if (!ai) { + return ''; + } + + try { + const subject = email.subject || ''; + const text = emailUtils.formatText(email.text || ''); + const htmlText = emailUtils.htmlToText(email.html || ''); + const body = (htmlText || text).slice(0, 6000); + + if (!subject && !body) { + return ''; + } + + const result = await ai.run(c.env.ai_model || '@cf/meta/llama-3.1-8b-instruct', { + messages: [ + { + role: 'system', + content: 'You extract verification codes from emails. Return only JSON like {"code":"123456"} or {"code":""}. Do not explain.' + }, + { + role: 'user', + content: `Subject: ${subject}\n\n${body}` + } + ], + temperature: 0, + max_tokens: 32 + }); + + const content = typeof result === 'string' ? result : result?.response || ''; + const json = JSON.parse(content); + return typeof json.code === 'string' ? json.code.trim().slice(0, 64) : ''; + } catch (e) { + console.error('验证码提取失败: ', e); + return ''; + } + } +}; + +export default aiService; diff --git a/mail-worker/wrangler-action.toml b/mail-worker/wrangler-action.toml index 582d0a6..d60ec0f 100644 --- a/mail-worker/wrangler-action.toml +++ b/mail-worker/wrangler-action.toml @@ -22,6 +22,9 @@ id = "${KV_NAMESPACE_ID}" # 使用占位符引用环境变量 binding = "r2" bucket_name = "${R2_BUCKET_NAME}" # 使用占位符引用环境变量 +[ai] +binding = "AI" + [assets] binding = "assets" #静态资源绑定名默认不可修改 directory = "./dist" #前端vue项目打包的静态资源存放位置,默认dist @@ -33,6 +36,7 @@ crons = ["0 16 * * *"] #定时任务每天晚上12点执行 [vars] +ai_model = "@cf/meta/llama-3.1-8b-instruct" #orm_log = false domain = "${DOMAIN}" #邮件域名可可配置多个 示例: ["example1.com","example2.com"] admin = "${ADMIN}" #管理员的邮箱 示例: admin@example.com diff --git a/mail-worker/wrangler-dev.toml b/mail-worker/wrangler-dev.toml index fb49a8a..28c0fb8 100644 --- a/mail-worker/wrangler-dev.toml +++ b/mail-worker/wrangler-dev.toml @@ -22,6 +22,9 @@ id = "2io01d4b299e481b9de060ece9e7785c" #binding = "r2" #bucket_name = "email" +[ai] +binding = "AI" + [assets] binding = "assets" directory = "./dist" @@ -30,6 +33,7 @@ run_worker_first = true [vars] +ai_model = "@cf/meta/llama-3.1-8b-instruct" orm_log = false domain = ["example.com", "example2.com", "example3.com", "example4.com"] admin = "admin@example.com" diff --git a/mail-worker/wrangler-test.toml b/mail-worker/wrangler-test.toml index 04137ac..edbcb0e 100644 --- a/mail-worker/wrangler-test.toml +++ b/mail-worker/wrangler-test.toml @@ -20,6 +20,9 @@ id = "9be10cd058e04c55b526f8c0c116f50c" #kv数据库id #binding = "r2" #r2对象存储绑定名默认不可修改 #bucket_name = "test-email" #r2对象存储桶的名字 +[ai] +binding = "AI" + [assets] binding = "assets" #静态资源绑定名默认不可修改 directory = "./dist" #前端vue项目打包的静态资源存放位置,默认dist @@ -31,6 +34,7 @@ crons = ["0 16 * * *"] #定时任务每天晚上12点执行 [vars] +ai_model = "@cf/meta/llama-3.1-8b-instruct" orm_log = false domain = ["example.com"] #邮件域名可可配置多个 示例: ["example1.com","example2.com"] admin = "admin@example.com" #管理员的邮箱 示例: admin@example.com diff --git a/mail-worker/wrangler.toml b/mail-worker/wrangler.toml index b8ec389..5ad5ab6 100644 --- a/mail-worker/wrangler.toml +++ b/mail-worker/wrangler.toml @@ -19,6 +19,9 @@ enabled = true #binding = "r2" #r2对象存储绑定名默认不可修改 #bucket_name = "" #r2对象存储桶的名字 +[ai] +binding = "AI" + [assets] binding = "assets" #静态资源绑定名默认不可修改 directory = "./dist" #前端vue项目打包的静态资源存放位置,默认dist @@ -29,7 +32,8 @@ run_worker_first = true crons = ["0 16 * * *"] #定时任务每天晚上12点执行 -#[vars] +[vars] +ai_model = "@cf/meta/llama-3.1-8b-instruct" #orm_log = false #domain = [] #邮件域名可可配置多个 示例: ["example1.com","example2.com"] #admin = "" #管理员的邮箱 示例: admin@example.com