feat: AI verification code recognition settings
This commit is contained in:
@@ -148,6 +148,9 @@ const en = {
|
|||||||
sendEmail: 'Send Email',
|
sendEmail: 'Send Email',
|
||||||
resendToken: 'Resend Token',
|
resendToken: 'Resend Token',
|
||||||
blackList: 'Blocklist',
|
blackList: 'Blocklist',
|
||||||
|
codeRecognition: 'Verification Code Recognition',
|
||||||
|
codeRecognitionRules: 'Recognition Rules',
|
||||||
|
codeRecognitionRulesDesc: 'Only recognize emails containing the following info. Empty means all.',
|
||||||
oss: 'Object Storage',
|
oss: 'Object Storage',
|
||||||
osDomain: 'Domain',
|
osDomain: 'Domain',
|
||||||
ossDomainDesc: 'Leave empty if using KV storage.',
|
ossDomainDesc: 'Leave empty if using KV storage.',
|
||||||
@@ -315,6 +318,7 @@ const en = {
|
|||||||
blackSubjectDesc: 'Subject',
|
blackSubjectDesc: 'Subject',
|
||||||
blackContentDesc: 'Content',
|
blackContentDesc: 'Content',
|
||||||
blackFromDesc: 'Sender or domain',
|
blackFromDesc: 'Sender or domain',
|
||||||
|
senderRules: 'Sender address or domain',
|
||||||
blackListDesc: 'Reject emails containing the following information',
|
blackListDesc: 'Reject emails containing the following information',
|
||||||
setSuccess: 'Settings saved successfully',
|
setSuccess: 'Settings saved successfully',
|
||||||
details: 'Details',
|
details: 'Details',
|
||||||
|
|||||||
@@ -148,6 +148,9 @@ const zh = {
|
|||||||
sendEmail: '邮件发送',
|
sendEmail: '邮件发送',
|
||||||
resendToken: 'Resend Token',
|
resendToken: 'Resend Token',
|
||||||
blackList: '邮件黑名单',
|
blackList: '邮件黑名单',
|
||||||
|
codeRecognition: '识别验证码',
|
||||||
|
codeRecognitionRules: '识别规则',
|
||||||
|
codeRecognitionRulesDesc: '只识别包含以下信息的邮件,为空识别全部',
|
||||||
oss: '对象存储',
|
oss: '对象存储',
|
||||||
osDomain: '访问域名',
|
osDomain: '访问域名',
|
||||||
ossDomainDesc: '如果是KV存储不要填',
|
ossDomainDesc: '如果是KV存储不要填',
|
||||||
@@ -315,6 +318,7 @@ const zh = {
|
|||||||
blackSubjectDesc: '邮件标题',
|
blackSubjectDesc: '邮件标题',
|
||||||
blackContentDesc: '邮件内容',
|
blackContentDesc: '邮件内容',
|
||||||
blackFromDesc: '发件人邮箱或域名',
|
blackFromDesc: '发件人邮箱或域名',
|
||||||
|
senderRules: '发件人邮箱或域名',
|
||||||
blackListDesc: '拒收包含以下信息的邮件',
|
blackListDesc: '拒收包含以下信息的邮件',
|
||||||
setSuccess: '设置成功',
|
setSuccess: '设置成功',
|
||||||
details: '详情',
|
details: '详情',
|
||||||
|
|||||||
@@ -361,6 +361,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-card">
|
||||||
|
<div class="card-title">Workers AI</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="setting-item">
|
||||||
|
<div><span>{{ $t('codeRecognition') }}</span></div>
|
||||||
|
<div>
|
||||||
|
<el-switch @change="change" :before-change="beforeChange" :active-value="0" :inactive-value="1"
|
||||||
|
v-model="setting.aiCode"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div><span>{{ $t('codeRecognitionRules') }}</span></div>
|
||||||
|
<div class="forward">
|
||||||
|
<el-button class="opt-button" size="small" type="primary" @click="openAiCodeFilter">
|
||||||
|
<Icon icon="fluent:settings-48-regular" width="18" height="18"/>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="settings-card about">
|
<div class="settings-card about">
|
||||||
<div class="card-title">{{ $t('about') }}</div>
|
<div class="card-title">{{ $t('about') }}</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
@@ -759,6 +780,22 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
<el-button type="primary" style="width: 100%;" :loading="settingLoading" @click="saveBlackList">{{ $t('save') }}</el-button>
|
<el-button type="primary" style="width: 100%;" :loading="settingLoading" @click="saveBlackList">{{ $t('save') }}</el-button>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<el-dialog v-model="aiCodeFilterShow" class="forward-dialog" @closed="resetAiCodeFilter">
|
||||||
|
<template #header>
|
||||||
|
<div class="forward-head">
|
||||||
|
<span class="forward-set-title">{{ $t('codeRecognitionRules') }}</span>
|
||||||
|
<el-tooltip effect="dark" :content="$t('codeRecognitionRulesDesc')">
|
||||||
|
<Icon class="warning" icon="fe:warning" width="18" height="18"/>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form>
|
||||||
|
<el-form-item :label="t('senderRules')" label-position="top">
|
||||||
|
<el-input-tag v-model="aiCodeFilter" @add-tag="aiCodeFilterAddTag"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-button type="primary" style="width: 100%;" :loading="settingLoading" @click="saveAiCodeFilter">{{ $t('save') }}</el-button>
|
||||||
|
</el-dialog>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -797,6 +834,7 @@ const userStore = useUserStore();
|
|||||||
const editTitleShow = ref(false)
|
const editTitleShow = ref(false)
|
||||||
const resendTokenFormShow = ref(false)
|
const resendTokenFormShow = ref(false)
|
||||||
const blackFormShow = ref(false)
|
const blackFormShow = ref(false)
|
||||||
|
const aiCodeFilterShow = ref(false)
|
||||||
const r2DomainShow = ref(false)
|
const r2DomainShow = ref(false)
|
||||||
const turnstileShow = ref(false)
|
const turnstileShow = ref(false)
|
||||||
const tgSettingShow = ref(false)
|
const tgSettingShow = ref(false)
|
||||||
@@ -864,6 +902,7 @@ const blackListForm = ref({
|
|||||||
blackContent: [],
|
blackContent: [],
|
||||||
blackFrom: []
|
blackFrom: []
|
||||||
})
|
})
|
||||||
|
const aiCodeFilter = ref([])
|
||||||
|
|
||||||
const authRefreshOptions = computed(() => [
|
const authRefreshOptions = computed(() => [
|
||||||
{label: t('disable'), value: 0},
|
{label: t('disable'), value: 0},
|
||||||
@@ -913,6 +952,7 @@ function getSettings() {
|
|||||||
resetAddS3Form()
|
resetAddS3Form()
|
||||||
resetEmailPrefix()
|
resetEmailPrefix()
|
||||||
resetBlackList()
|
resetBlackList()
|
||||||
|
resetAiCodeFilter()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1192,6 +1232,11 @@ function resetBlackList() {
|
|||||||
blackListForm.value.blackContent = setting.value.blackContent ? setting.value.blackContent.split(',') : []
|
blackListForm.value.blackContent = setting.value.blackContent ? setting.value.blackContent.split(',') : []
|
||||||
blackListForm.value.blackSubject = setting.value.blackSubject ? setting.value.blackSubject.split(',') : []
|
blackListForm.value.blackSubject = setting.value.blackSubject ? setting.value.blackSubject.split(',') : []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetAiCodeFilter() {
|
||||||
|
aiCodeFilter.value = setting.value.aiCodeFilter ? setting.value.aiCodeFilter.split(',') : []
|
||||||
|
}
|
||||||
|
|
||||||
function saveEmailPrefix() {
|
function saveEmailPrefix() {
|
||||||
const form = {}
|
const form = {}
|
||||||
form.minEmailPrefix = minEmailPrefix.value
|
form.minEmailPrefix = minEmailPrefix.value
|
||||||
@@ -1199,6 +1244,10 @@ function saveEmailPrefix() {
|
|||||||
editSetting(form, true)
|
editSetting(form, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveAiCodeFilter() {
|
||||||
|
editSetting({aiCodeFilter: aiCodeFilter.value + ''})
|
||||||
|
}
|
||||||
|
|
||||||
const opacityChange = debounce(doOpacityChange, 1000, {
|
const opacityChange = debounce(doOpacityChange, 1000, {
|
||||||
leading: false,
|
leading: false,
|
||||||
trailing: true
|
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() {
|
function delBackground() {
|
||||||
ElMessageBox.confirm(t('delBackgroundConfirm'), {
|
ElMessageBox.confirm(t('delBackgroundConfirm'), {
|
||||||
@@ -1331,6 +1394,10 @@ function openBlackListForm() {
|
|||||||
blackFormShow.value = true
|
blackFormShow.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAiCodeFilter() {
|
||||||
|
aiCodeFilterShow.value = true
|
||||||
|
}
|
||||||
|
|
||||||
function saveResendToken() {
|
function saveResendToken() {
|
||||||
const settingForm = {
|
const settingForm = {
|
||||||
resendTokens: {}
|
resendTokens: {}
|
||||||
@@ -1408,6 +1475,7 @@ function editSetting(settingForm, refreshStatus = true) {
|
|||||||
noticePopupShow.value = false
|
noticePopupShow.value = false
|
||||||
addS3Show.value = false
|
addS3Show.value = false
|
||||||
emailPrefixShow.value = false
|
emailPrefixShow.value = false
|
||||||
|
aiCodeFilterShow.value = false
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
loginOpacity.value = setting.value.loginOpacity
|
loginOpacity.value = setting.value.loginOpacity
|
||||||
setting.value = {...setting.value, ...JSON.parse(backup)}
|
setting.value = {...setting.value, ...JSON.parse(backup)}
|
||||||
|
|||||||
@@ -128,6 +128,10 @@ export const settingConst = {
|
|||||||
OPEN: 0,
|
OPEN: 0,
|
||||||
CLOSE: 1
|
CLOSE: 1
|
||||||
},
|
},
|
||||||
|
aiCode: {
|
||||||
|
OPEN: 0,
|
||||||
|
CLOSE: 1
|
||||||
|
},
|
||||||
authRefresh: {
|
authRefresh: {
|
||||||
OPEN: 1,
|
OPEN: 1,
|
||||||
CLOSE: 0
|
CLOSE: 0
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ export async function email(message, env, ctx) {
|
|||||||
noRecipient,
|
noRecipient,
|
||||||
blackSubject,
|
blackSubject,
|
||||||
blackContent,
|
blackContent,
|
||||||
blackFrom
|
blackFrom,
|
||||||
|
aiCode,
|
||||||
|
aiCodeFilter
|
||||||
} = await settingService.query({ env });
|
} = await settingService.query({ env });
|
||||||
|
|
||||||
if (receive === settingConst.receive.CLOSE) {
|
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 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 = {
|
const params = {
|
||||||
toEmail: message.to,
|
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) {
|
function checkBlock(blackSubjectStr, blackContentStr, blackFromStr, email) {
|
||||||
|
|
||||||
const blackFromList = blackFromStr ? blackFromStr.split(',') : []
|
const blackFromList = blackFromStr ? blackFromStr.split(',') : []
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const setting = sqliteTable('setting', {
|
|||||||
emailPrefixFilter: text('email_prefix_filter').default('').notNull(),
|
emailPrefixFilter: text('email_prefix_filter').default('').notNull(),
|
||||||
blackSubject: text('black_subject').default('').notNull(),
|
blackSubject: text('black_subject').default('').notNull(),
|
||||||
blackContent: text('black_content').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
|
export default setting
|
||||||
|
|||||||
@@ -49,6 +49,18 @@ const dbInit = {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(`跳过字段:${e.message}`);
|
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) {
|
async v2_9DB(c) {
|
||||||
@@ -592,7 +604,9 @@ const dbInit = {
|
|||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
auto_refresh INTEGER NOT NULL,
|
auto_refresh INTEGER NOT NULL,
|
||||||
register_verify 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();
|
`).run();
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,10 @@ const settingService = {
|
|||||||
params.emailPrefixFilter = params.emailPrefixFilter + '';
|
params.emailPrefixFilter = params.emailPrefixFilter + '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(params.aiCodeFilter)) {
|
||||||
|
params.aiCodeFilter = params.aiCodeFilter + '';
|
||||||
|
}
|
||||||
|
|
||||||
params.resendTokens = JSON.stringify(resendTokens);
|
params.resendTokens = JSON.stringify(resendTokens);
|
||||||
await orm(c).update(setting).set({ ...params }).returning().get();
|
await orm(c).update(setting).set({ ...params }).returning().get();
|
||||||
await this.refresh(c);
|
await this.refresh(c);
|
||||||
|
|||||||
Reference in New Issue
Block a user