mirror of
https://github.com/MengMengCode/VoCat.git
synced 2026-08-18 13:53:42 +08:00
324 lines
9.1 KiB
TypeScript
324 lines
9.1 KiB
TypeScript
import type { NotificationSettings } from "../../types";
|
|
|
|
// ---- 页面专属表单类型(camelCase,对应参考里的各 channel 表单) ----
|
|
|
|
export interface TelegramForm {
|
|
enabled: boolean;
|
|
botToken: string;
|
|
chatId: string;
|
|
adminId: string;
|
|
baseUrl: string;
|
|
proxy: string;
|
|
}
|
|
|
|
export interface HeaderRow {
|
|
id: number;
|
|
key: string;
|
|
value: string;
|
|
}
|
|
|
|
export interface WebhookForm {
|
|
enabled: boolean;
|
|
urls: string[];
|
|
secret: string;
|
|
timeoutMs: number;
|
|
retryMax: number;
|
|
textTemplate: string;
|
|
headers: HeaderRow[];
|
|
}
|
|
|
|
export interface BarkForm {
|
|
enabled: boolean;
|
|
urls: string[];
|
|
group: string;
|
|
icon: string;
|
|
level: string;
|
|
}
|
|
|
|
export interface EmailForm {
|
|
enabled: boolean;
|
|
useSsl: boolean;
|
|
smtpHost: string;
|
|
smtpPort: string;
|
|
username: string;
|
|
password: string;
|
|
fromAddress: string;
|
|
toAddresses: string; // 逗号分隔
|
|
}
|
|
|
|
export interface PushplusForm {
|
|
enabled: boolean;
|
|
token: string;
|
|
topic: string;
|
|
channel: string;
|
|
}
|
|
|
|
export interface WecomForm {
|
|
enabled: boolean;
|
|
urls: string[];
|
|
payloadTemplate: string;
|
|
}
|
|
|
|
export interface LarkForm {
|
|
enabled: boolean;
|
|
url: string;
|
|
signingEnabled: boolean;
|
|
secret: string;
|
|
payloadTemplate: string;
|
|
}
|
|
|
|
export const DEFAULT_WECOM_PAYLOAD_TEMPLATE = `{
|
|
"msgtype": "text",
|
|
"text": {
|
|
"content": {{message}}
|
|
}
|
|
}`;
|
|
|
|
export const DEFAULT_LARK_PAYLOAD_TEMPLATE = `{
|
|
"msg_type": "text",
|
|
"content": {
|
|
"text": {{message}}
|
|
}
|
|
}`;
|
|
|
|
export interface NotifyForms {
|
|
telegram: TelegramForm;
|
|
webhook: WebhookForm;
|
|
bark: BarkForm;
|
|
email: EmailForm;
|
|
pushplus: PushplusForm;
|
|
wecom: WecomForm;
|
|
lark: LarkForm;
|
|
}
|
|
|
|
// 系统保留头,自定义同名头会被忽略(品牌 vocat)
|
|
export const RESERVED_HEADERS = new Set(["content-type", "x-vocat-signature"]);
|
|
|
|
export const HEADER_NAME_SUGGESTIONS = [
|
|
"Authorization",
|
|
"X-Api-Key",
|
|
"X-Auth-Token",
|
|
"X-Webhook-Token",
|
|
"X-Signature",
|
|
"X-Request-Id",
|
|
"Accept",
|
|
"User-Agent",
|
|
];
|
|
|
|
let headerRowSeq = 0;
|
|
|
|
export function nextHeaderRowId() {
|
|
headerRowSeq += 1;
|
|
return headerRowSeq;
|
|
}
|
|
|
|
function str(value: unknown): string {
|
|
return value === null || value === undefined ? "" : String(value);
|
|
}
|
|
|
|
function num(value: unknown, fallback: number): number {
|
|
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
}
|
|
|
|
export function recordToHeaderRows(raw: unknown): HeaderRow[] {
|
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return [];
|
|
return Object.entries(raw as Record<string, unknown>).map(([key, value]) => ({
|
|
id: nextHeaderRowId(),
|
|
key,
|
|
value: value === null || value === undefined ? "" : String(value),
|
|
}));
|
|
}
|
|
|
|
export function headerRowsToRecord(rows: HeaderRow[]): Record<string, string> {
|
|
const out: Record<string, string> = {};
|
|
for (const row of rows) {
|
|
const key = String(row.key || "").trim();
|
|
if (!key || RESERVED_HEADERS.has(key.toLowerCase())) continue;
|
|
out[key] = String(row.value ?? "");
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? (value as Record<string, unknown>)
|
|
: {};
|
|
}
|
|
|
|
function joinList(value: unknown): string {
|
|
return Array.isArray(value) ? value.map((item) => String(item)).join(",") : "";
|
|
}
|
|
|
|
function strList(value: unknown): string[] {
|
|
return Array.isArray(value) ? value.map((item) => String(item)) : [];
|
|
}
|
|
|
|
export function defaultNotifyForms(): NotifyForms {
|
|
return formsFromNotifications({});
|
|
}
|
|
|
|
export function formsFromNotifications(data: Partial<NotificationSettings>): NotifyForms {
|
|
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 wecom = asRecord(data.wecom);
|
|
const lark = asRecord(data.lark);
|
|
return {
|
|
telegram: {
|
|
enabled: !!telegram.enabled,
|
|
botToken: str(telegram.botToken),
|
|
chatId: telegram.chatId === null || telegram.chatId === undefined ? "" : String(telegram.chatId),
|
|
adminId: telegram.adminId === null || telegram.adminId === undefined ? "" : String(telegram.adminId),
|
|
baseUrl: str(telegram.baseUrl),
|
|
proxy: str(telegram.proxy),
|
|
},
|
|
webhook: {
|
|
enabled: !!webhook.enabled,
|
|
urls: strList(webhook.urls),
|
|
secret: str(webhook.secret),
|
|
timeoutMs: num(webhook.timeoutMs, 5000),
|
|
retryMax: num(webhook.retryMax, 3),
|
|
textTemplate:
|
|
webhook.textTemplate === null || webhook.textTemplate === undefined
|
|
? "收到新短信\n设备 {{device_label}}\n号码 {{number}}\n时间 {{time}}\n内容 {{text}}"
|
|
: String(webhook.textTemplate),
|
|
headers: recordToHeaderRows(webhook.headers),
|
|
},
|
|
bark: {
|
|
enabled: !!bark.enabled,
|
|
urls: strList(bark.urls),
|
|
group: str(bark.group) || "vocat",
|
|
icon: str(bark.icon),
|
|
level: str(bark.level) || "active",
|
|
},
|
|
email: {
|
|
enabled: !!email.enabled,
|
|
useSsl: !!email.useSsl,
|
|
smtpHost: str(email.smtpHost),
|
|
smtpPort: email.smtpPort === null || email.smtpPort === undefined ? "" : String(email.smtpPort),
|
|
username: str(email.username),
|
|
password: str(email.password),
|
|
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",
|
|
},
|
|
wecom: {
|
|
enabled: !!wecom.enabled,
|
|
urls: strList(wecom.urls),
|
|
payloadTemplate: str(wecom.payloadTemplate ?? wecom.payload_template) || DEFAULT_WECOM_PAYLOAD_TEMPLATE,
|
|
},
|
|
lark: {
|
|
enabled: !!lark.enabled,
|
|
url: str(lark.url),
|
|
signingEnabled: !!lark.signingEnabled,
|
|
secret: lark.signingEnabled ? str(lark.secret) : "",
|
|
payloadTemplate: str(lark.payloadTemplate ?? lark.payload_template) || DEFAULT_LARK_PAYLOAD_TEMPLATE,
|
|
},
|
|
};
|
|
}
|
|
|
|
function splitList(value: string): string[] {
|
|
return value
|
|
? value
|
|
.split(",")
|
|
.map((item) => item.trim())
|
|
.filter(Boolean)
|
|
: [];
|
|
}
|
|
|
|
export function buildWebhookPayload(form: WebhookForm, 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,
|
|
secret: form.secret || "",
|
|
timeoutMs: Number(form.timeoutMs) || 5000,
|
|
retryMax: Number(form.retryMax) || 3,
|
|
textTemplate: String(form.textTemplate || ""),
|
|
headers: headerRowsToRecord(form.headers),
|
|
};
|
|
}
|
|
|
|
export function buildBarkPayload(form: BarkForm, 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,
|
|
group: String(form.group || "").trim(),
|
|
icon: String(form.icon || "").trim(),
|
|
level: String(form.level || "").trim(),
|
|
};
|
|
}
|
|
|
|
export function buildEmailPayload(form: EmailForm, forTest = false) {
|
|
return {
|
|
enabled: !!form.enabled,
|
|
useSsl: !!form.useSsl,
|
|
smtpHost: forTest ? String(form.smtpHost || "").trim() : form.smtpHost || "",
|
|
smtpPort: Number(form.smtpPort) || 0,
|
|
username: forTest ? String(form.username || "").trim() : form.username || "",
|
|
password: forTest ? String(form.password || "").trim() : form.password || "",
|
|
fromAddress: forTest ? String(form.fromAddress || "").trim() : form.fromAddress || "",
|
|
toAddresses: splitList(form.toAddresses),
|
|
};
|
|
}
|
|
|
|
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 buildLarkPayload(form: LarkForm, forTest = false) {
|
|
const payload: {
|
|
enabled: boolean;
|
|
url?: string;
|
|
signing_enabled: boolean;
|
|
payload_template: string;
|
|
secret?: string;
|
|
} = {
|
|
enabled: !!form.enabled,
|
|
signing_enabled: !!form.signingEnabled,
|
|
payload_template: String(form.payloadTemplate || ""),
|
|
};
|
|
const url = forTest ? String(form.url || "").trim() : String(form.url || "");
|
|
if (url) payload.url = url;
|
|
if (form.signingEnabled) payload.secret = String(form.secret || "");
|
|
return payload;
|
|
}
|
|
|
|
export function buildNotificationsPayload(forms: NotifyForms) {
|
|
return {
|
|
telegram: {
|
|
enabled: !!forms.telegram.enabled,
|
|
botToken: forms.telegram.botToken || "",
|
|
// vocat 后端将 chat_id/admin_id 存为字符串(参考实现是数字)
|
|
chatId: forms.telegram.chatId.trim(),
|
|
adminId: forms.telegram.adminId.trim(),
|
|
baseUrl: forms.telegram.baseUrl || "",
|
|
proxy: forms.telegram.proxy || "",
|
|
},
|
|
email: buildEmailPayload(forms.email),
|
|
pushplus: {
|
|
enabled: !!forms.pushplus.enabled,
|
|
token: forms.pushplus.token || "",
|
|
topic: forms.pushplus.topic || "",
|
|
channel: forms.pushplus.channel || "",
|
|
},
|
|
webhook: buildWebhookPayload(forms.webhook),
|
|
bark: buildBarkPayload(forms.bark),
|
|
wecom: buildWecomPayload(forms.wecom),
|
|
lark: buildLarkPayload(forms.lark),
|
|
};
|
|
}
|