@perk-net/perk-pushplus-sdk 1.1.1 → 1.2.1

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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  - **同时支持 Node.js 与浏览器**:Node.js 18+ 使用内置 `fetch`,浏览器使用原生 `fetch`,无运行时依赖。
6
6
  - **三种产物**:CommonJS (`.cjs`) + ESModule (`.js`) + 浏览器 IIFE (`.global.js`),可通过 npm / `<script>` 直接加载。
7
7
  - **完整 TypeScript 类型**:所有请求 / 响应 / 枚举 / 回调全部带类型声明。
8
- - **全部开放接口**:用户、消息、消息 token、群组、群组用户、好友、webhook、渠道、ClawBot、功能设置、预处理、图片服务(含一键上传到 PushPlus 图床)。
8
+ - **全部开放接口**:用户、消息、消息 token、群组、群组用户、好友、webhook、渠道、ClawBot、功能设置、预处理、图片服务(含一键上传到 PushPlus 图床)、push 表单、push 文档、push 表格。
9
9
  - **AccessKey 自动管理**:缓存 + 过期前自动刷新;`code=401` 自动刷新并重试一次。
10
10
  - **本地限流守卫**:命中 `code=900` 后按 token 短路同 token 后续发送,避免被服务端长期封禁。
11
11
  - **Builder 链式 API**:与 Java/Python SDK 风格保持一致。
@@ -14,6 +14,9 @@
14
14
  > 接口文档:
15
15
  > - 消息接口:<https://www.pushplus.plus/doc/guide/api.html>
16
16
  > - 开放接口:<https://www.pushplus.plus/doc/guide/openApi.html>
17
+ > - push 表单:<https://www.pushplus.plus/doc/ecosystem/form/>
18
+ > - push 文档:<https://www.pushplus.plus/doc/ecosystem/doc/>
19
+ > - push 表格:<https://www.pushplus.plus/doc/ecosystem/sheet/>
17
20
 
18
21
  ## 安装
19
22
 
@@ -87,7 +90,7 @@ await client.send({
87
90
  template: Template.MARKDOWN,
88
91
  });
89
92
 
