From 8b9efe5ad33d6f2744344d00a74127c1acd0f75b Mon Sep 17 00:00:00 2001 From: mercury233 Date: Thu, 6 Nov 2025 10:52:41 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0Webhook=E6=94=AF?= =?UTF-8?q?=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++-- src/index.ts | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 306feb6..6243fce 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Twikoo 评论系统对不同的消息推送平台做了大量的适配工作, ## 支持的消息推送平台 +- Webhook - [Qmsg](https://qmsg.zendee.cn/) - [Server 酱](https://sct.ftqq.com/r/13235) - [Push Plus](https://www.pushplus.plus/) @@ -39,7 +40,6 @@ Twikoo 评论系统对不同的消息推送平台做了大量的适配工作, - 阿里云短信 - 腾讯云短信 -- 自定义 Webhook ## 使用方法 @@ -71,7 +71,7 @@ console.log(result); | 参数 | 必填 | 默认 | 说明 | | ---- | ---- | ---- | ---- | -| 平台名称 | ✅ | 无 | 字符串,平台名称的缩写,支持:`qmsg`、`serverchan`、`pushplus`、`pushplushxtrip`、`dingtalk`、`wecom`、`bark`、`gocqhttp`、`atri`、`pushdeer`、`igot`、`telegram`、`feishu`、`ifttt`、`wecombot`、`discord`, `wxpusher` | +| 平台名称 | ✅ | 无 | 字符串,平台名称的缩写,支持:`webhook`、`qmsg`、`serverchan`、`pushplus`、`pushplushxtrip`、`dingtalk`、`wecom`、`bark`、`gocqhttp`、`atri`、`pushdeer`、`igot`、`telegram`、`feishu`、`ifttt`、`wecombot`、`discord`, `wxpusher` | | token | ✅ | 无 | 平台用户身份标识,通常情况下是一串数字和字母组合,详情和示例见下方详细说明 | | title | | 内容第一行 | 可选,消息标题,如果推送平台不支持消息标题,则会拼接在正文首行 | | content | ✅ | 无 | Markdown 格式的推送内容,如果推送平台不支持 Markdown,pushoo 会自动转换成支持的格式 | @@ -79,6 +79,19 @@ console.log(result); ```typescript interface NoticeOptions { + /** + * webhook通知方式的参数配置 + */ + webhook?: { + /** + * url 发送通知的地址 + */ + url: string; + /** + * method 请求方法,默认为 POST + */ + method?: 'GET' | 'POST'; + }; /** * bark通知方式的参数配置 */ @@ -122,6 +135,38 @@ interface NoticeOptions { ## 详细说明 +### 💬 Webhook 缩写: `webhook` + +Webhook 是一种用户定义的 HTTP 回调,通常用于将实时数据推送到指定的 URL。pushoo 可以通过 Webhook 方式将消息推送到你自定义的后端。 + +示例调用: + +```js +let respond = await pushoo('webhook', { + token: '', // 可选,暂不支持签名 + title: '', // 可选 + content: '推送内容', + options: { + webhook: { + url: 'https://example.com/webhook-endpoint', + method: 'POST' // 可选,默认为 POST,也可以设置为 GET + } + } +}); +``` + +特别地,为兼容 Twikoo 中现有的使用方式,可以直接把平台名称设置为 Webhook 的 URL 地址(以 `http://` 或 `https://` 开头),无需传入 `options`。 + +```js +let respond = await pushoo('https://example.com/webhook-endpoint', { + token: '', // 可选 + title: '', // 可选 + content: '推送内容' +}); +``` + +此时如果 URL 的末尾为 `:GET` 则使用 GET 方法发送请求(实际 URL 自动去掉 `:GET`),否则默认使用 POST 方法发送请求。 + ### 💬 [Qmsg](https://qmsg.zendee.cn/) 缩写: `qmsg` Qmsg 酱是 Zendee 提供的第三方 QQ 消息推送服务,免费,消息以 QQ 消息的形式推送,支持私聊推送和群推送。请注意,为避免 Qmsg 酱被 Tencent 冻结,pushoo 会自动删除消息中的网址和 IP 地址。 diff --git a/src/index.ts b/src/index.ts index 7d930c8..57cc49d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,19 @@ import { marked } from 'marked'; import markdownToTxt from 'markdown-to-txt'; export interface NoticeOptions { + /** + * Webhook通知方式的参数配置 + */ + webhook?: { + /** + * url 发送通知的地址 + */ + url: string; + /** + * method 请求方法,默认为 POST + */ + method?: 'GET' | 'POST'; + }; /** * bark通知方式的参数配置 */ @@ -77,6 +90,7 @@ export interface CommonOptions { } export type ChannelType = + | 'webhook' | 'qmsg' | 'serverchan' | 'serverchain' @@ -129,6 +143,37 @@ function removeUrlAndIp(content: string) { .replace(mailRegExp, ''); } +/** + * 自定义 Webhook 推送 + */ +async function noticeWebhook(options: CommonOptions) { + checkParameters(options, ['content']); + let method = options?.options?.webhook?.method || 'POST'; + let url = options?.options?.webhook?.url; + if (!url) { + throw new Error('Webhook url is required'); + } + if (method === 'GET') { + const params = new URLSearchParams({ + ...(options.token ? { token: options.token } : {}), + ...(options.title ? { title: options.title } : {}), + content: options.content, + }); + const response = await axios.get(url, { params }); + return response.data; + } else if (method === 'POST') { + const payload: Record = { + ...(options.token && { token: options.token }), + ...(options.title && { title: options.title }), + content: options.content, + }; + const response = await axios.post(url, payload); + return response.data; + } else { + throw new Error(`Unsupported Webhook request method: ${method}`); + } +} + /** * https://qmsg.zendee.cn/ */ @@ -646,10 +691,11 @@ async function noticeJoin(options: CommonOptions) { return response.data; } -async function notice(channel: ChannelType, options: CommonOptions) { +async function notice(channel: ChannelType | string, options: CommonOptions) { try { let data: any; const noticeFn = { + webhook: noticeWebhook, qmsg: noticeQmsg, serverchan: noticeServerChan, serverchain: noticeServerChan, @@ -673,6 +719,15 @@ async function notice(channel: ChannelType, options: CommonOptions) { }[channel.toLowerCase()]; if (noticeFn) { data = await noticeFn(options); + } else if (typeof channel === 'string' && (channel.startsWith('http://') || channel.startsWith('https://'))) { + options.options = options.options || {}; + options.options.webhook = { url: channel }; + if (channel.endsWith(':GET')) { + // hack: 如果 URL 以 :GET 结尾,则使用 GET 方法 + options.options.webhook.method = 'GET'; + options.options.webhook.url = channel.slice(0, -4); + } + data = await noticeWebhook(options); } else { throw new Error(`<${channel}> is not supported`); } From 62ff8c768f0ba5d4a1db88c2954a30468cd93527 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Mon, 10 Nov 2025 11:03:14 +0800 Subject: [PATCH 2/3] chore: disable eslint no-param-reassign --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index bbb05e7..3e5a995 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -22,6 +22,7 @@ module.exports = { ], 'no-console': 'off', 'no-unused-vars': 'off', + 'no-param-reassign': 'off', }, settings: { 'import/resolver': { From d825fa69c40b3bb91100f951951ffe240db413b6 Mon Sep 17 00:00:00 2001 From: mercury233 Date: Mon, 10 Nov 2025 11:03:22 +0800 Subject: [PATCH 3/3] fix: lint --- src/index.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/index.ts b/src/index.ts index 57cc49d..e7d8dc8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -148,8 +148,8 @@ function removeUrlAndIp(content: string) { */ async function noticeWebhook(options: CommonOptions) { checkParameters(options, ['content']); - let method = options?.options?.webhook?.method || 'POST'; - let url = options?.options?.webhook?.url; + const method = options?.options?.webhook?.method || 'POST'; + const url = options?.options?.webhook?.url; if (!url) { throw new Error('Webhook url is required'); } @@ -161,7 +161,8 @@ async function noticeWebhook(options: CommonOptions) { }); const response = await axios.get(url, { params }); return response.data; - } else if (method === 'POST') { + } + if (method === 'POST') { const payload: Record = { ...(options.token && { token: options.token }), ...(options.title && { title: options.title }), @@ -169,9 +170,8 @@ async function noticeWebhook(options: CommonOptions) { }; const response = await axios.post(url, payload); return response.data; - } else { - throw new Error(`Unsupported Webhook request method: ${method}`); } + throw new Error(`Unsupported Webhook request method: ${method}`); } /**