From 4b2348ce2de7d4681c7c63a6a9dfe091549d2b81 Mon Sep 17 00:00:00 2001 From: Alone88 Date: Sun, 6 Sep 2026 15:32:26 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20WPUSH=20=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=B8=A0=E9=81=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 镜像 PushPlus 适配器新增 wpush 渠道:POST https://api.wpush.cn/api/v1/send, 仅当响应 JSON code===0 视为成功;支持可选 channel / topic_code;API Key 脱敏不落日志。 接入 dispatch 注册表、配置页、测试发送与单元测试。 --- README.md | 15 +++-- src/api/handlers/config.js | 7 ++- src/api/handlers/test-notification.js | 16 ++++- src/data/config.js | 5 +- src/services/notify/dispatch.js | 4 +- src/services/notify/wpush.js | 77 +++++++++++++++++++++++ src/views/configPage.html | 86 ++++++++++++++++++++++++-- tests/services/notify/channels.test.js | 55 +++++++++++++++- 8 files changed, 250 insertions(+), 15 deletions(-) create mode 100644 src/services/notify/wpush.js diff --git a/README.md b/README.md index 27131ad5..deafb74a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SubsTracker — 订阅管理与提醒系统 -基于 **Cloudflare Workers + KV** 的轻量级订阅到期提醒。在网页里管理订阅,到点通过 Telegram / Bark / 企业微信 / ntfy 等 **10 种渠道** 推送,并自带发送与调度日志方便排查。 +基于 **Cloudflare Workers + KV** 的轻量级订阅到期提醒。在网页里管理订阅,到点通过 Telegram / Bark / 企业微信 / ntfy 等 **11 种渠道** 推送,并自带发送与调度日志方便排查。 **适合**:个人自托管、域名/会员/账单到期提醒。 **不适合**:多用户协作、复杂企业审批流。 @@ -129,6 +129,7 @@ Cloudflare Dashboard → **Workers & Pages → KV** → 打开 `SUBSCRIPTIONS_KV | Bark | Device Key;自建可填 Server | | 企业微信 | 群机器人 Webhook | | ntfy | Server(默认 ntfy.sh)+ Topic;可选 Token | +| WPUSH | API Key;可选 Channel / Topic 编码 | | 邮件 | Resend API Key + 收发邮箱 | | Webhook | 任意 HTTP 地址 + 可选模板 | @@ -227,7 +228,7 @@ Cloudflare Dashboard → **Workers & Pages → KV** → 打开 `SUBSCRIPTIONS_KV ### 通知渠道(10) -Telegram · NotifyX · Webhook · 企业微信 · Resend 邮件 · Bark · Gotify · Server酱 · PushPlus · **ntfy** +Telegram · NotifyX · Webhook · 企业微信 · Resend 邮件 · Bark · Gotify · Server酱 · PushPlus · ntfy · **WPUSH** ### 可观测 @@ -296,15 +297,19 @@ Telegram · NotifyX · Webhook · 企业微信 · Resend 邮件 · Bark · Gotif 启用 ntfy → Server 默认 `https://ntfy.sh` → 填自己的 Topic → 手机 ntfy App 订阅同一 Topic → 点测试。 -### 10. 控制台里 `beacon.min.js` / cloudflareinsights 报错? +### 10. WPUSH 怎么配? + +启用 WPUSH → 在 [设置页](https://wpush.cn/settings) 获取 API Key → 可选填写渠道 / Topic 编码 → 点测试。成功条件为接口返回 `code === 0`。 + +### 11. 控制台里 `beacon.min.js` / cloudflareinsights 报错? 那是 **Cloudflare 统计脚本**,不是本项目业务代码。一般可忽略,与订阅列表无关。 -### 11. Authentication error [code: 10000](部署时) +### 12. Authentication error [code: 10000](部署时) Token 权限不足或 Wrangler 缓存问题:检查 API Token 权限,必要时删 `.wrangler/` 后重试。 -### 12. 第三方系统想调通知接口? +### 13. 第三方系统想调通知接口? 在系统配置生成 **第三方 API 令牌** 后: diff --git a/src/api/handlers/config.js b/src/api/handlers/config.js index 3d16f010..3a0055e5 100644 --- a/src/api/handlers/config.js +++ b/src/api/handlers/config.js @@ -14,7 +14,8 @@ const SECRET_FIELDS = [ 'GOTIFY_APP_TOKEN', 'SERVERCHAN_SENDKEY', 'PUSHPLUS_TOKEN', - 'NTFY_TOKEN' + 'NTFY_TOKEN', + 'WPUSH_APIKEY' ]; function isConfiguredSecret(value) { @@ -122,6 +123,10 @@ async function handleUpdateConfig(request, env) { NTFY_TOPIC: (newConfig.NTFY_TOPIC || '').trim(), NTFY_TOKEN: mergeSecretField(config, newConfig, 'NTFY_TOKEN', clearSecretFields), + WPUSH_APIKEY: mergeSecretField(config, newConfig, 'WPUSH_APIKEY', clearSecretFields), + WPUSH_CHANNEL: (newConfig.WPUSH_CHANNEL || '').trim(), + WPUSH_TOPIC_CODE: (newConfig.WPUSH_TOPIC_CODE || '').trim(), + ENABLED_NOTIFIERS: newConfig.ENABLED_NOTIFIERS || ['notifyx'], TIMEZONE: newConfig.TIMEZONE || config.TIMEZONE || 'UTC', diff --git a/src/api/handlers/test-notification.js b/src/api/handlers/test-notification.js index 129ba618..5acb2979 100644 --- a/src/api/handlers/test-notification.js +++ b/src/api/handlers/test-notification.js @@ -10,6 +10,7 @@ import { sendGotifyNotification } from '../../services/notify/gotify.js'; import { sendServerChanNotification } from '../../services/notify/serverchan.js'; import { sendPushPlusNotification } from '../../services/notify/pushplus.js'; import { sendNtfyNotification } from '../../services/notify/ntfy.js'; +import { sendWPushNotification } from '../../services/notify/wpush.js'; async function handleTestNotification(request, env) { try { @@ -19,7 +20,7 @@ async function handleTestNotification(request, env) { let message = ''; const type = typeof body.type === 'string' ? body.type.trim() : ''; - const supportedTypes = ['telegram', 'notifyx', 'webhook', 'wechatbot', 'email', 'bark', 'gotify', 'serverchan', 'pushplus', 'ntfy']; + const supportedTypes = ['telegram', 'notifyx', 'webhook', 'wechatbot', 'email', 'bark', 'gotify', 'serverchan', 'pushplus', 'ntfy', 'wpush']; if (!type) { return new Response( @@ -180,6 +181,19 @@ async function handleTestNotification(request, env) { const content = '这是一条测试通知,用于验证 ntfy 通知功能是否正常工作。\n\n发送时间: ' + formatBeijingTime(); success = await sendNtfyNotification(title, content, testConfig); message = success ? 'ntfy 通知发送成功' : 'ntfy 通知发送失败,请检查配置'; + } else if (type === 'wpush') { + const testConfig = { + ...config, + WPUSH_APIKEY: (typeof body.WPUSH_APIKEY === 'string' && body.WPUSH_APIKEY.trim().length > 0) + ? body.WPUSH_APIKEY.trim() + : config.WPUSH_APIKEY, + WPUSH_CHANNEL: body.WPUSH_CHANNEL || config.WPUSH_CHANNEL, + WPUSH_TOPIC_CODE: body.WPUSH_TOPIC_CODE || config.WPUSH_TOPIC_CODE + }; + const title = '测试通知'; + const content = '这是一条测试通知,用于验证 WPUSH 通知功能是否正常工作。\n\n发送时间: ' + formatBeijingTime(); + success = await sendWPushNotification(title, content, testConfig); + message = success ? 'WPUSH 通知发送成功' : 'WPUSH 通知发送失败,请检查配置'; } return new Response( diff --git a/src/data/config.js b/src/data/config.js index c1e070d1..3a6c7e3a 100644 --- a/src/data/config.js +++ b/src/data/config.js @@ -38,7 +38,10 @@ const DEFAULT_CONFIG = { PUSHPLUS_CHANNEL: '', NTFY_SERVER: 'https://ntfy.sh', NTFY_TOPIC: '', - NTFY_TOKEN: '' + NTFY_TOKEN: '', + WPUSH_APIKEY: '', + WPUSH_CHANNEL: '', + WPUSH_TOPIC_CODE: '' }; async function getConfig(env) { diff --git a/src/services/notify/dispatch.js b/src/services/notify/dispatch.js index 9de020f1..a3681097 100644 --- a/src/services/notify/dispatch.js +++ b/src/services/notify/dispatch.js @@ -19,6 +19,7 @@ import { gotifyChannel } from './gotify.js'; import { serverChanChannel } from './serverchan.js'; import { pushplusChannel } from './pushplus.js'; import { ntfyChannel } from './ntfy.js'; +import { wpushChannel } from './wpush.js'; import { writeLog } from '../../data/notification-logs.repo.js'; /** 名字到渠道实例的映射;新增渠道在此注册即可 */ @@ -32,7 +33,8 @@ export const ALL_CHANNELS = { gotify: gotifyChannel, serverchan: serverChanChannel, pushplus: pushplusChannel, - ntfy: ntfyChannel + ntfy: ntfyChannel, + wpush: wpushChannel }; /** diff --git a/src/services/notify/wpush.js b/src/services/notify/wpush.js new file mode 100644 index 00000000..a3dff5f5 --- /dev/null +++ b/src/services/notify/wpush.js @@ -0,0 +1,77 @@ +// @ts-check +/** + * WPUSH 通知渠道 + * + * 文档:https://wpush.cn/docs + * 发送:POST https://api.wpush.cn/api/v1/send + * 成功条件:响应 JSON 的 code === 0(注意:与 PushPlus 的 200 不同) + * + * 配置: + * - WPUSH_APIKEY API Key(必填,勿写入日志) + * - WPUSH_CHANNEL 可选推送渠道(wechat/app/sms/mail/webhook/dingtalk/feishu/wechat_work/clawbot/qqbot) + * - WPUSH_TOPIC_CODE 可选 Topic 广播编码 + */ +import { ok, fail, errorMessage } from './channel.js'; + +/** @type {import('./channel.js').Channel} */ +export const wpushChannel = { + name: 'wpush', + + validateConfig(config) { + if (!config.WPUSH_APIKEY || !String(config.WPUSH_APIKEY).trim()) { + return { ok: false, error: '缺少 WPUSH_APIKEY' }; + } + return { ok: true }; + }, + + async send(payload, config) { + const v = wpushChannel.validateConfig(config); + if (!v.ok) return fail('wpush', v.error || '配置无效'); + + /** @type {Record} */ + const body = { + apikey: String(config.WPUSH_APIKEY).trim(), + title: payload.title || '订阅提醒', + content: payload.content || payload.title || '订阅提醒' + }; + if (config.WPUSH_CHANNEL && String(config.WPUSH_CHANNEL).trim()) { + body.channel = String(config.WPUSH_CHANNEL).trim(); + } + if (config.WPUSH_TOPIC_CODE && String(config.WPUSH_TOPIC_CODE).trim()) { + body.topic_code = String(config.WPUSH_TOPIC_CODE).trim(); + } + + try { + const r = await fetch('https://api.wpush.cn/api/v1/send', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + const result = await r.json().catch(() => ({})); + // 切勿把 apikey 打进日志;失败信息只用 code / message + return result && result.code === 0 + ? ok('wpush', result) + : fail( + 'wpush', + `WPUSH 返回 code=${result?.code} ${result?.message || result?.msg || ''}`, + result + ); + } catch (err) { + return fail('wpush', errorMessage(err)); + } + }, + + async test(config) { + return wpushChannel.send( + { title: '订阅管理 - 测试通知', content: '这是一条 WPUSH 测试通知。' }, + config + ); + } +}; + +/** @deprecated 旧版兼容函数 */ +export async function sendWPushNotification(title, content, config) { + const r = await wpushChannel.send({ title, content }, config); + if (!r.success) console.error('[WPUSH]', r.error); + return r.success; +} diff --git a/src/views/configPage.html b/src/views/configPage.html index 88dd862f..d93f3c6a 100644 --- a/src/views/configPage.html +++ b/src/views/configPage.html @@ -285,6 +285,10 @@

通知设置

PushPlus + @@ -665,6 +672,49 @@

PushPlus 配置

+ +
+

WPUSH 配置

+
+
+ +
+ + +
+

加载中...

+

从 WPUSH 设置页 获取 API Key

+
+
+ + +

具体可用渠道以 WPUSH 文档 为准

+
+
+ + +
+
+
+ +
+
@@ -789,6 +839,7 @@

测试中...'; button.disabled = true; @@ -1252,6 +1316,11 @@