feat: add mail id and url in webhook (#443)

This commit is contained in:
Dream Hunter
2024-09-09 22:29:18 +08:00
committed by GitHub
parent 5ece49a576
commit 393c5902c3
11 changed files with 87 additions and 12 deletions
+45 -4
View File
@@ -1,6 +1,7 @@
<script setup> <script setup>
import { defineAsyncComponent } from 'vue' import { defineAsyncComponent, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { useGlobalState } from '../store' import { useGlobalState } from '../store'
import { api } from '../api' import { api } from '../api'
@@ -17,6 +18,7 @@ import About from './common/About.vue';
const SendMail = defineAsyncComponent(() => import('./index/SendMail.vue')); const SendMail = defineAsyncComponent(() => import('./index/SendMail.vue'));
const { settings, openSettings, indexTab, globalTabplacement } = useGlobalState() const { settings, openSettings, indexTab, globalTabplacement } = useGlobalState()
const message = useMessage() const message = useMessage()
const route = useRoute()
const { t } = useI18n({ const { t } = useI18n({
messages: { messages: {
@@ -30,6 +32,7 @@ const { t } = useI18n({
s3Attachment: 'S3 Attachment', s3Attachment: 'S3 Attachment',
saveToS3Success: 'save to s3 success', saveToS3Success: 'save to s3 success',
webhookSettings: 'Webhook Settings', webhookSettings: 'Webhook Settings',
query: 'Query',
}, },
zh: { zh: {
mailbox: '收件箱', mailbox: '收件箱',
@@ -41,11 +44,17 @@ const { t } = useI18n({
s3Attachment: 'S3附件', s3Attachment: 'S3附件',
saveToS3Success: '保存到s3成功', saveToS3Success: '保存到s3成功',
webhookSettings: 'Webhook 设置', webhookSettings: 'Webhook 设置',
query: '查询',
} }
} }
}); });
const fetchMailData = async (limit, offset) => { const fetchMailData = async (limit, offset) => {
if (mailIdQuery.value > 0) {
const singleMail = await api.fetch(`/api/mail/${mailIdQuery.value}`);
if (singleMail) return { results: [singleMail], count: 1 };
return { results: [], count: 0 };
}
return await api.fetch(`/api/mails?limit=${limit}&offset=${offset}`); return await api.fetch(`/api/mails?limit=${limit}&offset=${offset}`);
}; };
@@ -80,6 +89,30 @@ const saveToS3 = async (mail_id, filename, blob) => {
message.error(error.message || "save to s3 error"); message.error(error.message || "save to s3 error");
} }
} }
const mailBoxKey = ref("")
const mailIdQuery = ref("")
const showMailIdQuery = ref(false)
const queryMail = () => {
mailBoxKey.value = Date.now();
}
watch(route, () => {
if (!route.query.mail_id) {
showMailIdQuery.value = false;
mailIdQuery.value = "";
queryMail();
}
})
onMounted(() => {
if (route.query.mail_id) {
showMailIdQuery.value = true;
mailIdQuery.value = route.query.mail_id;
queryMail();
}
})
</script> </script>
<template> <template>
@@ -87,9 +120,17 @@ const saveToS3 = async (mail_id, filename, blob) => {
<AddressBar /> <AddressBar />
<n-tabs v-if="settings.address" type="card" v-model:value="indexTab" :placement="globalTabplacement"> <n-tabs v-if="settings.address" type="card" v-model:value="indexTab" :placement="globalTabplacement">
<n-tab-pane name="mailbox" :tab="t('mailbox')"> <n-tab-pane name="mailbox" :tab="t('mailbox')">
<MailBox :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled" :saveToS3="saveToS3" <div v-if="showMailIdQuery" style="margin-bottom: 10px;">
:enableUserDeleteEmail="openSettings.enableUserDeleteEmail" :fetchMailData="fetchMailData" <n-input-group>
:deleteMail="deleteMail" /> <n-input v-model:value="mailIdQuery" />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
</div>
<MailBox :key="mailBoxKey" :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled"
:saveToS3="saveToS3" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="sendbox" :tab="t('sendbox')"> <n-tab-pane name="sendbox" :tab="t('sendbox')">
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail" <SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
+2
View File
@@ -128,6 +128,8 @@ ENABLE_AUTO_REPLY = false
# TG_BOT_INFO = "{}" # TG_BOT_INFO = "{}"
# global forward address list, if set, all emails will be forwarded to these addresses # global forward address list, if set, all emails will be forwarded to these addresses
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"] # FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
# Frontend URL
# FRONTEND_URL = "https://xxxx.xxx"
[[d1_databases]] [[d1_databases]]
binding = "DB" binding = "DB"
@@ -99,6 +99,8 @@ ENABLE_AUTO_REPLY = false
# TG_BOT_INFO = "{}" # TG_BOT_INFO = "{}"
# 全局转发地址列表,如果不配置则不启用,启用后所有邮件都会转发到列表中的地址 # 全局转发地址列表,如果不配置则不启用,启用后所有邮件都会转发到列表中的地址
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"] # FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
# 前端地址,用于发送 webhook 的邮件 url
# FRONTEND_URL = "https://xxxx.xxx"
# D1 数据库的名称和 ID 可以在 cloudflare 控制台查看 # D1 数据库的名称和 ID 可以在 cloudflare 控制台查看
[[d1_databases]] [[d1_databases]]
@@ -22,12 +22,14 @@ async function saveWebhookSettings(c: Context<HonoCustomType>): Promise<Response
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>();
// random raw email // random raw email
const raw = await c.env.DB.prepare( const { id: mailId, raw } = await c.env.DB.prepare(
`SELECT raw FROM raw_mails ORDER BY RANDOM() LIMIT 1` `SELECT id, raw FROM raw_mails ORDER BY RANDOM() LIMIT 1`
).first<string>("raw"); ).first<{ id: string, raw: string }>() || {};
const parsedEmail = await commonParseMail(raw); const parsedEmail = await commonParseMail(raw);
const res = await sendWebhook(settings, { const res = await sendWebhook(settings, {
id: mailId || "0",
url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
from: parsedEmail?.sender || "test@test.com", from: parsedEmail?.sender || "test@test.com",
to: "admin@test.com", to: "admin@test.com",
subject: parsedEmail?.subject || "test subject", subject: parsedEmail?.subject || "test subject",
+9 -1
View File
@@ -339,6 +339,7 @@ export async function sendWebhook(settings: WebhookSettings, formatMap: WebhookM
); );
/* eslint-enable no-useless-escape */ /* eslint-enable no-useless-escape */
} }
console.log("send webhook", settings.url, settings.method, settings.headers, body);
const response = await fetch(settings.url, { const response = await fetch(settings.url, {
method: settings.method, method: settings.method,
headers: JSON.parse(settings.headers), headers: JSON.parse(settings.headers),
@@ -354,7 +355,8 @@ export async function sendWebhook(settings: WebhookSettings, formatMap: WebhookM
export async function triggerWebhook( export async function triggerWebhook(
c: Context<HonoCustomType>, c: Context<HonoCustomType>,
address: string, address: string,
raw_mail: string raw_mail: string,
message_id: string | null
): Promise<void> { ): Promise<void> {
if (!c.env.KV || !getBooleanValue(c.env.ENABLE_WEBHOOK)) { if (!c.env.KV || !getBooleanValue(c.env.ENABLE_WEBHOOK)) {
return return
@@ -382,8 +384,14 @@ export async function triggerWebhook(
if (webhookList.length === 0) { if (webhookList.length === 0) {
return return
} }
const mailId = await c.env.DB.prepare(
`SELECT id FROM raw_mails where address = ? and message_id = ?`
).bind(address, message_id).first<string>("id");
const parsedEmail = await commonParseMail(raw_mail); const parsedEmail = await commonParseMail(raw_mail);
const webhookMail = { const webhookMail = {
id: mailId || "",
url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
from: parsedEmail?.sender || "", from: parsedEmail?.sender || "",
to: address, to: address,
subject: parsedEmail?.subject || "", subject: parsedEmail?.subject || "",
+1 -1
View File
@@ -50,7 +50,7 @@ async function email(message: ForwardableEmailMessage, env: Bindings, ctx: Execu
try { try {
await triggerWebhook( await triggerWebhook(
{ env: env } as Context<HonoCustomType>, { env: env } as Context<HonoCustomType>,
message.to, rawEmail message.to, rawEmail, message_id
); );
} catch (error) { } catch (error) {
console.log("send webhook error", error); console.log("send webhook error", error);
+9
View File
@@ -32,6 +32,15 @@ api.get('/api/mails', async (c) => {
); );
}) })
api.get('/api/mail/:mail_id', async (c) => {
const { address } = c.get("jwtPayload")
const { mail_id } = c.req.param();
const result = await c.env.DB.prepare(
`SELECT * FROM raw_mails where id = ? and address = ?`
).bind(mail_id, address).first();
return c.json(result);
})
api.delete('/api/mails/:id', async (c) => { api.delete('/api/mails/:id', async (c) => {
if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) { if (!getBooleanValue(c.env.ENABLE_USER_DELETE_EMAIL)) {
return c.text("User delete email is disabled", 403) return c.text("User delete email is disabled", 403)
+5 -3
View File
@@ -36,12 +36,14 @@ 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");
// random raw email // random raw email
const raw = await c.env.DB.prepare( const { id: mailId, raw } = await c.env.DB.prepare(
`SELECT raw FROM raw_mails WHERE address = ? ORDER BY RANDOM() LIMIT 1` `SELECT id, raw FROM raw_mails WHERE address = ? ORDER BY RANDOM() LIMIT 1`
).bind(address).first<string>("raw"); ).bind(address).first<{ id: string, raw: string }>() || {};
const parsedEmail = await commonParseMail(raw); const parsedEmail = await commonParseMail(raw);
const res = await sendWebhook(settings, { const res = await sendWebhook(settings, {
id: mailId || "0",
url: c.env.FRONTEND_URL ? `${c.env.FRONTEND_URL}?mail_id=${mailId}` : "",
from: parsedEmail?.sender || "test@test.com", from: parsedEmail?.sender || "test@test.com",
to: address, to: address,
subject: parsedEmail?.subject || "test subject", subject: parsedEmail?.subject || "test subject",
+4
View File
@@ -22,6 +22,8 @@ export class AdminWebhookSettings {
} }
export type WebhookMail = { export type WebhookMail = {
id: string;
url?: string;
from: string; from: string;
to: string; to: string;
subject: string; subject: string;
@@ -128,6 +130,8 @@ export class WebhookSettings {
"Content-Type": "application/json" "Content-Type": "application/json"
}, null, 2) }, null, 2)
body: string = JSON.stringify({ body: string = JSON.stringify({
"id": "${id}",
"url": "${url}",
"from": "${from}", "from": "${from}",
"to": "${to}", "to": "${to}",
"subject": "${subject}", "subject": "${subject}",
+3
View File
@@ -61,6 +61,9 @@ export type Bindings = {
TELEGRAM_BOT_TOKEN: string TELEGRAM_BOT_TOKEN: string
TG_MAX_ADDRESS: number | undefined TG_MAX_ADDRESS: number | undefined
TG_BOT_INFO: string | object | undefined TG_BOT_INFO: string | object | undefined
// webhook config
FRONTEND_URL: string | undefined
} }
type JwtPayload = { type JwtPayload = {
+2
View File
@@ -70,6 +70,8 @@ ENABLE_AUTO_REPLY = false
# TG_BOT_INFO = "{}" # TG_BOT_INFO = "{}"
# global forward address list, if set, all emails will be forwarded to these addresses # global forward address list, if set, all emails will be forwarded to these addresses
# FORWARD_ADDRESS_LIST = ["xxx@xxx.com"] # FORWARD_ADDRESS_LIST = ["xxx@xxx.com"]
# Frontend URL
# FRONTEND_URL = "https://xxxx.xxx"
[[d1_databases]] [[d1_databases]]
binding = "DB" binding = "DB"