@perk-net/perk-pushplus-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 perk-net
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,334 @@
1
+ # @perk-net/perk-pushplus-sdk
2
+
3
+ [pushplus(推送加)](https://www.pushplus.plus) 官方接口的 **JavaScript / TypeScript SDK**,覆盖 **消息接口** 与 **全部开放接口**。
4
+
5
+ - **同时支持 Node.js 与浏览器**:Node.js 18+ 使用内置 `fetch`,浏览器使用原生 `fetch`,无运行时依赖。
6
+ - **三种产物**:CommonJS (`.cjs`) + ESModule (`.js`) + 浏览器 IIFE (`.global.js`),可通过 npm / `<script>` 直接加载。
7
+ - **完整 TypeScript 类型**:所有请求 / 响应 / 枚举 / 回调全部带类型声明。
8
+ - **AccessKey 自动管理**:缓存 + 过期前自动刷新;`code=401` 自动刷新并重试一次。
9
+ - **本地限流守卫**:命中 `code=900` 后按 token 短路同 token 后续发送,避免被服务端长期封禁。
10
+ - **Builder 链式 API**:与 Java/Python SDK 风格保持一致。
11
+ - **回调解析**:`message_complate` / `add_topic_user` / `add_friend` 三类回调统一类型化解析。
12
+
13
+ > 接口文档:
14
+ > - 消息接口:<https://www.pushplus.plus/doc/guide/api.html>
15
+ > - 开放接口:<https://www.pushplus.plus/doc/guide/openApi.html>
16
+
17
+ ## 安装
18
+
19
+ ```bash
20
+ npm install @perk-net/perk-pushplus-sdk
21
+ # 或
22
+ pnpm add @perk-net/perk-pushplus-sdk
23
+ # 或
24
+ yarn add @perk-net/perk-pushplus-sdk
25
+ ```
26
+
27
+ 浏览器直接通过 CDN 引入:
28
+
29
+ ```html
30
+ <script src="https://unpkg.com/@perk-net/perk-pushplus-sdk/dist/index.global.js"></script>
31
+ <script>
32
+ // 全局变量名 PerkPushPlus
33
+ const client = new PerkPushPlus.PushPlusClient({ token: 'your_user_token' });
34
+ client.sendSimple('标题', 'Hello PushPlus').then(console.log);
35
+ </script>
36
+ ```
37
+
38
+ > **作用域包**:本包在 npm 上为组织 **`@perk-net`** 下的公开包。安装与导入时请始终带上作用域前缀(见上文命令)。
39
+ > **维护者发布**:请使用具备 **`@perk-net` 组织发布权限** 的账号,并满足 npm 要求(如已开启账号 **2FA**,或使用可绕过写入 2FA 的 **Granular Access Token**)。仓库内已设置 `publishConfig.access: "public"`,首次发布通常无需再手动加 `--access public`。
40
+
41
+ ## 快速开始
42
+
43
+ ### 1. 构建客户端
44
+
45
+ ```ts
46
+ import { PushPlusClient } from '@perk-net/perk-pushplus-sdk';
47
+
48
+ const client = new PushPlusClient({
49
+ token: 'your_user_token', // 个人中心 -> 一对一推送
50
+ secretKey: 'your_secret_key', // 个人中心 -> 开发设置(开放接口必填)
51
+ });
52
+
53
+ // 也支持 Builder 风格
54
+ const client2 = PushPlusClient.builder()
55
+ .token('your_user_token')
56
+ .secretKey('your_secret_key')
57
+ .build();
58
+ ```
59
+
60
+ > `PushPlusClient` 无状态,建议作为单例长期持有。
61
+
62
+ ### 2. 发送消息
63
+
64
+ ```ts
65
+ import { Channel, Template, sendRequest } from '@perk-net/perk-pushplus-sdk';
66
+
67
+ // 最简:默认 wechat / html
68
+ const shortCode = await client.sendSimple('标题', '<b>内容</b>');
69
+
70
+ // 完整:使用 Builder
71
+ const code = await client.send(
72
+ sendRequest()
73
+ .title('CPU 告警')
74
+ .content('# CPU > 90%\n请尽快处理')
75
+ .template(Template.MARKDOWN)
76
+ .channel(Channel.WECHAT)
77
+ .topic('ops')
78
+ .callbackUrl('https://your.host/pushplus/callback')
79
+ .build(),
80
+ );
81
+
82
+ // 也可以直接传普通对象
83
+ await client.send({
84
+ title: '部署完成',
85
+ content: 'v1.0.0',
86
+ template: Template.MARKDOWN,
87
+ });
88
+ ```
89
+
90
+ ### 3. 多渠道发送
91
+
92
+ ```ts
93
+ import { Channel, batchSendRequest } from '@perk-net/perk-pushplus-sdk';
94
+
95
+ const results = await client.batchSend(
96
+ batchSendRequest()
97
+ .title('多渠道告警')
98
+ .content('CPU > 90%')
99
+ .channel(Channel.WECHAT).option('')
100
+ .channel(Channel.WEBHOOK).option('bark')
101
+ .channel(Channel.EXTENSION).option('')
102
+ .build(),
103
+ );
104
+
105
+ for (const r of results) {
106
+ console.log(r.channel, r.shortCode, r.code, r.message);
107
+ }
108
+ ```
109
+
110
+ `channel(...)` 与 `option(...)` 可累计调用,SDK 自动用逗号拼接,与官方文档示例语义一致。
111
+
112
+ ### 4. 开放接口(全量)
113
+
114
+ > 需要在 PushPlus 后台「开发设置」中:开启开放接口、配置 `secretKey`、把调用方所在公网 IP 加入安全 IP 列表。
115
+ > AccessKey 完全自动管理 —— 直接调用就好。
116
+
117
+ ```ts
118
+ // 用户
119
+ const me = await client.user.myInfo();
120
+ const limit = await client.user.getLimitTime();
121
+ const count = await client.user.getSendCount();
122
+
123
+ // 消息
124
+ const page = await client.openMessage.list({ current: 1, pageSize: 20 });
125
+ const result = await client.openMessage.queryResult('short-code');
126
+ const url = client.openMessage.detailUrl('short-code');
127
+
128
+ // 消息 token
129
+ const newToken = await client.messageToken.add({ name: 'for-jenkins' });
130
+
131
+ // 群组
132
+ const topics = await client.topic.list({
133
+ current: 1, pageSize: 20, params: { topicType: 0 },
134
+ });
135
+ const detail = await client.topic.detail(123);
136
+ const qr = await client.topic.qrCode(123, 86400, -1);
137
+
138
+ // 群组用户
139
+ await client.topicUser.editRemark(456, '老张');
140
+
141
+ // 好友
142
+ const myQr = await client.friend.getQrCode({ content: 'welcome' });
143
+ const friends = await client.friend.list({ current: 1, pageSize: 20 });
144
+
145
+ // webhook 渠道
146
+ import { WebhookType } from '@perk-net/perk-pushplus-sdk';
147
+ await client.webhook.add({
148
+ webhookCode: 'bark',
149
+ webhookName: '我的 Bark',
150
+ webhookType: WebhookType.BARK,
151
+ webhookUrl: 'https://api.day.app/xxxx',
152
+ });
153
+
154
+ // 渠道(公众号 / 企业微信 / 邮箱)
155
+ const mps = await client.channel.mpList();
156
+
157
+ // ClawBot
158
+ const botQr = await client.clawBot.getBotQrcode();
159
+
160
+ // 设置
161
+ await client.setting.changeIsSend(1); // 启用发送
162
+ await client.setting.changeOpenMessageType(0);
163
+
164
+ // 预处理(仅会员)
165
+ const out = await client.pre.test({ content: '...', message: 'hi' });
166
+ ```
167
+
168
+ ### 5. 回调解析
169
+
170
+ PushPlus 在消息发送完成、群组新增用户、新增好友时会回调你预置的 URL。SDK 提供类型安全的解析:
171
+
172
+ ```ts
173
+ import { CallbackEvent, parseCallback } from '@perk-net/perk-pushplus-sdk';
174
+
175
+ // Express
176
+ app.post('/pushplus/callback', express.json(), (req, res) => {
177
+ const payload = parseCallback(req.body);
178
+ switch (payload.event) {
179
+ case CallbackEvent.MESSAGE_COMPLETE:
180
+ console.log('发送结果', payload.messageInfo?.shortCode, payload.messageInfo?.sendStatus);
181
+ break;
182
+ case CallbackEvent.ADD_TOPIC_USER:
183
+ console.log('新订阅', payload.topicUserInfo?.openId);
184
+ break;
185
+ case CallbackEvent.ADD_FRIEND:
186
+ console.log('新好友', payload.friendInfo?.token, payload.qrCode);
187
+ break;
188
+ }
189
+ res.send('ok');
190
+ });
191
+ ```
192
+
193
+ `parseCallback` 接受字符串或已经解析过的对象,返回带类型的 `CallbackPayload`。
194
+
195
+ ## 配置
196
+
197
+ ```ts
198
+ new PushPlusClient({
199
+ token: 'xxx',
200
+ secretKey: 'xxx',
201
+ baseUrl: 'https://www.pushplus.plus',
202
+ connectTimeoutMs: 10_000,
203
+ readTimeoutMs: 30_000,
204
+ accessKeyRefreshAheadSeconds: 300,
205
+ logRequest: false,
206
+ rateLimitGuardEnabled: true,
207
+ rateLimitCooldownMs: 0, // 0 表示「次日 0 点」自动解禁
208
+ userAgent: 'my-app/1.0',
209
+ httpRequester: undefined, // 自定义 HTTP 客户端(可选)
210
+ });
211
+ ```
212
+
213
+ | 字段 | 默认 | 说明 |
214
+ | --- | --- | --- |
215
+ | `token` | – | 用户 token / 消息 token,发送消息使用 |
216
+ | `secretKey` | – | 用户 secretKey,调用开放接口使用 |
217
+ | `baseUrl` | `https://www.pushplus.plus` | 服务地址 |
218
+ | `connectTimeoutMs` | `10000` | 连接超时(毫秒) |
219
+ | `readTimeoutMs` | `30000` | 请求/读超时(毫秒) |
220
+ | `accessKeyRefreshAheadSeconds` | `300` | AccessKey 提前刷新秒数 |
221
+ | `logRequest` | `false` | 开启 DEBUG 级请求/响应日志(写到 `console.debug`) |
222
+ | `rateLimitGuardEnabled` | `true` | 是否启用本地限流守卫 |
223
+ | `rateLimitCooldownMs` | `0` | 命中 `code=900` 后的本地禁推时长(毫秒);`0` 表示到「次日 0 点」 |
224
+ | `userAgent` | `@perk-net/perk-pushplus-sdk/<v>` | UA 头(仅 Node.js 生效,浏览器禁止设置) |
225
+ | `httpRequester` | 内置 fetch 实现 | 自定义 HTTP 客户端 |
226
+
227
+ ## 错误处理
228
+
229
+ 所有错误都会包装成 `PushPlusError`:
230
+
231
+ ```ts
232
+ import { ErrorCode, PushPlusError } from '@perk-net/perk-pushplus-sdk';
233
+
234
+ try {
235
+ await client.sendSimple('t', 'c');
236
+ } catch (e) {
237
+ if (e instanceof PushPlusError) {
238
+ if (e.isRateLimited()) {
239
+ console.warn('PushPlus 限流,今天暂停推送:', e.message);
240
+ return;
241
+ }
242
+ switch (e.errorCode) {
243
+ case ErrorCode.INVALID_TOKEN:
244
+ console.error('token 错误,立即排查配置'); break;
245
+ case ErrorCode.NOT_VERIFIED:
246
+ console.error('账号未实名认证'); break;
247
+ case ErrorCode.INSUFFICIENT_POINTS:
248
+ console.warn('积分不足'); break;
249
+ default:
250
+ console.warn(`PushPlus 失败: code=${e.code}, msg=${e.message}`);
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ `ErrorCode` 已经把官方文档的全部业务码语义化(`OK / NOT_LOGIN / UNAUTHORIZED / IP_FORBIDDEN / SERVER_ERROR / DATA_ERROR / FORBIDDEN_VIEW / INSUFFICIENT_POINTS / RATE_LIMITED / INVALID_TOKEN / NOT_VERIFIED / VALIDATION_ERROR`)。
257
+
258
+ 参考:[PushPlus 接口返回码说明](https://www.pushplus.plus/doc/guide/code.html)。
259
+
260
+ ## 限流守卫(code=900 自动短路)
261
+
262
+ PushPlus 在请求次数过多时会返回 `code=900`,官方文档明确建议「根据返回值判断当天是否让程序继续调用发送消息接口,否则会让账号进一步受限」。SDK 默认替你做这件事:
263
+
264
+ - 任意一次 `client.send(...)` / `client.batchSend(...)` 命中 `code=900` 后,SDK 会按 token 维度记下「禁推至 X 时刻」。
265
+ - 同 token 后续发送调用不再发起 HTTP,直接抛 `PushPlusError(code=900)`。
266
+ - 默认禁推到**系统时区的次日 0 点**;通过 `rateLimitCooldownMs` 可改为固定时长(例如文档示例的 2 天)。
267
+ - 仅作用于发送接口,开放接口不受影响。
268
+ - 进程内单例,**不跨进程共享**——多实例部署时每个进程最多被命中一次。
269
+
270
+ 可观察 / 可干预:
271
+
272
+ ```ts
273
+ const guard = client.rateLimitGuard;
274
+
275
+ const until = guard.blockedUntilAt('user_token'); // null 表示未被限流;否则为本地解禁时间戳(毫秒)
276
+ guard.clear('user_token'); // 例如:人工确认服务端已解禁后立即放行
277
+ ```
278
+
279
+ 完全关闭这个行为(不推荐):
280
+
281
+ ```ts
282
+ new PushPlusClient({ token: 'xxx', rateLimitGuardEnabled: false });
283
+ ```
284
+
285
+ ## 自定义 HTTP 客户端
286
+
287
+ `fetch` 不满足需求(如想用 axios / undici / got / 浏览器代理)时,实现 `HttpRequester` 接口即可:
288
+
289
+ ```ts
290
+ import { HttpRequester, HttpResponse, PushPlusClient } from '@perk-net/perk-pushplus-sdk';
291
+ import axios from 'axios';
292
+
293
+ class AxiosHttpRequester implements HttpRequester {
294
+ async execute({ method, url, headers, body }): Promise<HttpResponse> {
295
+ const resp = await axios.request({
296
+ method,
297
+ url,
298
+ headers,
299
+ data: body,
300
+ validateStatus: () => true, // 自行处理状态码
301
+ transformResponse: r => r, // 直接拿到字符串
302
+ });
303
+ return { statusCode: resp.status, body: resp.data };
304
+ }
305
+ }
306
+
307
+ const client = PushPlusClient.builder()
308
+ .token('xxx')
309
+ .httpRequester(new AxiosHttpRequester())
310
+ .build();
311
+ ```
312
+
313
+ ## 兼容性
314
+
315
+ | 环境 | 要求 | 备注 |
316
+ | --- | --- | --- |
317
+ | Node.js | `>=18`(推荐) | 18+ 内置全局 `fetch` |
318
+ | Node.js | `>=14` | 需自行注入 `HttpRequester` 或 `fetch` polyfill(如 `undici`) |
319
+ | 浏览器 | 现代浏览器 | 使用原生 `fetch` + `AbortController`,注意 PushPlus 服务端 CORS 策略 |
320
+ | TypeScript | `>=4.5` | 完整类型 |
321
+
322
+ > ⚠️ **浏览器使用注意**:PushPlus 接口是否允许跨域取决于服务端响应头。如果生产环境无法直接从浏览器调用,请通过你自己的后端代理后再使用本 SDK。
323
+
324
+ ## 示例
325
+
326
+ 更多示例见 [`examples/`](./examples) 目录:
327
+
328
+ - [`examples/send.mjs`](./examples/send.mjs) — Node.js 发送消息
329
+ - [`examples/express-callback.mjs`](./examples/express-callback.mjs) — Express 接收回调
330
+ - [`examples/browser.html`](./examples/browser.html) — 浏览器中通过 `<script>` 直接使用
331
+
332
+ ## License
333
+
334
+ Apache License 2.0