From 0650bcd476341cf5c3aab9fb1546c6129fb1d745 Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:14:04 +0800 Subject: [PATCH 1/9] Enhance webhook error handling and logging Refactor webhook error handling and improve logging. --- src/lib/webhook/webhook.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/webhook/webhook.ts b/src/lib/webhook/webhook.ts index 2f30e46..baa7107 100644 --- a/src/lib/webhook/webhook.ts +++ b/src/lib/webhook/webhook.ts @@ -4,16 +4,14 @@ export default async function sendWebhook(payload: string, url: string): Promise return } - const requestHeaders = { - 'Content-Type': 'application/json', - } - try { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 5000) const response = await fetch(url, { method: 'POST', - headers: requestHeaders, + headers: { + 'Content-Type': 'application/json', + }, body: payload, signal: controller.signal }) @@ -21,10 +19,12 @@ export default async function sendWebhook(payload: string, url: string): Promise clearTimeout(timeoutId) if (!response.ok) { - const errorMsg = `HTTP ${response.status}: ${response.statusText}` - console.error('Webhook error:', errorMsg) + const errorBody = await response.text() + console.error('Webhook error:', `HTTP ${response.status}: ${response.statusText}`, errorBody) + } else { + console.log('Webhook sent successfully:', url) } } catch (error) { console.error('Webhook error:', error) } -} \ No newline at end of file +} From 6ab96a5518c378b639ee6e8e40f04aa01f3ca8da Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:16:15 +0800 Subject: [PATCH 2/9] Trim whitespace in webhook and Telegram type checks Refactor webhook and Telegram message checks to trim whitespace from types. --- src/lib/email/store.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/email/store.ts b/src/lib/email/store.ts index 299e4d0..2e07ccc 100644 --- a/src/lib/email/store.ts +++ b/src/lib/email/store.ts @@ -79,12 +79,14 @@ export default async function storeEmail( const res = await emailDB.create(env, emailData); - if (env.WEBHOOK_URL && env.WEBHOOK_TEMPLATE && env.WEBHOOK_TYPE.split(',').includes(emailData.emailType)) { - await sendWebhook(replaceTemplateAdvanced(env.WEBHOOK_TEMPLATE, res), env.WEBHOOK_URL); + if (env.WEBHOOK_URL && env.WEBHOOK_TEMPLATE && env.WEBHOOK_TYPE.split(',').map(t => t.trim()).includes(emailData.emailType)) { + const webhookPayload = replaceTemplateAdvanced(env.WEBHOOK_TEMPLATE, res); + console.log('Sending webhook to:', env.WEBHOOK_URL, 'type:', emailData.emailType); + await sendWebhook(webhookPayload, env.WEBHOOK_URL); } // 发送到Telegram Bot - if (env.TELEGRAM_BOT_TOKEN && env.TELEGRAM_CHAT_ID && env.TELEGRAM_TEMPLATE && env.TELEGRAM_TYPE && env.TELEGRAM_TYPE.split(',').includes(emailData.emailType)) { + if (env.TELEGRAM_BOT_TOKEN && env.TELEGRAM_CHAT_ID && env.TELEGRAM_TEMPLATE && env.TELEGRAM_TYPE && env.TELEGRAM_TYPE.split(',').map(t => t.trim()).includes(emailData.emailType)) { await sendTelegramMessage( replaceTemplateAdvanced(env.TELEGRAM_TEMPLATE, res), env.TELEGRAM_BOT_TOKEN, From 13bc3103fcf4976d6273b71064915fc95e716837 Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:38:28 +0800 Subject: [PATCH 3/9] Enhance webhook logging with payload details Include the webhook payload in the log message when sending a webhook. --- src/lib/email/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/email/store.ts b/src/lib/email/store.ts index 2e07ccc..6a94c38 100644 --- a/src/lib/email/store.ts +++ b/src/lib/email/store.ts @@ -81,7 +81,7 @@ export default async function storeEmail( if (env.WEBHOOK_URL && env.WEBHOOK_TEMPLATE && env.WEBHOOK_TYPE.split(',').map(t => t.trim()).includes(emailData.emailType)) { const webhookPayload = replaceTemplateAdvanced(env.WEBHOOK_TEMPLATE, res); - console.log('Sending webhook to:', env.WEBHOOK_URL, 'type:', emailData.emailType); + console.log('Sending webhook to:', env.WEBHOOK_URL, 'type:', emailData.emailType, 'payload:', webhookPayload); await sendWebhook(webhookPayload, env.WEBHOOK_URL); } From 9f7eb4006c440a17e00f6a1f2350634fbb483e38 Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:39:00 +0800 Subject: [PATCH 4/9] Extend webhook fetch timeout to 10 seconds Increase timeout for webhook fetch request and improve error handling. --- src/lib/webhook/webhook.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/lib/webhook/webhook.ts b/src/lib/webhook/webhook.ts index baa7107..3612996 100644 --- a/src/lib/webhook/webhook.ts +++ b/src/lib/webhook/webhook.ts @@ -6,7 +6,7 @@ export default async function sendWebhook(payload: string, url: string): Promise try { const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort(), 5000) + const timeoutId = setTimeout(() => controller.abort(), 10000) const response = await fetch(url, { method: 'POST', headers: { @@ -18,12 +18,24 @@ export default async function sendWebhook(payload: string, url: string): Promise clearTimeout(timeoutId) + const body = await response.text() + if (!response.ok) { - const errorBody = await response.text() - console.error('Webhook error:', `HTTP ${response.status}: ${response.statusText}`, errorBody) - } else { - console.log('Webhook sent successfully:', url) + console.error('Webhook error:', `HTTP ${response.status}: ${response.statusText}`, body) + return + } + + try { + const result = JSON.parse(body) + if (result.errcode !== undefined && result.errcode !== 0) { + console.error('Webhook API error:', result) + return + } + } catch { + // response is not JSON, ignore } + + console.log('Webhook sent successfully:', url, body) } catch (error) { console.error('Webhook error:', error) } From b14f155b7ea389290699155bf4f97fe7b0f38ba9 Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 09:53:11 +0800 Subject: [PATCH 5/9] Improve webhook payload normalization and error handling --- src/lib/webhook/webhook.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib/webhook/webhook.ts b/src/lib/webhook/webhook.ts index 3612996..4b70ce3 100644 --- a/src/lib/webhook/webhook.ts +++ b/src/lib/webhook/webhook.ts @@ -1,9 +1,25 @@ +function normalizePayload(payload: string): string { + try { + return JSON.stringify(JSON.parse(payload)) + } catch { + // Template contains escaped quotes (e.g. from CI env vars), try to recover + const unescaped = payload.replace(/\\"/g, '"').replace(/\\n/g, '\n') + try { + return JSON.stringify(JSON.parse(unescaped)) + } catch { + return payload + } + } +} + export default async function sendWebhook(payload: string, url: string): Promise { if (!url) { console.error('Webhook error: URL is required') return } + const body = normalizePayload(payload) + try { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 10000) @@ -12,21 +28,21 @@ export default async function sendWebhook(payload: string, url: string): Promise headers: { 'Content-Type': 'application/json', }, - body: payload, + body, signal: controller.signal }) clearTimeout(timeoutId) - const body = await response.text() + const resBody = await response.text() if (!response.ok) { - console.error('Webhook error:', `HTTP ${response.status}: ${response.statusText}`, body) + console.error('Webhook error:', `HTTP ${response.status}: ${response.statusText}`, resBody) return } try { - const result = JSON.parse(body) + const result = JSON.parse(resBody) if (result.errcode !== undefined && result.errcode !== 0) { console.error('Webhook API error:', result) return @@ -35,7 +51,7 @@ export default async function sendWebhook(payload: string, url: string): Promise // response is not JSON, ignore } - console.log('Webhook sent successfully:', url, body) + console.log('Webhook sent successfully:', url, resBody) } catch (error) { console.error('Webhook error:', error) } From cdb5364ec78400681517662450919bbf68b532bd Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:13:29 +0800 Subject: [PATCH 6/9] Improve payload normalization in webhook.ts Refactor payload normalization to handle over-escaped quotes more effectively. --- src/lib/webhook/webhook.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/webhook/webhook.ts b/src/lib/webhook/webhook.ts index 4b70ce3..fc85d11 100644 --- a/src/lib/webhook/webhook.ts +++ b/src/lib/webhook/webhook.ts @@ -1,15 +1,15 @@ function normalizePayload(payload: string): string { try { return JSON.stringify(JSON.parse(payload)) - } catch { - // Template contains escaped quotes (e.g. from CI env vars), try to recover - const unescaped = payload.replace(/\\"/g, '"').replace(/\\n/g, '\n') - try { - return JSON.stringify(JSON.parse(unescaped)) - } catch { - return payload - } - } + } catch {} + + // Over-escaped quotes from CI env var pipeline: \" \\\" \\\\\" etc. + const unescaped = payload.replace(/\\+"/g, '"') + try { + return JSON.stringify(JSON.parse(unescaped)) + } catch {} + + return payload } export default async function sendWebhook(payload: string, url: string): Promise { From 256aac35096e8deea52f901061557e0bf7a9ac3a Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:14:14 +0800 Subject: [PATCH 7/9] Refactor email template replacement function Refactor template replacement logic to use a separate replacer function for clarity and maintainability. --- src/lib/email/store.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/email/store.ts b/src/lib/email/store.ts index 6a94c38..fb9d330 100644 --- a/src/lib/email/store.ts +++ b/src/lib/email/store.ts @@ -9,13 +9,17 @@ import type { Email, NewEmail } from "@/types"; function replaceTemplateAdvanced(template: string, email: Email): string { - return template.replace(/{(\w+)}/g, (match, key) => { + const replacer = (_match: string, key: string): string => { const value = email[key as keyof Email]; if (value === null || value === undefined) { return ''; } return JSON.stringify(String(value)).slice(1, -1); - }); + }; + // Handle {{variable}} first, then remaining {variable} + return template + .replace(/\{\{(\w+)\}\}/g, replacer) + .replace(/{(\w+)}/g, replacer); } export default async function storeEmail( From ddbb6c7667686b6ab5057d47e8569ee594e80a0f Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:39:08 +0800 Subject: [PATCH 8/9] Refactor sendWebhook to use payload directly Removed the normalizePayload function and directly used the payload in the fetch request. --- src/lib/webhook/webhook.ts | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/lib/webhook/webhook.ts b/src/lib/webhook/webhook.ts index fc85d11..13f289b 100644 --- a/src/lib/webhook/webhook.ts +++ b/src/lib/webhook/webhook.ts @@ -1,25 +1,9 @@ -function normalizePayload(payload: string): string { - try { - return JSON.stringify(JSON.parse(payload)) - } catch {} - - // Over-escaped quotes from CI env var pipeline: \" \\\" \\\\\" etc. - const unescaped = payload.replace(/\\+"/g, '"') - try { - return JSON.stringify(JSON.parse(unescaped)) - } catch {} - - return payload -} - export default async function sendWebhook(payload: string, url: string): Promise { if (!url) { console.error('Webhook error: URL is required') return } - const body = normalizePayload(payload) - try { const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), 10000) @@ -28,7 +12,7 @@ export default async function sendWebhook(payload: string, url: string): Promise headers: { 'Content-Type': 'application/json', }, - body, + body: payload, signal: controller.signal }) From a2c6ed33e16521d9c4ff433e16ac3e00aa32c869 Mon Sep 17 00:00:00 2001 From: yytvoo <120768519+yytvoo@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:39:34 +0800 Subject: [PATCH 9/9] Refactor template processing functions for emails --- src/lib/email/store.ts | 58 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/lib/email/store.ts b/src/lib/email/store.ts index fb9d330..fb3ca23 100644 --- a/src/lib/email/store.ts +++ b/src/lib/email/store.ts @@ -8,18 +8,60 @@ import { DEFAULT_EXTRACT_RESULT } from "@/types"; import type { Email, NewEmail } from "@/types"; -function replaceTemplateAdvanced(template: string, email: Email): string { - const replacer = (_match: string, key: string): string => { +function replaceVars(str: string, email: Email): string { + const fn = (_match: string, key: string): string => { const value = email[key as keyof Email]; if (value === null || value === undefined) { return ''; } - return JSON.stringify(String(value)).slice(1, -1); + return String(value); }; - // Handle {{variable}} first, then remaining {variable} + return str + .replace(/\{\{(\w+)\}\}/g, fn) + .replace(/{(\w+)}/g, fn); +} + +function replaceInValue(value: unknown, email: Email): unknown { + if (typeof value === 'string') { + return replaceVars(value, email); + } + if (Array.isArray(value)) { + return value.map(item => replaceInValue(item, email)); + } + if (value !== null && typeof value === 'object') { + const result: Record = {}; + for (const [k, v] of Object.entries(value)) { + result[k] = replaceInValue(v, email); + } + return result; + } + return value; +} + +function processTemplate(template: string, email: Email): string { + // Try to parse as JSON, walk tree, replace vars in string values, then stringify. + // This avoids all escaping issues from CI/config pipeline. + for (const input of [template, template.replace(/\\+"/g, '"')]) { + try { + const parsed = JSON.parse(input); + return JSON.stringify(replaceInValue(parsed, email)); + } catch { + continue; + } + } + + // Fallback: raw string replacement with JSON-safe value escaping return template - .replace(/\{\{(\w+)\}\}/g, replacer) - .replace(/{(\w+)}/g, replacer); + .replace(/\{\{(\w+)\}\}/g, (_match: string, key: string) => { + const value = email[key as keyof Email]; + if (value === null || value === undefined) return ''; + return JSON.stringify(String(value)).slice(1, -1); + }) + .replace(/{(\w+)}/g, (_match: string, key: string) => { + const value = email[key as keyof Email]; + if (value === null || value === undefined) return ''; + return JSON.stringify(String(value)).slice(1, -1); + }); } export default async function storeEmail( @@ -84,7 +126,7 @@ export default async function storeEmail( const res = await emailDB.create(env, emailData); if (env.WEBHOOK_URL && env.WEBHOOK_TEMPLATE && env.WEBHOOK_TYPE.split(',').map(t => t.trim()).includes(emailData.emailType)) { - const webhookPayload = replaceTemplateAdvanced(env.WEBHOOK_TEMPLATE, res); + const webhookPayload = processTemplate(env.WEBHOOK_TEMPLATE, res); console.log('Sending webhook to:', env.WEBHOOK_URL, 'type:', emailData.emailType, 'payload:', webhookPayload); await sendWebhook(webhookPayload, env.WEBHOOK_URL); } @@ -92,7 +134,7 @@ export default async function storeEmail( // 发送到Telegram Bot if (env.TELEGRAM_BOT_TOKEN && env.TELEGRAM_CHAT_ID && env.TELEGRAM_TEMPLATE && env.TELEGRAM_TYPE && env.TELEGRAM_TYPE.split(',').map(t => t.trim()).includes(emailData.emailType)) { await sendTelegramMessage( - replaceTemplateAdvanced(env.TELEGRAM_TEMPLATE, res), + processTemplate(env.TELEGRAM_TEMPLATE, res), env.TELEGRAM_BOT_TOKEN, env.TELEGRAM_CHAT_ID );