Merge pull request #2 from zAhYAng/feat/wecom-notifications

feat: add WeCom message push notifications
This commit is contained in:
Meng Meng
2026-08-11 22:56:19 +08:00
committed by GitHub
18 changed files with 1055 additions and 39 deletions
+51 -1
View File
@@ -6,7 +6,7 @@ import { Select } from "../ui/Select";
import { Switch } from "../ui/Switch";
import { ChannelHeader, EmptyLine, Field, UrlListEditor } from "./controls";
import { HEADER_NAME_SUGGESTIONS, nextHeaderRowId } from "./model";
import type { BarkForm, EmailForm, HeaderRow, WebhookForm } from "./model";
import type { BarkForm, EmailForm, HeaderRow, WebhookForm, WecomForm } from "./model";
const HEADER_LIST_ID = "vocat-webhook-header-names";
@@ -267,3 +267,53 @@ export function WebhookTab({ value, onChange, testing, onTest }: PushChannelProp
</div>
);
}
export function WecomTab({ value, onChange, testing, onTest }: PushChannelProps<WecomForm>) {
const { t, lang } = useI18n();
const off = !value.enabled;
const complete = hasAnyUrl(value.urls) && !!value.payloadTemplate.trim();
return (
<div className="pt-2">
<ChannelHeader
title={t("启用企业微信消息推送")}
enabled={value.enabled}
onToggle={(enabled) => onChange({ enabled })}
actions={
<Button size="small" variant="primary" plain loading={testing} disabled={off || !complete} onClick={onTest}>
{t("测试通知")}
</Button>
}
/>
<SMSOnlyHint />
<div className="space-y-4">
<div className="rounded-lg bg-gray-50 px-3 py-2 text-xs leading-5 text-gray-500 dark:bg-gray-800/60 dark:text-gray-400">
{t("每个企业微信消息推送 Webhook URL 单独占一行,点击添加 URL 新增一行;不使用逗号、空格或换行分隔多个 URL。")}
</div>
<UrlListEditor
urls={value.urls}
onChange={(urls) => onChange({ urls })}
enabled={value.enabled}
placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
emptyText={t("尚未配置任何企业微信消息推送 Webhook URL,点击右侧添加按钮。")}
/>
<Field
label={t("JSON 请求体模板")}
hint={
<>
{t("支持完整企业微信消息推送 JSON。变量必须作为 JSON 值使用,例如")} <code>{"{{message}}"}</code>{lang === "zh" ? "。" : "."}
{t("可用变量:{{event}}、{{title}}、{{message}}、{{timestamp}}、{{content}}、{{number}}、{{device_id}}、{{device_name}}、{{device_label}}、{{time}}。")}
</>
}
>
<Textarea
value={value.payloadTemplate}
onChange={(event) => onChange({ payloadTemplate: event.target.value })}
disabled={off}
rows={12}
className="font-mono text-xs"
/>
</Field>
</div>
</div>
);
}
+47 -17
View File
@@ -47,18 +47,32 @@ export interface EmailForm {
}
export interface PushplusForm {
enabled: boolean;
token: string;
topic: string;
channel: string;
enabled: boolean;
token: string;
topic: string;
channel: string;
}
export interface WecomForm {
enabled: boolean;
urls: string[];
payloadTemplate: string;
}
export const DEFAULT_WECOM_PAYLOAD_TEMPLATE = `{
"msgtype": "text",
"text": {
"content": {{message}}
}
}`;
export interface NotifyForms {
telegram: TelegramForm;
webhook: WebhookForm;
bark: BarkForm;
email: EmailForm;
pushplus: PushplusForm;
email: EmailForm;
pushplus: PushplusForm;
wecom: WecomForm;
}
// 系统保留头,自定义同名头会被忽略(品牌 vocat)
@@ -131,8 +145,9 @@ export function formsFromNotifications(data: Partial<NotificationSettings>): Not
const telegram = asRecord(data.telegram);
const webhook = asRecord(data.webhook);
const bark = asRecord(data.bark);
const email = asRecord(data.email);
const pushplus = asRecord(data.pushplus);
const email = asRecord(data.email);
const pushplus = asRecord(data.pushplus);
const wecom = asRecord(data.wecom);
return {
telegram: {
enabled: !!telegram.enabled,
@@ -171,13 +186,18 @@ export function formsFromNotifications(data: Partial<NotificationSettings>): Not
fromAddress: str(email.fromAddress),
toAddresses: joinList(email.toAddresses),
},
pushplus: {
enabled: !!pushplus.enabled,
token: str(pushplus.token),
topic: str(pushplus.topic),
channel: str(pushplus.channel) || "wechat",
},
};
pushplus: {
enabled: !!pushplus.enabled,
token: str(pushplus.token),
topic: str(pushplus.topic),
channel: str(pushplus.channel) || "wechat",
},
wecom: {
enabled: !!wecom.enabled,
urls: strList(wecom.urls),
payloadTemplate: str(wecom.payloadTemplate ?? wecom.payload_template) || DEFAULT_WECOM_PAYLOAD_TEMPLATE,
},
};
}
function splitList(value: string): string[] {
@@ -226,6 +246,15 @@ export function buildEmailPayload(form: EmailForm, forTest = false) {
};
}
export function buildWecomPayload(form: WecomForm, forTest = false) {
const urls = Array.isArray(form.urls) ? form.urls : [];
return {
enabled: !!form.enabled,
urls: forTest ? urls.map((url) => String(url || "").trim()).filter(Boolean) : urls,
payload_template: String(form.payloadTemplate || ""),
};
}
export function buildNotificationsPayload(forms: NotifyForms) {
return {
telegram: {
@@ -245,6 +274,7 @@ export function buildNotificationsPayload(forms: NotifyForms) {
channel: forms.pushplus.channel || "",
},
webhook: buildWebhookPayload(forms.webhook),
bark: buildBarkPayload(forms.bark),
};
bark: buildBarkPayload(forms.bark),
wecom: buildWecomPayload(forms.wecom),
};
}