mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-05 23:47:50 +08:00
feat: update webhook to support global webhook (#407)
This commit is contained in:
@@ -14,37 +14,37 @@ const props = defineProps({
|
|||||||
enableUserDeleteEmail: {
|
enableUserDeleteEmail: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
showEMailTo: {
|
showEMailTo: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true,
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
fetchMailData: {
|
fetchMailData: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => { },
|
default: () => { },
|
||||||
requried: true
|
required: true
|
||||||
},
|
},
|
||||||
deleteMail: {
|
deleteMail: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => { },
|
default: () => { },
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
showReply: {
|
showReply: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
showSaveS3: {
|
showSaveS3: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
saveToS3: {
|
saveToS3: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: (mail_id, filename, blob) => { },
|
default: (mail_id, filename, blob) => { },
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const props = defineProps({
|
|||||||
enableUserDeleteEmail: {
|
enableUserDeleteEmail: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
showEMailFrom: {
|
showEMailFrom: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -21,12 +21,12 @@ const props = defineProps({
|
|||||||
fetchMailData: {
|
fetchMailData: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => { },
|
default: () => { },
|
||||||
requried: true
|
required: true
|
||||||
},
|
},
|
||||||
deleteMail: {
|
deleteMail: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: () => { },
|
default: () => { },
|
||||||
requried: false
|
required: false
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
fetchData: {
|
||||||
|
type: Function,
|
||||||
|
default: () => { },
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
saveSettings: {
|
||||||
|
type: Function,
|
||||||
|
default: (webhookSettings: WebhookSettings) => { },
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
testSettings: {
|
||||||
|
type: Function,
|
||||||
|
default: (webhookSettings: WebhookSettings) => { },
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const { t } = useI18n({
|
||||||
|
messages: {
|
||||||
|
en: {
|
||||||
|
successTip: 'Success',
|
||||||
|
test: 'Test',
|
||||||
|
save: 'Save',
|
||||||
|
notEnabled: 'Webhook is not enabled for you',
|
||||||
|
urlMissing: 'URL is required',
|
||||||
|
enable: 'Enable',
|
||||||
|
messagePusherDemo: 'Fill with Message Pusher Demo',
|
||||||
|
messagePusherDoc: 'Message Pusher Doc',
|
||||||
|
fillInDemoTip: 'Please modify the URL and other settings to your own',
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
successTip: '成功',
|
||||||
|
test: '测试',
|
||||||
|
save: '保存',
|
||||||
|
notEnabled: 'Webhook 未开启,请联系管理员开启',
|
||||||
|
urlMissing: 'URL 不能为空',
|
||||||
|
enable: '启用',
|
||||||
|
messagePusherDemo: '填入MessagePusher示例',
|
||||||
|
messagePusherDoc: 'MessagePusher文档',
|
||||||
|
fillInDemoTip: '请修改URL和其他设置为您自己的配置',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class WebhookSettings {
|
||||||
|
enabled: boolean = false
|
||||||
|
url: string = ''
|
||||||
|
method: string = 'POST'
|
||||||
|
headers: string = JSON.stringify({}, null, 2)
|
||||||
|
body: string = JSON.stringify({}, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const messagePusherDocLink = "https://github.com/songquanpeng/message-pusher";
|
||||||
|
|
||||||
|
const messagePusherDemo = {
|
||||||
|
enabled: true,
|
||||||
|
url: 'https://msgpusher.com/push/username',
|
||||||
|
method: 'POST',
|
||||||
|
headers: JSON.stringify({
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}, null, 2),
|
||||||
|
body: JSON.stringify({
|
||||||
|
"token": "token",
|
||||||
|
"title": "${subject}",
|
||||||
|
"description": "${subject}",
|
||||||
|
"content": "*${subject}*\n\nFrom: ${from}\nTo: ${to}\n\n${parsedText}\n"
|
||||||
|
}, null, 2),
|
||||||
|
} as WebhookSettings;
|
||||||
|
|
||||||
|
const fillMessagePuhserDemo = () => {
|
||||||
|
Object.assign(webhookSettings.value, messagePusherDemo)
|
||||||
|
message.success(t('fillInDemoTip'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhookSettings = ref<WebhookSettings>(new WebhookSettings())
|
||||||
|
const enableWebhook = ref(false)
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await props.fetchData()
|
||||||
|
Object.assign(webhookSettings.value, res)
|
||||||
|
enableWebhook.value = true
|
||||||
|
} catch (error) {
|
||||||
|
message.error((error as Error).message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveSettings = async () => {
|
||||||
|
if (!webhookSettings.value.url) {
|
||||||
|
message.error(t('urlMissing'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await props.saveSettings(webhookSettings.value)
|
||||||
|
message.success(t('successTip'))
|
||||||
|
} catch (error) {
|
||||||
|
message.error((error as Error).message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSettings = async () => {
|
||||||
|
if (!webhookSettings.value.url) {
|
||||||
|
message.error(t('urlMissing'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await props.testSettings(webhookSettings.value)
|
||||||
|
message.success(t('successTip'))
|
||||||
|
} catch (error) {
|
||||||
|
message.error((error as Error).message || "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchData();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="center">
|
||||||
|
<n-card :bordered="false" embedded v-if="enableWebhook" style="max-width: 800px; overflow: auto;">
|
||||||
|
<n-flex justify="end">
|
||||||
|
<n-button tag="a" :href="messagePusherDocLink" target="_blank" secondary>
|
||||||
|
{{ t('messagePusherDoc') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="fillMessagePuhserDemo" secondary>
|
||||||
|
{{ t('messagePusherDemo') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button v-if="webhookSettings.enabled" @click="testSettings" secondary>
|
||||||
|
{{ t('test') }}
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="saveSettings" type="primary">
|
||||||
|
{{ t('save') }}
|
||||||
|
</n-button>
|
||||||
|
</n-flex>
|
||||||
|
<n-form-item-row :label="t('enable')">
|
||||||
|
<n-switch v-model:value="webhookSettings.enabled" :round="false" />
|
||||||
|
</n-form-item-row>
|
||||||
|
<div v-if="webhookSettings.enabled">
|
||||||
|
<n-form-item-row label="URL">
|
||||||
|
<n-input v-model:value="webhookSettings.url" />
|
||||||
|
</n-form-item-row>
|
||||||
|
<n-form-item-row label="METHOD">
|
||||||
|
<n-select v-model:value="webhookSettings.method" tag :options='[
|
||||||
|
{ label: "POST", value: "POST" }
|
||||||
|
]' />
|
||||||
|
</n-form-item-row>
|
||||||
|
<n-form-item-row label="HEADERS">
|
||||||
|
<n-input v-model:value="webhookSettings.headers" type="textarea" :autosize="{ minRows: 3 }" />
|
||||||
|
</n-form-item-row>
|
||||||
|
<n-form-item-row label="BODY">
|
||||||
|
<n-input v-model:value="webhookSettings.body" type="textarea" :autosize="{ minRows: 3 }" />
|
||||||
|
</n-form-item-row>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
<n-result v-else status="404" :title="t('notEnabled')" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
text-align: left;
|
||||||
|
place-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.n-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -20,6 +20,7 @@ import Maintenance from './admin/Maintenance.vue';
|
|||||||
import Appearance from './common/Appearance.vue';
|
import Appearance from './common/Appearance.vue';
|
||||||
import Telegram from './admin/Telegram.vue';
|
import Telegram from './admin/Telegram.vue';
|
||||||
import Webhook from './admin/Webhook.vue';
|
import Webhook from './admin/Webhook.vue';
|
||||||
|
import MailWebhook from './admin/MailWebhook.vue';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
adminAuth, showAdminAuth, adminTab, loading,
|
adminAuth, showAdminAuth, adminTab, loading,
|
||||||
@@ -51,12 +52,13 @@ const { t } = useI18n({
|
|||||||
senderAccess: 'Sender Access Control',
|
senderAccess: 'Sender Access Control',
|
||||||
sendBox: 'Send Box',
|
sendBox: 'Send Box',
|
||||||
telegram: 'Telegram Bot',
|
telegram: 'Telegram Bot',
|
||||||
webhook: 'Webhook',
|
webhookSettings: 'Webhook Settings',
|
||||||
statistics: 'Statistics',
|
statistics: 'Statistics',
|
||||||
maintenance: 'Maintenance',
|
maintenance: 'Maintenance',
|
||||||
appearance: 'Appearance',
|
appearance: 'Appearance',
|
||||||
about: 'About',
|
about: 'About',
|
||||||
ok: 'OK',
|
ok: 'OK',
|
||||||
|
mailWebhook: 'Mail Webhook',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
accessHeader: 'Admin 密码',
|
accessHeader: 'Admin 密码',
|
||||||
@@ -72,12 +74,13 @@ const { t } = useI18n({
|
|||||||
senderAccess: '发件权限控制',
|
senderAccess: '发件权限控制',
|
||||||
sendBox: '发件箱',
|
sendBox: '发件箱',
|
||||||
telegram: '电报机器人',
|
telegram: '电报机器人',
|
||||||
webhook: 'Webhook',
|
webhookSettings: 'Webhook 设置',
|
||||||
statistics: '统计',
|
statistics: '统计',
|
||||||
maintenance: '维护',
|
maintenance: '维护',
|
||||||
appearance: '外观',
|
appearance: '外观',
|
||||||
about: '关于',
|
about: '关于',
|
||||||
ok: '确定',
|
ok: '确定',
|
||||||
|
mailWebhook: '邮件 Webhook',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -119,7 +122,7 @@ onMounted(async () => {
|
|||||||
<n-tab-pane name="senderAccess" :tab="t('senderAccess')">
|
<n-tab-pane name="senderAccess" :tab="t('senderAccess')">
|
||||||
<SenderAccess />
|
<SenderAccess />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="webhook" :tab="t('webhook')">
|
<n-tab-pane name="webhook" :tab="t('webhookSettings')">
|
||||||
<Webhook />
|
<Webhook />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
@@ -142,6 +145,9 @@ onMounted(async () => {
|
|||||||
<n-tab-pane name="unknow" :tab="t('unknow')">
|
<n-tab-pane name="unknow" :tab="t('unknow')">
|
||||||
<MailsUnknow />
|
<MailsUnknow />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="mailWebhook" :tab="t('mailWebhook')">
|
||||||
|
<MailWebhook />
|
||||||
|
</n-tab-pane>
|
||||||
</n-tabs>
|
</n-tabs>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="sendBox" :tab="t('sendBox')">
|
<n-tab-pane name="sendBox" :tab="t('sendBox')">
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const { t } = useI18n({
|
|||||||
about: 'About',
|
about: 'About',
|
||||||
s3Attachment: 'S3 Attachment',
|
s3Attachment: 'S3 Attachment',
|
||||||
saveToS3Success: 'save to s3 success',
|
saveToS3Success: 'save to s3 success',
|
||||||
|
webhookSettings: 'Webhook Settings',
|
||||||
},
|
},
|
||||||
zh: {
|
zh: {
|
||||||
mailbox: '收件箱',
|
mailbox: '收件箱',
|
||||||
@@ -39,6 +40,7 @@ const { t } = useI18n({
|
|||||||
about: '关于',
|
about: '关于',
|
||||||
s3Attachment: 'S3附件',
|
s3Attachment: 'S3附件',
|
||||||
saveToS3Success: '保存到s3成功',
|
saveToS3Success: '保存到s3成功',
|
||||||
|
webhookSettings: 'Webhook 设置',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -102,7 +104,7 @@ const saveToS3 = async (mail_id, filename, blob) => {
|
|||||||
<n-tab-pane v-if="openSettings.enableAutoReply" name="auto_reply" :tab="t('auto_reply')">
|
<n-tab-pane v-if="openSettings.enableAutoReply" name="auto_reply" :tab="t('auto_reply')">
|
||||||
<AutoReply />
|
<AutoReply />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane v-if="openSettings.enableWebhook" name="webhook" :tab="t('webhook')">
|
<n-tab-pane v-if="openSettings.enableWebhook" name="webhook" :tab="t('webhookSettings')">
|
||||||
<Webhook />
|
<Webhook />
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane v-if="openSettings.isS3Enabled" name="s3_attachment" :tab="t('s3Attachment')">
|
<n-tab-pane v-if="openSettings.isS3Enabled" name="s3_attachment" :tab="t('s3Attachment')">
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
// @ts-ignore
|
||||||
|
import { api } from '../../api'
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import WebhookComponent from '../../components/WebhookComponent.vue'
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
return await api.fetch(`/admin/mail_webhook/settings`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveSettings = async (webhookSettings: any) => {
|
||||||
|
await api.fetch(`/admin/mail_webhook/settings`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(webhookSettings),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSettings = async (webhookSettings: any) => {
|
||||||
|
await api.fetch(`/admin/mail_webhook/test`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(webhookSettings),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<WebhookComponent :fetchData="fetchData" :saveSettings="saveSettings" :testSettings="testSettings" />
|
||||||
|
</template>
|
||||||
@@ -15,7 +15,7 @@ const props = defineProps({
|
|||||||
bindUserAddress: {
|
bindUserAddress: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: async () => { await api.bindUserAddress(); },
|
default: async () => { await api.bindUserAddress(); },
|
||||||
requried: true
|
required: true
|
||||||
},
|
},
|
||||||
newAddressPath: {
|
newAddressPath: {
|
||||||
type: Function,
|
type: Function,
|
||||||
@@ -29,7 +29,7 @@ const props = defineProps({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
requried: true
|
required: true
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -151,16 +151,15 @@ onMounted(async () => {
|
|||||||
<n-button type="primary" tertiary @click="requestAccess" size="small">{{ t('requestAccess')
|
<n-button type="primary" tertiary @click="requestAccess" size="small">{{ t('requestAccess')
|
||||||
}}</n-button>
|
}}</n-button>
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<br />
|
|
||||||
<AdminContact />
|
<AdminContact />
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<n-alert type="info" :show-icon="false" :bordered="false">
|
<n-alert type="info" :show-icon="false" :bordered="false" closable>
|
||||||
{{ t('send_balance') }}: {{ settings.send_balance }}
|
{{ t('send_balance') }}: {{ settings.send_balance }}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<div class="right">
|
<n-flex justify="end">
|
||||||
<n-button type="primary" @click="send">{{ t('send') }}</n-button>
|
<n-button type="primary" @click="send">{{ t('send') }}</n-button>
|
||||||
</div>
|
</n-flex>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<n-form :model="sendMailModel">
|
<n-form :model="sendMailModel">
|
||||||
<n-form-item :label="t('fromName')" label-placement="top">
|
<n-form-item :label="t('fromName')" label-placement="top">
|
||||||
@@ -232,9 +231,7 @@ onMounted(async () => {
|
|||||||
justify-content: left;
|
justify-content: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right {
|
.n-alert {
|
||||||
text-align: right;
|
margin-bottom: 10px;
|
||||||
place-items: right;
|
|
||||||
justify-content: right;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,129 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
import { useGlobalState } from '../../store'
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { api } from '../../api'
|
import { api } from '../../api'
|
||||||
|
|
||||||
const { settings } = useGlobalState()
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const message = useMessage()
|
import WebhookComponent from '../../components/WebhookComponent.vue'
|
||||||
|
|
||||||
const { t } = useI18n({
|
|
||||||
messages: {
|
|
||||||
en: {
|
|
||||||
successTip: 'Success',
|
|
||||||
test: 'Test',
|
|
||||||
save: 'Save',
|
|
||||||
notEnabled: 'Webhook is not enabled for you',
|
|
||||||
urlMissing: 'URL is required',
|
|
||||||
},
|
|
||||||
zh: {
|
|
||||||
successTip: '成功',
|
|
||||||
test: '测试',
|
|
||||||
save: '保存',
|
|
||||||
notEnabled: 'Webhook 未开启,请联系管理员开启',
|
|
||||||
urlMissing: 'URL 不能为空',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
class WebhookSettings {
|
|
||||||
url: string = ''
|
|
||||||
method: string = 'POST'
|
|
||||||
headers: string = JSON.stringify({}, null, 2)
|
|
||||||
body: string = JSON.stringify({}, null, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
const webhookSettings = ref<WebhookSettings>(new WebhookSettings())
|
|
||||||
const enableWebhook = ref(false)
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
return await api.fetch(`/api/webhook/settings`)
|
||||||
const res = await api.fetch(`/api/webhook/settings`)
|
|
||||||
Object.assign(webhookSettings.value, res)
|
|
||||||
enableWebhook.value = true
|
|
||||||
} catch (error) {
|
|
||||||
message.error((error as Error).message || "error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveSettings = async () => {
|
const saveSettings = async (webhookSettings: any) => {
|
||||||
if (!webhookSettings.value.url) {
|
|
||||||
message.error(t('urlMissing'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await api.fetch(`/api/webhook/settings`, {
|
await api.fetch(`/api/webhook/settings`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(webhookSettings.value),
|
body: JSON.stringify(webhookSettings),
|
||||||
})
|
})
|
||||||
message.success(t('successTip'))
|
|
||||||
} catch (error) {
|
|
||||||
message.error((error as Error).message || "error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const testSettings = async () => {
|
const testSettings = async (webhookSettings: any) => {
|
||||||
if (!webhookSettings.value.url) {
|
|
||||||
message.error(t('urlMissing'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await api.fetch(`/api/webhook/test`, {
|
await api.fetch(`/api/webhook/test`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(webhookSettings.value),
|
body: JSON.stringify(webhookSettings),
|
||||||
})
|
})
|
||||||
message.success(t('successTip'))
|
|
||||||
} catch (error) {
|
|
||||||
message.error((error as Error).message || "error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await fetchData();
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="center" v-if="settings.address">
|
<WebhookComponent :fetchData="fetchData" :saveSettings="saveSettings" :testSettings="testSettings" />
|
||||||
<n-card :bordered="false" embedded v-if="enableWebhook" style="max-width: 800px; overflow: auto;">
|
|
||||||
<n-form-item-row label="URL">
|
|
||||||
<n-input v-model:value="webhookSettings.url" />
|
|
||||||
</n-form-item-row>
|
|
||||||
<n-form-item-row label="METHOD">
|
|
||||||
<n-select v-model:value="webhookSettings.method" tag :options='[
|
|
||||||
{ label: "POST", value: "POST" }
|
|
||||||
]' />
|
|
||||||
</n-form-item-row>
|
|
||||||
<n-form-item-row label="HEADERS">
|
|
||||||
<n-input v-model:value="webhookSettings.headers" type="textarea" :autosize="{ minRows: 3 }" />
|
|
||||||
</n-form-item-row>
|
|
||||||
<n-form-item-row label="BODY">
|
|
||||||
<n-input v-model:value="webhookSettings.body" type="textarea" :autosize="{ minRows: 3 }" />
|
|
||||||
</n-form-item-row>
|
|
||||||
<n-button @click="testSettings" secondary block strong>
|
|
||||||
{{ t('test') }}
|
|
||||||
</n-button>
|
|
||||||
<n-button @click="saveSettings" type="primary" block>
|
|
||||||
{{ t('save') }}
|
|
||||||
</n-button>
|
|
||||||
</n-card>
|
|
||||||
<n-result v-else status="404" :title="t('notEnabled')" />
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.center {
|
|
||||||
display: flex;
|
|
||||||
text-align: left;
|
|
||||||
place-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.n-button {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> 注意字符串格式的变量的最外层的引号是不需要的
|
> 注意字符串格式的变量的最外层的引号是不需要的
|
||||||
|
>
|
||||||
|
> - 对于 `USER_ROLES` 请配置为此格式 `[{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"vip","prefix":"vip"},{"domains":["awsl.uk","dreamhunter2333.xyz"],"role":"admin","prefix":""}]`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { CONSTANTS } from '../constants'
|
|||||||
import cleanup_api from './cleanup_api'
|
import cleanup_api from './cleanup_api'
|
||||||
import admin_user_api from './admin_user_api'
|
import admin_user_api from './admin_user_api'
|
||||||
import webhook_settings from './webhook_settings'
|
import webhook_settings from './webhook_settings'
|
||||||
|
import mail_webhook_settings from './mail_webhook_settings'
|
||||||
|
|
||||||
export const api = new Hono<HonoCustomType>()
|
export const api = new Hono<HonoCustomType>()
|
||||||
|
|
||||||
@@ -291,9 +292,12 @@ api.post('/admin/account_settings', async (c) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// cleanup
|
||||||
api.post('/admin/cleanup', cleanup_api.cleanup)
|
api.post('/admin/cleanup', cleanup_api.cleanup)
|
||||||
api.get('/admin/auto_cleanup', cleanup_api.getCleanup)
|
api.get('/admin/auto_cleanup', cleanup_api.getCleanup)
|
||||||
api.post('/admin/auto_cleanup', cleanup_api.saveCleanup)
|
api.post('/admin/auto_cleanup', cleanup_api.saveCleanup)
|
||||||
|
|
||||||
|
// user settings
|
||||||
api.get('/admin/user_settings', admin_user_api.getSetting)
|
api.get('/admin/user_settings', admin_user_api.getSetting)
|
||||||
api.post('/admin/user_settings', admin_user_api.saveSetting)
|
api.post('/admin/user_settings', admin_user_api.saveSetting)
|
||||||
api.get('/admin/users', admin_user_api.getUsers)
|
api.get('/admin/users', admin_user_api.getUsers)
|
||||||
@@ -302,5 +306,12 @@ api.post('/admin/users', admin_user_api.createUser)
|
|||||||
api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword)
|
api.post('/admin/users/:user_id/reset_password', admin_user_api.resetPassword)
|
||||||
api.get('/admin/user_roles', async (c) => c.json(getUserRoles(c)))
|
api.get('/admin/user_roles', async (c) => c.json(getUserRoles(c)))
|
||||||
api.post('/admin/user_roles', admin_user_api.updateUserRoles)
|
api.post('/admin/user_roles', admin_user_api.updateUserRoles)
|
||||||
|
|
||||||
|
// webhook settings
|
||||||
api.get("/admin/webhook/settings", webhook_settings.getWebhookSettings);
|
api.get("/admin/webhook/settings", webhook_settings.getWebhookSettings);
|
||||||
api.post("/admin/webhook/settings", webhook_settings.saveWebhookSettings);
|
api.post("/admin/webhook/settings", webhook_settings.saveWebhookSettings);
|
||||||
|
|
||||||
|
// mail webhook settings
|
||||||
|
api.get("/admin/mail_webhook/settings", mail_webhook_settings.getWebhookSettings);
|
||||||
|
api.post("/admin/mail_webhook/settings", mail_webhook_settings.saveWebhookSettings);
|
||||||
|
api.post("/admin/mail_webhook/test", mail_webhook_settings.testWebhookSettings);
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { Context } from "hono";
|
||||||
|
import { HonoCustomType } from "../types";
|
||||||
|
import { CONSTANTS } from "../constants";
|
||||||
|
import { WebhookSettings } from "../models";
|
||||||
|
import { getBooleanValue } from "../utils";
|
||||||
|
import { commonParseMail, sendWebhook } from "../common";
|
||||||
|
|
||||||
|
async function getWebhookSettings(c: Context<HonoCustomType>): 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 settings = await c.env.KV.get<WebhookSettings>(
|
||||||
|
CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY, "json"
|
||||||
|
) || new WebhookSettings();
|
||||||
|
return c.json(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||||
|
const settings = await c.req.json<WebhookSettings>();
|
||||||
|
await c.env.KV.put(
|
||||||
|
CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY,
|
||||||
|
JSON.stringify(settings));
|
||||||
|
return c.json({ success: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||||
|
const settings = await c.req.json<WebhookSettings>();
|
||||||
|
// random raw email
|
||||||
|
const raw = await c.env.DB.prepare(
|
||||||
|
`SELECT raw FROM raw_mails ORDER BY RANDOM() LIMIT 1`
|
||||||
|
).first<string>("raw");
|
||||||
|
|
||||||
|
const parsedEmail = await commonParseMail(raw);
|
||||||
|
const res = await sendWebhook(settings, {
|
||||||
|
from: parsedEmail?.sender || "test@test.com",
|
||||||
|
to: "admin@test.com",
|
||||||
|
subject: parsedEmail?.subject || "test subject",
|
||||||
|
raw: raw || "test raw email",
|
||||||
|
parsedText: parsedEmail?.text || "test parsed text",
|
||||||
|
parsedHtml: parsedEmail?.html || "test parsed html"
|
||||||
|
});
|
||||||
|
if (!res.success) {
|
||||||
|
return c.text(res.message || "send webhook error", 400);
|
||||||
|
}
|
||||||
|
return c.json({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getWebhookSettings,
|
||||||
|
saveWebhookSettings,
|
||||||
|
testWebhookSettings,
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import { Jwt } from 'hono/utils/jwt'
|
|||||||
import { getBooleanValue, getDomains, getStringValue, getIntValue, getUserRoles, getDefaultDomains } from './utils';
|
import { getBooleanValue, getDomains, getStringValue, getIntValue, getUserRoles, getDefaultDomains } from './utils';
|
||||||
import { HonoCustomType, UserRole } from './types';
|
import { HonoCustomType, UserRole } from './types';
|
||||||
import { unbindTelegramByAddress } from './telegram_api/common';
|
import { unbindTelegramByAddress } from './telegram_api/common';
|
||||||
|
import { CONSTANTS } from './constants';
|
||||||
|
import { AdminWebhookSettings, WebhookMail, WebhookSettings } from './models';
|
||||||
|
|
||||||
const DEFAULT_NAME_REGEX = /[^a-z0-9]/g;
|
const DEFAULT_NAME_REGEX = /[^a-z0-9]/g;
|
||||||
|
|
||||||
@@ -273,3 +275,76 @@ export const getAllowDomains = async (c: Context<HonoCustomType>): Promise<strin
|
|||||||
const user_role = await commonGetUserRole(c, user.user_id);
|
const user_role = await commonGetUserRole(c, user.user_id);
|
||||||
return user_role?.domains || getDefaultDomains(c);;
|
return user_role?.domains || getDefaultDomains(c);;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export 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)) {
|
||||||
|
/* eslint-disable no-useless-escape */
|
||||||
|
body = body.replace(
|
||||||
|
new RegExp(`\\$\\{${key}\\}`, "g"),
|
||||||
|
JSON.stringify(
|
||||||
|
formatMap[key as keyof WebhookMail]
|
||||||
|
).replace(/^"(.*)"$/, '\$1')
|
||||||
|
);
|
||||||
|
/* eslint-enable no-useless-escape */
|
||||||
|
}
|
||||||
|
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 triggerWebhook(
|
||||||
|
c: Context<HonoCustomType>,
|
||||||
|
address: string,
|
||||||
|
raw_mail: string
|
||||||
|
): Promise<void> {
|
||||||
|
if (!c.env.KV || !getBooleanValue(c.env.ENABLE_WEBHOOK)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const webhookList: WebhookSettings[] = []
|
||||||
|
|
||||||
|
// admin mail webhook
|
||||||
|
const adminMailWebhookSettings = await c.env.KV.get<WebhookSettings>(CONSTANTS.WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY, "json");
|
||||||
|
if (adminMailWebhookSettings?.enabled) {
|
||||||
|
webhookList.push(adminMailWebhookSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// user mail webhook
|
||||||
|
const adminSettings = await c.env.KV.get<AdminWebhookSettings>(CONSTANTS.WEBHOOK_KV_SETTINGS_KEY, "json");
|
||||||
|
if (adminSettings?.allowList.includes(address)) {
|
||||||
|
const settings = await c.env.KV.get<WebhookSettings>(
|
||||||
|
`${CONSTANTS.WEBHOOK_KV_USER_SETTINGS_KEY}:${address}`, "json"
|
||||||
|
);
|
||||||
|
if (settings?.enabled) {
|
||||||
|
webhookList.push(settings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no webhook
|
||||||
|
if (webhookList.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const parsedEmail = await commonParseMail(raw_mail);
|
||||||
|
const webhookMail = {
|
||||||
|
from: parsedEmail?.sender || "",
|
||||||
|
to: address,
|
||||||
|
subject: parsedEmail?.subject || "",
|
||||||
|
raw: raw_mail,
|
||||||
|
parsedText: parsedEmail?.text || "",
|
||||||
|
parsedHtml: parsedEmail?.html || ""
|
||||||
|
}
|
||||||
|
for (const settings of webhookList) {
|
||||||
|
const res = await sendWebhook(settings, webhookMail);
|
||||||
|
if (!res.success) {
|
||||||
|
console.error(res.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ export const CONSTANTS = {
|
|||||||
WEBHOOK_KV_SETTINGS_KEY: "temp-mail-webhook-settings",
|
WEBHOOK_KV_SETTINGS_KEY: "temp-mail-webhook-settings",
|
||||||
WEBHOOK_KV_USER_SETTINGS_KEY: "temp-mail-webhook-user-settings",
|
WEBHOOK_KV_USER_SETTINGS_KEY: "temp-mail-webhook-user-settings",
|
||||||
EMAIL_KV_BLACK_LIST: "temp-mail-email-black-list",
|
EMAIL_KV_BLACK_LIST: "temp-mail-email-black-list",
|
||||||
|
WEBHOOK_KV_ADMIN_MAIL_SETTINGS_KEY: "temp-mail-webhook-admin-mail-settings",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { getEnvStringList } from "../utils";
|
|||||||
import { sendMailToTelegram } from "../telegram_api";
|
import { sendMailToTelegram } from "../telegram_api";
|
||||||
import { Bindings, HonoCustomType } from "../types";
|
import { Bindings, HonoCustomType } from "../types";
|
||||||
import { auto_reply } from "./auto_reply";
|
import { auto_reply } from "./auto_reply";
|
||||||
import { trigerWebhook } from "../mails_api/webhook_settings";
|
|
||||||
import { isBlocked } from "./black_list";
|
import { isBlocked } from "./black_list";
|
||||||
|
import { triggerWebhook } from "../common";
|
||||||
|
|
||||||
|
|
||||||
async function email(message: ForwardableEmailMessage, env: Bindings, ctx: ExecutionContext) {
|
async function email(message: ForwardableEmailMessage, env: Bindings, ctx: ExecutionContext) {
|
||||||
@@ -48,7 +48,7 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
|
|||||||
|
|
||||||
// send webhook
|
// send webhook
|
||||||
try {
|
try {
|
||||||
await trigerWebhook(
|
await triggerWebhook(
|
||||||
{ env: env } as Context<HonoCustomType>,
|
{ env: env } as Context<HonoCustomType>,
|
||||||
message.to, rawEmail
|
message.to, rawEmail
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,26 +1,9 @@
|
|||||||
import { Context } from "hono";
|
import { Context } from "hono";
|
||||||
import { HonoCustomType } from "../types";
|
import { HonoCustomType } from "../types";
|
||||||
import { CONSTANTS } from "../constants";
|
import { CONSTANTS } from "../constants";
|
||||||
import { AdminWebhookSettings, WebhookMail } from "../models";
|
import { AdminWebhookSettings, WebhookSettings } from "../models";
|
||||||
import { getBooleanValue } from "../utils";
|
import { getBooleanValue } from "../utils";
|
||||||
import { commonParseMail } from "../common";
|
import { commonParseMail, sendWebhook } from "../common";
|
||||||
|
|
||||||
|
|
||||||
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}",
|
|
||||||
"subject": "${subject}",
|
|
||||||
"raw": "${raw}",
|
|
||||||
"parsedText": "${parsedText}",
|
|
||||||
"parsedHtml": "${parsedHtml}",
|
|
||||||
}, null, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function getWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
async function getWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||||
@@ -55,63 +38,6 @@ async function saveWebhookSettings(c: Context<HonoCustomType>): Promise<Response
|
|||||||
return c.json({ success: true })
|
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)) {
|
|
||||||
/* eslint-disable no-useless-escape */
|
|
||||||
body = body.replace(
|
|
||||||
new RegExp(`\\$\\{${key}\\}`, "g"),
|
|
||||||
JSON.stringify(
|
|
||||||
formatMap[key as keyof WebhookMail]
|
|
||||||
).replace(/^"(.*)"$/, '\$1')
|
|
||||||
);
|
|
||||||
/* eslint-enable no-useless-escape */
|
|
||||||
}
|
|
||||||
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<HonoCustomType>,
|
|
||||||
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 commonParseMail(raw_mail);
|
|
||||||
const res = await sendWebhook(settings, {
|
|
||||||
from: parsedEmail?.sender || "",
|
|
||||||
to: address,
|
|
||||||
subject: parsedEmail?.subject || "",
|
|
||||||
raw: raw_mail,
|
|
||||||
parsedText: parsedEmail?.text || "",
|
|
||||||
parsedHtml: parsedEmail?.html || ""
|
|
||||||
});
|
|
||||||
if (!res.success) {
|
|
||||||
console.log(res.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
async function testWebhookSettings(c: Context<HonoCustomType>): Promise<Response> {
|
||||||
const settings = await c.req.json<WebhookSettings>();
|
const settings = await c.req.json<WebhookSettings>();
|
||||||
const { address } = c.get("jwtPayload");
|
const { address } = c.get("jwtPayload");
|
||||||
|
|||||||
@@ -119,3 +119,20 @@ export class UserInfo {
|
|||||||
this.userEmail = userEmail;
|
this.userEmail = userEmail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class WebhookSettings {
|
||||||
|
enabled: boolean = false
|
||||||
|
url: string = ''
|
||||||
|
method: string = 'POST'
|
||||||
|
headers: string = JSON.stringify({
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}, null, 2)
|
||||||
|
body: string = JSON.stringify({
|
||||||
|
"from": "${from}",
|
||||||
|
"to": "${to}",
|
||||||
|
"subject": "${subject}",
|
||||||
|
"raw": "${raw}",
|
||||||
|
"parsedText": "${parsedText}",
|
||||||
|
"parsedHtml": "${parsedHtml}",
|
||||||
|
}, null, 2)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user