90
- // push 表单:template=form 时需传 pushId(表单编码)
93
+ // push 表单 / 文档 / 表格:templateform/doc/excel 时需传 pushId(对应编码)
91
94
  await client.send(
92
95
  sendRequest()
93
96
  .title('表单通知')
@@ -96,6 +99,22 @@ await client.send(
96
99
  .pushId('表单编码')
97
100
  .build(),
98
101
  );
102
+ await client.send(
103
+ sendRequest()
104
+ .title('本周工作同步')
105
+ .content('请查收')
106
+ .template(Template.DOC)
107
+ .pushId('文档编码')
108
+ .build(),
109
+ );
110
+ await client.send(
111
+ sendRequest()
112
+ .title('销售日报')
113
+ .content('请查收')
114
+ .template(Template.EXCEL)
115
+ .pushId('表格编码')
116
+ .build(),
117
+ );
99
118
  ```
100
119
 
101
120
  ### 3. 多渠道发送
@@ -148,10 +167,18 @@ const qr = await client.topic.qrCode(123, 86400, -1);
148
167
 
149
168
  // 群组用户
150
169
  await client.topicUser.editRemark(456, '老张');
170
+ await client.topicUser.addBlacklist(10);
171
+ const topicBlacklist = await client.topicUser.blacklistList({
172
+ current: 1,
173
+ pageSize: 20,
174
+ params: { topicId: 1 },
175
+ });
151
176
 
152
177
  // 好友
153
178
  const myQr = await client.friend.getQrCode({ content: 'welcome' });
154
179
  const friends = await client.friend.list({ current: 1, pageSize: 20 });
180
+ await client.friend.addBlacklist(friends.list[0].friendId);
181
+ const friendBlacklist = await client.friend.blacklistList({ current: 1, pageSize: 20 });
155
182
 
156
183
  // webhook 渠道
157
184
  import { WebhookType } from '@perk-net/perk-pushplus-sdk';
@@ -182,6 +209,48 @@ const uploaded = await client.image.uploadBytes(bytes, { fileName: 'logo.png' })
182
209
  console.log(uploaded.url); // 直接拿到可访问的图片 URL
183
210
  const imgs = await client.image.list({ current: 1, pageSize: 10 });
184
211
  await client.image.delete(imgs.list[0].id);
212
+
213
+ // push 表单
214
+ const form = await client.form.create('用户满意度调查');
215
+ await client.form.save({
216
+ id: form.id!,
217
+ title: '用户满意度调查',
218
+ items: [{ id: 'q_name', type: 'input', label: '您的姓名', required: true }],
219
+ });
220
+ const published = await client.form.publish(form.id!);
221
+ console.log(published.fillUrl);
222
+ await client.send({
223
+ title: published.title,
224
+ content: '请花1分钟完成填写',
225
+ template: Template.FORM,
226
+ pushId: published.formCode,
227
+ });
228
+
229
+ // push 文档
230
+ import { readFile } from 'node:fs/promises';
231
+ const doc = await client.doc.importWord(await readFile('本周工作同步.docx'), '本周工作同步.docx');
232
+ await client.doc.updateShare(doc.docCode!, 1, 0);
233
+ await client.doc.publish(doc.docCode!);
234
+ await client.send({
235
+ title: doc.title,
236
+ content: '请查收',
237
+ template: Template.DOC,
238
+ pushId: doc.docCode,
239
+ });
240
+
241
+ // push 表格
242
+ const sheet = await client.excel.importExcel(await readFile('销售日报.xlsx'), '销售日报.xlsx');
243
+ await client.excel.writeCells(sheet.docCode!, 'A1', [
244
+ ['日期', '销售额'],
245
+ ['2026-08-13', 12800],
246
+ ], 'Sheet1');
247
+ await client.excel.publish(sheet.docCode!);
248
+ await client.send({
249
+ title: sheet.title,
250
+ content: '请查收',
251
+ template: Template.EXCEL,
252
+ pushId: sheet.docCode,
253
+ });
185
254
  ```
186
255
 
187
256
  ### 图片服务
package/dist/index.cjs CHANGED
@@ -75,6 +75,27 @@ var WebhookTypeDescription = {
75
75
  [11 /* WX_PUSHER */]: "WxPusher",
76
76
  [12 /* CUSTOM */]: "\u81EA\u5B9A\u4E49"
77
77
  };
78
+ var FormStatus = /* @__PURE__ */ ((FormStatus2) => {
79
+ FormStatus2[FormStatus2["DRAFT"] = 0] = "DRAFT";
80
+ FormStatus2[FormStatus2["COLLECTING"] = 1] = "COLLECTING";
81
+ FormStatus2[FormStatus2["STOPPED"] = 2] = "STOPPED";
82
+ return FormStatus2;
83
+ })(FormStatus || {});
84
+ var FormStatusDescription = {
85
+ [0 /* DRAFT */]: "\u8349\u7A3F",
86
+ [1 /* COLLECTING */]: "\u6536\u96C6\u4E2D",
87
+ [2 /* STOPPED */]: "\u5DF2\u505C\u6B62"
88
+ };
89
+ var SharePerm = /* @__PURE__ */ ((SharePerm2) => {
90
+ SharePerm2[SharePerm2["CLOSED"] = 0] = "CLOSED";
91
+ SharePerm2[SharePerm2["VIEW"] = 1] = "VIEW";
92
+ return SharePerm2;
93
+ })(SharePerm || {});
94
+ var ShareLogin = /* @__PURE__ */ ((ShareLogin2) => {
95
+ ShareLogin2[ShareLogin2["ANONYMOUS"] = 0] = "ANONYMOUS";
96
+ ShareLogin2[ShareLogin2["REQUIRED"] = 1] = "REQUIRED";
97
+ return ShareLogin2;
98
+ })(ShareLogin || {});
78
99
  var ErrorCode = /* @__PURE__ */ ((ErrorCode3) => {
79
100
  ErrorCode3[ErrorCode3["OK"] = 200] = "OK";
80
101
  ErrorCode3[ErrorCode3["NOT_LOGIN"] = 302] = "NOT_LOGIN";
@@ -354,6 +375,26 @@ var AbstractApi = class {
354
375
  }
355
376
  return parseApiResponse(resp);
356
377
  }
378
+ /**
379
+ * 执行带二进制请求体的请求并返回原始 ApiResponse(不进行 code 校验)。
380
+ * 用于 multipart 上传等场景。
381
+ */
382
+ async executeRaw(method, path, headers, body) {
383
+ const url = this.resolveUrl(path);
384
+ const resp = await callExecuteRaw(this.http, {
385
+ method,
386
+ url,
387
+ headers: headers != null ? headers : void 0,
388
+ body
389
+ });
390
+ if (!isSuccessfulHttpStatus(resp.statusCode)) {
391
+ throw new PushPlusError(
392
+ `PushPlus \u63A5\u53E3 HTTP \u8C03\u7528\u5931\u8D25: status=${resp.statusCode}, body=${resp.body}`,
393
+ resp.statusCode
394
+ );
395
+ }
396
+ return parseApiResponse(resp);
397
+ }
357
398
  /** 执行请求并直接返回 data;非 200 抛出异常。 */
358
399
  async executeForData(method, path, headers, body) {
359
400
  var _a;
@@ -484,6 +525,39 @@ var _OpenAbstractApi = class _OpenAbstractApi extends AbstractApi {
484
525
  (_b = resp.code) != null ? _b : -1
485
526
  );
486
527
  }
528
+ /** 以 multipart 上传文件(自动携带 access-key;code=401 时刷新后重试一次)。 */
529
+ executeOpenMultipart(path, multipart) {
530
+ return this.executeOpenRaw("POST", path, multipart.body, {
531
+ "Content-Type": multipart.contentType
532
+ });
533
+ }
534
+ /**
535
+ * 执行带二进制 body 的开放接口请求;当返回 code=401 时自动刷新 key 并重试一次。
536
+ */
537
+ async executeOpenRaw(method, path, body, extraHeaders) {
538
+ var _a, _b;
539
+ const headers = { ...await this.headersWithAccessKey(), ...extraHeaders != null ? extraHeaders : {} };
540
+ const resp = await this.executeRaw(method, path, headers, body);
541
+ if (isApiSuccess(resp)) {
542
+ return resp.data;
543
+ }
544
+ if (resp.code === _OpenAbstractApi.CODE_ACCESS_KEY_INVALID) {
545
+ this.accessKeyManager.invalidate();
546
+ const retryHeaders = { ...await this.headersWithAccessKey(), ...extraHeaders != null ? extraHeaders : {} };
547
+ const retry = await this.executeRaw(method, path, retryHeaders, body);
548
+ if (isApiSuccess(retry)) {
549
+ return retry.data;
550
+ }
551
+ throw new PushPlusError(
552
+ `PushPlus \u5F00\u653E\u63A5\u53E3\u4E1A\u52A1\u5931\u8D25(\u91CD\u8BD5\u540E): code=${retry.code}, msg=${retry.msg}`,
553
+ (_a = retry.code) != null ? _a : -1
554
+ );
555
+ }
556
+ throw new PushPlusError(
557
+ `PushPlus \u5F00\u653E\u63A5\u53E3\u4E1A\u52A1\u5931\u8D25: code=${resp.code}, msg=${resp.msg}`,
558
+ (_b = resp.code) != null ? _b : -1
559
+ );
560
+ }
487
561
  };
488
562
  _OpenAbstractApi.HEADER_ACCESS_KEY = "access-key";
489
563
  /** PushPlus AccessKey 失效相关的业务码(用于触发自动重试)。 */
@@ -546,6 +620,301 @@ var ClawBotApi = class extends OpenAbstractApi {
546
620
  }
547
621
  };
