feat: 支持自定义 webhook 请求的 headers,更新说明内容 (#693)

This commit is contained in:
ᴀᴍᴛᴏᴀᴇʀ
2026-03-31 01:49:32 +08:00
committed by GitHub
parent 55dde84f96
commit 91ab64a068
3 changed files with 81 additions and 17 deletions

View File

@@ -1,6 +1,8 @@
mod info;
mod message;
use std::collections::HashMap;
use anyhow::Result;
use futures::future;
pub use info::DownloadNotifyInfo;
@@ -20,6 +22,8 @@ pub enum Notifier {
Webhook {
url: String,
template: Option<String>,
#[serde(default)]
headers: Option<HashMap<String, String>>,
#[serde(skip)]
// 一个内部辅助字段,用于决定是否强制渲染当前模板,在测试时使用
ignore_cache: Option<()>,
@@ -74,6 +78,7 @@ impl Notifier {
Notifier::Webhook {
url,
template,
headers,
ignore_cache,
} => {
let key = webhook_template_key(url);
@@ -82,12 +87,20 @@ impl Notifier {
Some(_) => handlebar.render_template(webhook_template_content(template), &message)?,
None => handlebar.render(&key, &message)?,
};
client
.post(url)
.header(header::CONTENT_TYPE, "application/json")
.body(payload)
.send()
.await?;
let mut headers_map = header::HeaderMap::new();
headers_map.insert(header::CONTENT_TYPE, "application/json".try_into()?);
if let Some(custom_headers) = headers {
for (key, value) in custom_headers {
if let (Ok(key), Ok(value)) =
(header::HeaderName::try_from(key), header::HeaderValue::try_from(value))
{
headers_map.insert(key, value);
}
}
}
client.post(url).headers(headers_map).body(payload).send().await?;
}
}
Ok(())

View File

@@ -311,6 +311,7 @@ export interface WebhookNotifier {
type: 'webhook';
url: string;
template?: string | null;
headers?: Record<string, string> | null;
}
export type Notifier = TelegramNotifier | WebhookNotifier;

View File

@@ -5,8 +5,6 @@
import { toast } from 'svelte-sonner';
import type { Notifier } from '$lib/types';
const jsonExample = '{"text": "您的消息内容"}';
export let notifier: Notifier | null = null;
export let onSave: (notifier: Notifier) => void;
export let onCancel: () => void;
@@ -16,6 +14,7 @@
let chatId = '';
let webhookUrl = '';
let webhookTemplate = '';
let webhookHeaders: { key: string; value: string }[] = [];
// 初始化表单
$: {
@@ -28,6 +27,11 @@
type = 'webhook';
webhookUrl = notifier.url;
webhookTemplate = notifier.template || '';
if (notifier.headers) {
webhookHeaders = Object.entries(notifier.headers).map(([key, value]) => ({ key, value }));
} else {
webhookHeaders = [];
}
}
} else {
type = 'telegram';
@@ -35,11 +39,11 @@
chatId = '';
webhookUrl = '';
webhookTemplate = '';
webhookHeaders = [];
}
}
function handleSave() {
// 验证表单
if (type === 'telegram') {
if (!botToken.trim()) {
toast.error('请输入 Bot Token');
@@ -62,7 +66,6 @@
return;
}
// 简单的 URL 验证
try {
new URL(webhookUrl.trim());
} catch {
@@ -70,10 +73,20 @@
return;
}
const headers: Record<string, string> = {};
for (const { key, value } of webhookHeaders) {
const trimmedKey = key.trim();
const trimmedValue = value.trim();
if (trimmedKey && trimmedValue) {
headers[trimmedKey] = trimmedValue;
}
}
const newNotifier: Notifier = {
type: 'webhook',
url: webhookUrl.trim(),
template: webhookTemplate.trim() || null
template: webhookTemplate.trim() || null,
headers: Object.keys(headers).length > 0 ? headers : null
};
onSave(newNotifier);
}
@@ -111,11 +124,7 @@
{:else if type === 'webhook'}
<div class="space-y-2">
<Label for="webhook-url">Webhook URL</Label>
<Input id="webhook-url" placeholder="https://example.com/webhook" bind:value={webhookUrl} />
<p class="text-muted-foreground text-xs">
接收通知的 Webhook 地址<br />
格式示例:{jsonExample}
</p>
<Input id="webhook-url" placeholder="请输入 Webhook 地址" bind:value={webhookUrl} />
</div>
<div class="space-y-2">
<Label for="webhook-template">模板(可选)</Label>
@@ -127,7 +136,48 @@
></textarea>
<p class="text-muted-foreground text-xs">
用于渲染 Webhook 的 Handlebars 模板。如果不填写,将使用默认模板。<br />
可用变量:<code class="text-xs">message</code>(通知内容)
可用变量:<code class="text-xs">message</code>(通知内容)<code class="text-xs"
>image_url</code
>(封面图片地址,无图时为 null
</p>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between">
<Label>自定义请求头(可选)</Label>
<Button
variant="ghost"
size="sm"
onclick={() => (webhookHeaders = [...webhookHeaders, { key: '', value: '' }])}
>
+ 添加请求头
</Button>
</div>
{#each webhookHeaders as header, index (index)}
<div class="flex items-center gap-2">
<Input
placeholder="Header 名称(例如 Authorization"
bind:value={header.key}
class="flex-1"
/>
<Input
placeholder="Header 值"
bind:value={header.value}
class="flex-1"
type={header.key.toLowerCase() === 'authorization' ? 'password' : 'text'}
/>
<Button
variant="ghost"
size="sm"
onclick={() => (webhookHeaders = webhookHeaders.filter((_, i) => i !== index))}
class="h-10 px-2"
>
×
</Button>
</div>
{/each}
<p class="text-muted-foreground text-xs">
添加自定义请求头例如Authorization: Bearer your_token
</p>
</div>
{/if}