diff --git a/mail-vue/src/i18n/en.js b/mail-vue/src/i18n/en.js
index 0c7a97e..d030b63 100644
--- a/mail-vue/src/i18n/en.js
+++ b/mail-vue/src/i18n/en.js
@@ -148,6 +148,9 @@ const en = {
sendEmail: 'Send Email',
resendToken: 'Resend Token',
blackList: 'Blocklist',
+ codeRecognition: 'Verification Code Recognition',
+ codeRecognitionRules: 'Recognition Rules',
+ codeRecognitionRulesDesc: 'Only recognize emails containing the following info. Empty means all.',
oss: 'Object Storage',
osDomain: 'Domain',
ossDomainDesc: 'Leave empty if using KV storage.',
@@ -315,6 +318,7 @@ const en = {
blackSubjectDesc: 'Subject',
blackContentDesc: 'Content',
blackFromDesc: 'Sender or domain',
+ senderRules: 'Sender address or domain',
blackListDesc: 'Reject emails containing the following information',
setSuccess: 'Settings saved successfully',
details: 'Details',
diff --git a/mail-vue/src/i18n/zh.js b/mail-vue/src/i18n/zh.js
index 5730c39..fc382d2 100644
--- a/mail-vue/src/i18n/zh.js
+++ b/mail-vue/src/i18n/zh.js
@@ -148,6 +148,9 @@ const zh = {
sendEmail: '邮件发送',
resendToken: 'Resend Token',
blackList: '邮件黑名单',
+ codeRecognition: '识别验证码',
+ codeRecognitionRules: '识别规则',
+ codeRecognitionRulesDesc: '只识别包含以下信息的邮件,为空识别全部',
oss: '对象存储',
osDomain: '访问域名',
ossDomainDesc: '如果是KV存储不要填',
@@ -315,6 +318,7 @@ const zh = {
blackSubjectDesc: '邮件标题',
blackContentDesc: '邮件内容',
blackFromDesc: '发件人邮箱或域名',
+ senderRules: '发件人邮箱或域名',
blackListDesc: '拒收包含以下信息的邮件',
setSuccess: '设置成功',
details: '详情',
diff --git a/mail-vue/src/views/sys-setting/index.vue b/mail-vue/src/views/sys-setting/index.vue
index 7a0b0cf..ddda292 100644
--- a/mail-vue/src/views/sys-setting/index.vue
+++ b/mail-vue/src/views/sys-setting/index.vue
@@ -361,6 +361,27 @@
+
+
Workers AI
+
+
+
{{ $t('codeRecognition') }}
+
+
+
+
+
+
{{ $t('codeRecognitionRules') }}
+
+
+
+
+
+
+
+
+
{{ $t('about') }}
@@ -759,6 +780,22 @@
{{ $t('save') }}
+
+
+
+ {{ $t('codeRecognitionRules') }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t('save') }}
+
@@ -797,6 +834,7 @@ const userStore = useUserStore();
const editTitleShow = ref(false)
const resendTokenFormShow = ref(false)
const blackFormShow = ref(false)
+const aiCodeFilterShow = ref(false)
const r2DomainShow = ref(false)
const turnstileShow = ref(false)
const tgSettingShow = ref(false)
@@ -864,6 +902,7 @@ const blackListForm = ref({
blackContent: [],
blackFrom: []
})
+const aiCodeFilter = ref([])
const authRefreshOptions = computed(() => [
{label: t('disable'), value: 0},
@@ -913,6 +952,7 @@ function getSettings() {
resetAddS3Form()
resetEmailPrefix()
resetBlackList()
+ resetAiCodeFilter()
})
}
@@ -1192,6 +1232,11 @@ function resetBlackList() {
blackListForm.value.blackContent = setting.value.blackContent ? setting.value.blackContent.split(',') : []
blackListForm.value.blackSubject = setting.value.blackSubject ? setting.value.blackSubject.split(',') : []
}
+
+function resetAiCodeFilter() {
+ aiCodeFilter.value = setting.value.aiCodeFilter ? setting.value.aiCodeFilter.split(',') : []
+}
+
function saveEmailPrefix() {
const form = {}
form.minEmailPrefix = minEmailPrefix.value
@@ -1199,6 +1244,10 @@ function saveEmailPrefix() {
editSetting(form, true)
}
+function saveAiCodeFilter() {
+ editSetting({aiCodeFilter: aiCodeFilter.value + ''})
+}
+
const opacityChange = debounce(doOpacityChange, 1000, {
leading: false,
trailing: true
@@ -1241,6 +1290,20 @@ function banEmailAddTag(val) {
})
}
+function aiCodeFilterAddTag(val) {
+ const emails = Array.from(new Set(
+ val.split(/[,,]/).map(item => item.trim()).filter(item => item)
+ ));
+
+ aiCodeFilter.value.splice(aiCodeFilter.value.length - 1, 1)
+
+ emails.forEach(email => {
+ if ((isEmail(email) || isDomain(email)) && !aiCodeFilter.value.includes(email)) {
+ aiCodeFilter.value.push(email)
+ }
+ })
+}
+
function delBackground() {
ElMessageBox.confirm(t('delBackgroundConfirm'), {
@@ -1331,6 +1394,10 @@ function openBlackListForm() {
blackFormShow.value = true
}
+function openAiCodeFilter() {
+ aiCodeFilterShow.value = true
+}
+
function saveResendToken() {
const settingForm = {
resendTokens: {}
@@ -1408,6 +1475,7 @@ function editSetting(settingForm, refreshStatus = true) {
noticePopupShow.value = false
addS3Show.value = false
emailPrefixShow.value = false
+ aiCodeFilterShow.value = false
}).catch((e) => {
loginOpacity.value = setting.value.loginOpacity
setting.value = {...setting.value, ...JSON.parse(backup)}
diff --git a/mail-worker/src/const/entity-const.js b/mail-worker/src/const/entity-const.js
index 946b108..711b2af 100644
--- a/mail-worker/src/const/entity-const.js
+++ b/mail-worker/src/const/entity-const.js
@@ -128,6 +128,10 @@ export const settingConst = {
OPEN: 0,
CLOSE: 1
},
+ aiCode: {
+ OPEN: 0,
+ CLOSE: 1
+ },
authRefresh: {
OPEN: 1,
CLOSE: 0
diff --git a/mail-worker/src/email/email.js b/mail-worker/src/email/email.js
index 41f62e3..1136be3 100644
--- a/mail-worker/src/email/email.js
+++ b/mail-worker/src/email/email.js
@@ -28,7 +28,9 @@ export async function email(message, env, ctx) {
noRecipient,
blackSubject,
blackContent,
- blackFrom
+ blackFrom,
+ aiCode,
+ aiCodeFilter
} = await settingService.query({ env });
if (receive === settingConst.receive.CLOSE) {
@@ -90,7 +92,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 code = aiCode === settingConst.aiCode.OPEN && checkAiCodeFilter(aiCodeFilter, email) ? await aiService.extractCode({ env }, email) : '';
const params = {
toEmail: message.to,
@@ -183,6 +185,19 @@ export async function email(message, env, ctx) {
}
}
+function checkAiCodeFilter(aiCodeFilterStr, email) {
+ const filterList = aiCodeFilterStr ? aiCodeFilterStr.split(',').map(item => item.trim().toLowerCase()).filter(Boolean) : [];
+
+ if (filterList.length === 0) {
+ return true;
+ }
+
+ const fromEmail = (email.from?.address || '').trim().toLowerCase();
+ const fromDomain = emailUtils.getDomain(fromEmail).toLowerCase();
+
+ return filterList.some(item => item === fromEmail || item === fromDomain);
+}
+
function checkBlock(blackSubjectStr, blackContentStr, blackFromStr, email) {
const blackFromList = blackFromStr ? blackFromStr.split(',') : []
diff --git a/mail-worker/src/entity/setting.js b/mail-worker/src/entity/setting.js
index 7172ff3..b50519f 100644
--- a/mail-worker/src/entity/setting.js
+++ b/mail-worker/src/entity/setting.js
@@ -49,6 +49,8 @@ export const setting = sqliteTable('setting', {
emailPrefixFilter: text('email_prefix_filter').default('').notNull(),
blackSubject: text('black_subject').default('').notNull(),
blackContent: text('black_content').default('').notNull(),
- blackFrom: text('black_from').default('').notNull()
+ blackFrom: text('black_from').default('').notNull(),
+ aiCode: integer('ai_code').default(1).notNull(),
+ aiCodeFilter: text('ai_code_filter').default('').notNull()
});
export default setting
diff --git a/mail-worker/src/init/init.js b/mail-worker/src/init/init.js
index 7f65607..d9ad096 100644
--- a/mail-worker/src/init/init.js
+++ b/mail-worker/src/init/init.js
@@ -49,6 +49,18 @@ const dbInit = {
} catch (e) {
console.warn(`跳过字段:${e.message}`);
}
+
+ try {
+ await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN ai_code INTEGER NOT NULL DEFAULT 1;`).run();
+ } catch (e) {
+ console.warn(`跳过字段:${e.message}`);
+ }
+
+ try {
+ await c.env.db.prepare(`ALTER TABLE setting ADD COLUMN ai_code_filter TEXT NOT NULL DEFAULT '';`).run();
+ } catch (e) {
+ console.warn(`跳过字段:${e.message}`);
+ }
},
async v2_9DB(c) {
@@ -592,7 +604,9 @@ const dbInit = {
title TEXT NOT NULL,
auto_refresh INTEGER NOT NULL,
register_verify INTEGER NOT NULL,
- add_email_verify INTEGER NOT NULL
+ add_email_verify INTEGER NOT NULL,
+ ai_code INTEGER NOT NULL DEFAULT 1,
+ ai_code_filter TEXT NOT NULL DEFAULT ''
)
`).run();
diff --git a/mail-worker/src/service/setting-service.js b/mail-worker/src/service/setting-service.js
index da94c02..8cc4743 100644
--- a/mail-worker/src/service/setting-service.js
+++ b/mail-worker/src/service/setting-service.js
@@ -133,6 +133,10 @@ const settingService = {
params.emailPrefixFilter = params.emailPrefixFilter + '';
}
+ if (Array.isArray(params.aiCodeFilter)) {
+ params.aiCodeFilter = params.aiCodeFilter + '';
+ }
+
params.resendTokens = JSON.stringify(resendTokens);
await orm(c).update(setting).set({ ...params }).returning().get();
await this.refresh(c);