548
622
 
623
+ // src/multipart.ts
624
+ function buildFileMultipart(fileName, contentType, fileBytes) {
625
+ if (fileBytes == null || fileBytes.byteLength === 0) {
626
+ throw new PushPlusError("\u4E0A\u4F20\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
627
+ }
628
+ const safeName = fileName && fileName.trim() ? fileName : "file";
629
+ const mime = contentType && contentType.trim() ? contentType : "application/octet-stream";
630
+ const boundary = "----PushPlusBoundary" + randomBoundarySuffix();
631
+ const crlf = "\r\n";
632
+ const enc = new TextEncoder();
633
+ const head = enc.encode(
634
+ `--${boundary}${crlf}Content-Disposition: form-data; name="file"; filename="${escapeFileName(safeName)}"${crlf}Content-Type: ${mime}${crlf}${crlf}`
635
+ );
636
+ const tail = enc.encode(`${crlf}--${boundary}--${crlf}`);
637
+ const body = new Uint8Array(head.byteLength + fileBytes.byteLength + tail.byteLength);
638
+ body.set(head, 0);
639
+ body.set(fileBytes, head.byteLength);
640
+ body.set(tail, head.byteLength + fileBytes.byteLength);
641
+ return { contentType: `multipart/form-data; boundary=${boundary}`, body };
642
+ }
643
+ async function toFileBytes(file) {
644
+ if (file instanceof Uint8Array) {
645
+ return file;
646
+ }
647
+ if (file instanceof ArrayBuffer) {
648
+ return new Uint8Array(file);
649
+ }
650
+ if (typeof Blob !== "undefined" && file instanceof Blob) {
651
+ const ab = await file.arrayBuffer();
652
+ return new Uint8Array(ab);
653
+ }
654
+ throw new PushPlusError(`\u4E0D\u652F\u6301\u7684\u4E0A\u4F20\u6587\u4EF6\u7C7B\u578B: ${Object.prototype.toString.call(file)}`);
655
+ }
656
+ function escapeFileName(name) {
657
+ return name.replace(/"/g, "_").replace(/\r/g, " ").replace(/\n/g, " ");
658
+ }
659
+ function randomBoundarySuffix() {
660
+ let s = "";
661
+ for (let i = 0; i < 32; i++) {
662
+ s += Math.floor(Math.random() * 16).toString(16);
663
+ }
664
+ return s;
665
+ }
666
+
667
+ // src/api/doc-api.ts
668
+ var DocApi = class extends OpenAbstractApi {
669
+ constructor(config, http, mgr) {
670
+ super(config, http, mgr);
671
+ }
672
+ /** 我的文档分页。 */
673
+ list(query) {
674
+ return this.executeOpen(
675
+ "POST",
676
+ "/push/api/open/doc/list",
677
+ query != null ? query : {}
678
+ );
679
+ }
680
+ /** 创建空白文档。 */
681
+ create(title) {
682
+ return this.executeOpen("POST", "/push/api/open/doc/create", { title });
683
+ }
684
+ /**
685
+ * 导入 Word(.docx)创建文档。
686
+ *
687
+ * 标题默认取文件名;创建后默认关闭分享,需再调用 publish 才会同步到分享页。
688
+ */
689
+ async importWord(file, fileName = "document.docx") {
690
+ const bytes = await toFileBytes(file);
691
+ const name = fileName && fileName.trim() ? fileName : "document.docx";
692
+ return this.executeOpenMultipart(
693
+ "/push/api/open/doc/import",
694
+ buildFileMultipart(name, guessDocxContentType(name), bytes)
695
+ );
696
+ }
697
+ /** 获取文档元信息与 HTML 草稿正文。 */
698
+ content(docCode) {
699
+ return this.executeOpen(
700
+ "GET",
701
+ this.appendQuery("/push/api/open/doc/content", { docCode })
702
+ );
703
+ }
704
+ /** 保存 HTML 草稿(不影响分享页,需再 publish)。 */
705
+ saveContent(docCode, content) {
706
+ return this.executeOpen("POST", "/push/api/open/doc/saveContent", { docCode, content });
707
+ }
708
+ /** 将草稿同步为分享页快照。 */
709
+ publish(docCode) {
710
+ return this.executeOpen(
711
+ "POST",
712
+ this.appendQuery("/push/api/open/doc/publish", { docCode })
713
+ );
714
+ }
715
+ /** 重命名。 */
716
+ async rename(docCode, title) {
717
+ await this.executeOpen("POST", "/push/api/open/doc/rename", { docCode, title });
718
+ }
719
+ /** 删除文档。 */
720
+ async delete(docCode) {
721
+ await this.executeOpen(
722
+ "POST",
723
+ this.appendQuery("/push/api/open/doc/delete", { docCode })
724
+ );
725
+ }
726
+ /**
727
+ * 更新分享设置。
728
+ *
729
+ * @param sharePerm 0 关闭 / 1 开启(仅可查看)
730
+ * @param shareLogin 0 免登录 / 1 需登录;不传则沿用原值
731
+ */
732
+ updateShare(docCode, sharePerm, shareLogin) {
733
+ const body = { docCode, sharePerm };
734
+ if (shareLogin != null) body.shareLogin = shareLogin;
735
+ return this.executeOpen("POST", "/push/api/open/doc/updateShare", body);
736
+ }
737
+ };
738
+ function guessDocxContentType(name) {
739
+ return name.toLowerCase().endsWith(".docx") ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/octet-stream";
740
+ }
741
+
742
+ // src/api/excel-api.ts
743
+ var ExcelApi = class extends OpenAbstractApi {
744
+ constructor(config, http, mgr) {
745
+ super(config, http, mgr);
746
+ }
747
+ /** 我的表格分页。 */
748
+ list(query) {
749
+ return this.executeOpen(
750
+ "POST",
751
+ "/push/api/open/excel/list",
752
+ query != null ? query : {}
753
+ );
754
+ }
755
+ /** 创建空白表格。 */
756
+ create(title) {
757
+ return this.executeOpen("POST", "/push/api/open/excel/create", { title });
758
+ }
759
+ /**
760
+ * 导入 Excel(.xlsx / .xls)创建表格。
761
+ *
762
+ * 标题默认取文件名;创建后默认关闭分享,需再调用 publish 才会同步到分享页。
763
+ */
764
+ async importExcel(file, fileName = "workbook.xlsx") {
765
+ const bytes = await toFileBytes(file);
766
+ const name = fileName && fileName.trim() ? fileName : "workbook.xlsx";
767
+ return this.executeOpenMultipart(
768
+ "/push/api/open/excel/import",
769
+ buildFileMultipart(name, guessExcelContentType(name), bytes)
770
+ );
771
+ }
772
+ /** 获取表格元信息与整表 JSON 草稿。 */
773
+ content(docCode) {
774
+ return this.executeOpen(
775
+ "GET",
776
+ this.appendQuery("/push/api/open/excel/content", { docCode })
777
+ );
778
+ }
779
+ /**
780
+ * 整表覆盖保存草稿。
781
+ *
782
+ * `content` 可为 JSON 字符串,或工作簿对象(SDK 会序列化)。
783
+ */
784
+ saveContent(docCode, content) {
785
+ return this.executeOpen("POST", "/push/api/open/excel/saveContent", {
786
+ docCode,
787
+ content: stringifyJsonContent(content)
788
+ });
789
+ }
790
+ /**
791
+ * 从指定起始单元格起,按二维数组向右向下写入(草稿)。
792
+ *
793
+ * @param range 起始单元格,如 A1
794
+ * @param values 外层为行、内层为列
795
+ * @param sheetName 工作表名称;不传则写入活动表 / 第一张表
796
+ */
797
+ writeCells(docCode, range, values, sheetName) {
798
+ const body = { docCode, range, values };
799
+ if (sheetName != null) body.sheetName = sheetName;
800
+ return this.executeOpen("POST", "/push/api/open/excel/writeCells", body);
801
+ }
802
+ /** 将草稿同步为分享页快照。 */
803
+ publish(docCode) {
804
+ return this.executeOpen(
805
+ "POST",
806
+ this.appendQuery("/push/api/open/excel/publish", { docCode })
807
+ );
808
+ }
809
+ /** 重命名。 */
810
+ async rename(docCode, title) {
811
+ await this.executeOpen("POST", "/push/api/open/excel/rename", { docCode, title });
812
+ }
813
+ /** 删除表格。 */
814
+ async delete(docCode) {
815
+ await this.executeOpen(
816
+ "POST",
817
+ this.appendQuery("/push/api/open/excel/delete", { docCode })
818
+ );
819
+ }
820
+ /**
821
+ * 更新分享设置。
822
+ *
823
+ * @param sharePerm 0 关闭 / 1 开启(仅可查看)
824
+ * @param shareLogin 0 免登录 / 1 需登录;不传则沿用原值
825
+ */
826
+ updateShare(docCode, sharePerm, shareLogin) {
827
+ const body = { docCode, sharePerm };
828
+ if (shareLogin != null) body.shareLogin = shareLogin;
829
+ return this.executeOpen("POST", "/push/api/open/excel/updateShare", body);
830
+ }
831
+ };
832
+ function stringifyJsonContent(content) {
833
+ if (typeof content === "string") {
834
+ return content;
835
+ }
836
+ try {
837
+ return JSON.stringify(content);
838
+ } catch (e) {
839
+ throw new PushPlusError(`\u5E8F\u5217\u5316\u8868\u683C\u5185\u5BB9\u5931\u8D25: ${e.message}`, -1, { cause: e });
840
+ }
841
+ }
842
+ function guessExcelContentType(name) {
843
+ const lower = name.toLowerCase();
844
+ if (lower.endsWith(".xlsx")) {
845
+ return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
846
+ }
847
+ if (lower.endsWith(".xls")) {
848
+ return "application/vnd.ms-excel";
849
+ }
850
+ return "application/octet-stream";
851
+ }
852
+
853
+ // src/api/form-api.ts
854
+ var FormApi = class extends OpenAbstractApi {
855
+ constructor(config, http, mgr) {
856
+ super(config, http, mgr);
857
+ }
858
+ /** 我的表单分页。 */
859
+ list(query) {
860
+ return this.executeOpen(
861
+ "POST",
862
+ "/push/api/open/form/list",
863
+ query != null ? query : {}
864
+ );
865
+ }
866
+ /** 创建空白表单(草稿)。 */
867
+ create(title) {
868
+ return this.executeOpen("POST", "/push/api/open/form/create", { title });
869
+ }
870
+ /** 基于已有表单复制一份新草稿。 */
871
+ copy(id) {
872
+ return this.executeOpen(
873
+ "POST",
874
+ this.appendQuery("/push/api/open/form/copy", { id })
875
+ );
876
+ }
877
+ /** 保存表单设计(仅更新草稿;已发布需再调用 publish)。 */
878
+ async save(req) {
879
+ await this.executeOpen("POST", "/push/api/open/form/save", req);
880
+ }
881
+ /** 表单详情(含草稿题目、主题、设置)。 */
882
+ detail(id) {
883
+ return this.executeOpen(
884
+ "GET",
885
+ this.appendQuery("/push/api/open/form/detail", { id })
886
+ );
887
+ }
888
+ /** 草稿与发布快照的题目差异。 */
889
+ publishDiff(id) {
890
+ return this.executeOpen(
891
+ "GET",
892
+ this.appendQuery("/push/api/open/form/publishDiff", { id })
893
+ );
894
+ }
895
+ /** 发布表单,开始收集。 */
896
+ publish(id) {
897
+ return this.executeOpen(
898
+ "POST",
899
+ this.appendQuery("/push/api/open/form/publish", { id })
900
+ );
901
+ }
902
+ /** 停止收集。 */
903
+ async stop(id) {
904
+ await this.executeOpen(
905
+ "POST",
906
+ this.appendQuery("/push/api/open/form/stop", { id })
907
+ );
908
+ }
909
+ /** 删除表单(不可恢复)。 */
910
+ async delete(id) {
911
+ await this.executeOpen(
912
+ "POST",
913
+ this.appendQuery("/push/api/open/form/delete", { id })
914
+ );
915
+ }
916
+ };
917
+
549
918
  // src/api/friend-api.ts
550
919
  var FriendApi = class extends OpenAbstractApi {
551
920
  constructor(config, http, mgr) {
@@ -575,6 +944,40 @@ var FriendApi = class extends OpenAbstractApi {
575
944
  async editRemark(id, remark) {
576
945
  await this.executeOpen("POST", "/api/open/friend/editRemark", { id, remark });
577
946
  }
947
+ /**
948
+ * 5. 将好友加入黑名单。
949
+ *
950
+ * 加入后将解除双方好友关系,对方无法再添加你。不能将自己加入黑名单,仅可将已有好友加入黑名单。
951
+ *
952
+ * @param friendId 好友 id(好友列表中的 friendId 字段)
953
+ */
954
+ async addBlacklist(friendId) {
955
+ await this.executeOpen(
956
+ "POST",
957
+ this.appendQuery("/api/open/friend/addBlacklist", { friendId })
958
+ );
959
+ }
960
+ /** 6. 好友黑名单列表。 */
961
+ blacklistList(query) {
962
+ return this.executeOpen(
963
+ "POST",
964
+ "/api/open/friend/blacklistList",
965
+ query != null ? query : {}
966
+ );
967
+ }
968
+ /**
969
+ * 7. 解除好友黑名单。
970
+ *
971
+ * 解除后不会自动恢复好友关系,需重新扫码添加。
972
+ *
973
+ * @param id 黑名单记录 ID(黑名单列表中的 id 字段)
974
+ */
975
+ async removeBlacklist(id) {
976
+ await this.executeOpen(
977
+ "POST",
978
+ this.appendQuery("/api/open/friend/removeBlacklist", { id })
979
+ );
980
+ }
578
981
  };
579
982
 
580
983
  // src/api/image-api.ts
@@ -623,7 +1026,7 @@ var ImageApi = class extends OpenAbstractApi {
623
1026
  }
624
1027
  const fileName = options.fileName || "file";
625
1028
  const contentType = options.contentType || guessContentTypeByName(fileName) || "application/octet-stream";
626
- const boundary = "----PushPlusBoundary" + randomBoundarySuffix();
1029
+ const boundary = "----PushPlusBoundary" + randomBoundarySuffix2();
627
1030
  const body = buildMultipartBody(boundary, uploadToken, fileName, contentType, bytes);
628
1031
  const resp = await callExecuteRaw(this.http, {
629
1032
  method: "POST",
@@ -709,7 +1112,7 @@ function buildMultipartBody(boundary, uploadToken, fileName, contentType, fileBy
709
1112
  const crlf = "\r\n";
710
1113
  const enc = new TextEncoder();
711
1114
  const head = enc.encode(
712
- `--${boundary}${crlf}Content-Disposition: form-data; name="token"${crlf}${crlf}${uploadToken}${crlf}--${boundary}${crlf}Content-Disposition: form-data; name="file"; filename="${escapeFileName(fileName)}"${crlf}Content-Type: ${contentType}${crlf}${crlf}`
1115
+ `--${boundary}${crlf}Content-Disposition: form-data; name="token"${crlf}${crlf}${uploadToken}${crlf}--${boundary}${crlf}Content-Disposition: form-data; name="file"; filename="${escapeFileName2(fileName)}"${crlf}Content-Type: ${contentType}${crlf}${crlf}`
713
1116
  );
714
1117
  const tail = enc.encode(`${crlf}--${boundary}--${crlf}`);
715
1118
  const out = new Uint8Array(head.byteLength + fileBytes.byteLength + tail.byteLength);
@@ -718,7 +1121,7 @@ function buildMultipartBody(boundary, uploadToken, fileName, contentType, fileBy
718
1121
  out.set(tail, head.byteLength + fileBytes.byteLength);
719
1122
  return out;
720
1123
  }
721
- function escapeFileName(name) {
1124
+ function escapeFileName2(name) {
722
1125
  return name.replace(/"/g, "_").replace(/\r/g, " ").replace(/\n/g, " ");
723
1126
  }
724
1127
  function guessContentTypeByName(name) {
@@ -731,7 +1134,7 @@ function guessContentTypeByName(name) {
731
1134
  if (lower.endsWith(".svg")) return "image/svg+xml";
732
1135
  return null;
733
1136
  }
734
- function randomBoundarySuffix() {
1137
+ function randomBoundarySuffix2() {
735
1138
  let s = "";
736
1139
  for (let i = 0; i < 32; i++) {
737
1140
  s += Math.floor(Math.random() * 16).toString(16);
@@ -1074,6 +1477,42 @@ var TopicUserApi = class extends OpenAbstractApi {
1074
1477
  async editRemark(id, remark) {
1075
1478
  await this.executeOpen("POST", "/api/open/topicUser/editRemark", { id, remark });
1076
1479
  }
1480
+ /**
1481
+ * 4. 将订阅人加入黑名单。
1482
+ *
1483
+ * 加入后将移出群组,对方无法再加入该群组。积分群组不支持黑名单。不能将自己加入黑名单。
1484
+ *
1485
+ * @param topicRelationId 用户编号(订阅人列表中的 id 字段)
1486
+ */
1487
+ async addBlacklist(topicRelationId) {
1488
+ const path = this.appendQuery("/api/open/topicUser/addBlacklist", { topicRelationId });
1489
+ await this.executeOpen("POST", path);
1490
+ }
1491
+ /**
1492
+ * 5. 订阅人黑名单列表。
1493
+ *
1494
+ * `query.params.topicId` 必填。
1495
+ */
1496
+ blacklistList(query) {
1497
+ return this.executeOpen(
1498
+ "POST",
1499
+ "/api/open/topicUser/blacklistList",
1500
+ query
1501
+ );
1502
+ }
1503
+ /**
1504
+ * 6. 解除订阅人黑名单。
1505
+ *
1506
+ * 解除后不会自动恢复群组订阅,对方可重新加入该群组。
1507
+ *
1508
+ * @param id 黑名单记录 ID(黑名单列表中的 id 字段)
1509
+ */
1510
+ async removeBlacklist(id) {
1511
+ await this.executeOpen(
1512
+ "POST",
1513
+ this.appendQuery("/api/open/topicUser/removeBlacklist", { id })
1514
+ );
1515
+ }
1077
1516
  };
1078
1517
 
1079
1518
  // src/api/user-api.ts
@@ -1138,7 +1577,7 @@ function resolveConfig(input) {
1138
1577
  logRequest: (_g = cfg.logRequest) != null ? _g : false,
1139
1578
  rateLimitGuardEnabled: (_h = cfg.rateLimitGuardEnabled) != null ? _h : true,
1140
1579
  rateLimitCooldownMs: (_i = cfg.rateLimitCooldownMs) != null ? _i : 0,
1141
- userAgent: (_j = cfg.userAgent) != null ? _j : `@perk-net/perk-pushplus-sdk/1.1.1`
1580
+ userAgent: (_j = cfg.userAgent) != null ? _j : `@perk-net/perk-pushplus-sdk/1.2.1`
1142
1581
  };
1143
1582
  }
1144
1583
 
@@ -1233,6 +1672,9 @@ var PushPlusClient = class _PushPlusClient {
1233
1672
  this.setting = new SettingApi(this.config, this.httpRequester, this.accessKeyManager);
1234
1673
  this.pre = new PreApi(this.config, this.httpRequester, this.accessKeyManager);
1235
1674
  this.image = new ImageApi(this.config, this.httpRequester, this.accessKeyManager);
1675
+ this.form = new FormApi(this.config, this.httpRequester, this.accessKeyManager);
1676
+ this.doc = new DocApi(this.config, this.httpRequester, this.accessKeyManager);
1677
+ this.excel = new ExcelApi(this.config, this.httpRequester, this.accessKeyManager);
1236
1678
  }
1237
1679
  /** 与 Java SDK 风格一致的 Builder 入口。 */
1238
1680
  static builder() {
@@ -1476,8 +1918,13 @@ exports.Channel = Channel;
1476
1918
  exports.ChannelApi = ChannelApi;
1477
1919
  exports.ClawBotApi = ClawBotApi;
1478
1920
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
1921
+ exports.DocApi = DocApi;
1479
1922
  exports.ErrorCode = ErrorCode;
1923
+ exports.ExcelApi = ExcelApi;
1480
1924
  exports.FetchHttpRequester = FetchHttpRequester;
1925
+ exports.FormApi = FormApi;
1926
+ exports.FormStatus = FormStatus;
1927
+ exports.FormStatusDescription = FormStatusDescription;
1481
1928
  exports.FriendApi = FriendApi;
1482
1929
  exports.ImageApi = ImageApi;
1483
1930
  exports.MessageApi = MessageApi;
@@ -1494,6 +1941,8 @@ exports.SendRequestBuilder = SendRequestBuilder;
1494
1941
  exports.SendStatus = SendStatus;
1495
1942
  exports.SendStatusDescription = SendStatusDescription;
1496
1943
  exports.SettingApi = SettingApi;
1944
+ exports.ShareLogin = ShareLogin;
1945
+ exports.SharePerm = SharePerm;
1497
1946
  exports.Template = Template;
1498
1947
  exports.TopicApi = TopicApi;
1499
1948
  exports.TopicUserApi = TopicUserApi;