@perk-net/perk-pushplus-sdk 1.2.0 → 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 +46 -4
- package/dist/index.cjs +211 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -14
- package/dist/index.d.ts +124 -14
- package/dist/index.global.js +211 -5
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +211 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/api/base.ts +27 -1
- package/src/api/doc-api.ts +24 -0
- package/src/api/excel-api.ts +29 -0
- package/src/api/form-api.ts +3 -0
- package/src/api/friend-api.ts +38 -1
- package/src/api/open-base.ts +40 -0
- package/src/api/topic-user-api.ts +40 -1
- package/src/config.ts +1 -1
- package/src/index.ts +3 -0
- package/src/models.ts +46 -13
- package/src/multipart.ts +62 -0
package/dist/index.js
CHANGED
|
@@ -373,6 +373,26 @@ var AbstractApi = class {
|
|
|
373
373
|
}
|
|
374
374
|
return parseApiResponse(resp);
|
|
375
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* 执行带二进制请求体的请求并返回原始 ApiResponse(不进行 code 校验)。
|
|
378
|
+
* 用于 multipart 上传等场景。
|
|
379
|
+
*/
|
|
380
|
+
async executeRaw(method, path, headers, body) {
|
|
381
|
+
const url = this.resolveUrl(path);
|
|
382
|
+
const resp = await callExecuteRaw(this.http, {
|
|
383
|
+
method,
|
|
384
|
+
url,
|
|
385
|
+
headers: headers != null ? headers : void 0,
|
|
386
|
+
body
|
|
387
|
+
});
|
|
388
|
+
if (!isSuccessfulHttpStatus(resp.statusCode)) {
|
|
389
|
+
throw new PushPlusError(
|
|
390
|
+
`PushPlus \u63A5\u53E3 HTTP \u8C03\u7528\u5931\u8D25: status=${resp.statusCode}, body=${resp.body}`,
|
|
391
|
+
resp.statusCode
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
return parseApiResponse(resp);
|
|
395
|
+
}
|
|
376
396
|
/** 执行请求并直接返回 data;非 200 抛出异常。 */
|
|
377
397
|
async executeForData(method, path, headers, body) {
|
|
378
398
|
var _a;
|
|
@@ -503,6 +523,39 @@ var _OpenAbstractApi = class _OpenAbstractApi extends AbstractApi {
|
|
|
503
523
|
(_b = resp.code) != null ? _b : -1
|
|
504
524
|
);
|
|
505
525
|
}
|
|
526
|
+
/** 以 multipart 上传文件(自动携带 access-key;code=401 时刷新后重试一次)。 */
|
|
527
|
+
executeOpenMultipart(path, multipart) {
|
|
528
|
+
return this.executeOpenRaw("POST", path, multipart.body, {
|
|
529
|
+
"Content-Type": multipart.contentType
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* 执行带二进制 body 的开放接口请求;当返回 code=401 时自动刷新 key 并重试一次。
|
|
534
|
+
*/
|
|
535
|
+
async executeOpenRaw(method, path, body, extraHeaders) {
|
|
536
|
+
var _a, _b;
|
|
537
|
+
const headers = { ...await this.headersWithAccessKey(), ...extraHeaders != null ? extraHeaders : {} };
|
|
538
|
+
const resp = await this.executeRaw(method, path, headers, body);
|
|
539
|
+
if (isApiSuccess(resp)) {
|
|
540
|
+
return resp.data;
|
|
541
|
+
}
|
|
542
|
+
if (resp.code === _OpenAbstractApi.CODE_ACCESS_KEY_INVALID) {
|
|
543
|
+
this.accessKeyManager.invalidate();
|
|
544
|
+
const retryHeaders = { ...await this.headersWithAccessKey(), ...extraHeaders != null ? extraHeaders : {} };
|
|
545
|
+
const retry = await this.executeRaw(method, path, retryHeaders, body);
|
|
546
|
+
if (isApiSuccess(retry)) {
|
|
547
|
+
return retry.data;
|
|
548
|
+
}
|
|
549
|
+
throw new PushPlusError(
|
|
550
|
+
`PushPlus \u5F00\u653E\u63A5\u53E3\u4E1A\u52A1\u5931\u8D25(\u91CD\u8BD5\u540E): code=${retry.code}, msg=${retry.msg}`,
|
|
551
|
+
(_a = retry.code) != null ? _a : -1
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
throw new PushPlusError(
|
|
555
|
+
`PushPlus \u5F00\u653E\u63A5\u53E3\u4E1A\u52A1\u5931\u8D25: code=${resp.code}, msg=${resp.msg}`,
|
|
556
|
+
(_b = resp.code) != null ? _b : -1
|
|
557
|
+
);
|
|
558
|
+
}
|
|
506
559
|
};
|
|
507
560
|
_OpenAbstractApi.HEADER_ACCESS_KEY = "access-key";
|
|
508
561
|
/** PushPlus AccessKey 失效相关的业务码(用于触发自动重试)。 */
|
|
@@ -565,6 +618,50 @@ var ClawBotApi = class extends OpenAbstractApi {
|
|
|
565
618
|
}
|
|
566
619
|
};
|
|
567
620
|
|
|
621
|
+
// src/multipart.ts
|
|
622
|
+
function buildFileMultipart(fileName, contentType, fileBytes) {
|
|
623
|
+
if (fileBytes == null || fileBytes.byteLength === 0) {
|
|
624
|
+
throw new PushPlusError("\u4E0A\u4F20\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
|
|
625
|
+
}
|
|
626
|
+
const safeName = fileName && fileName.trim() ? fileName : "file";
|
|
627
|
+
const mime = contentType && contentType.trim() ? contentType : "application/octet-stream";
|
|
628
|
+
const boundary = "----PushPlusBoundary" + randomBoundarySuffix();
|
|
629
|
+
const crlf = "\r\n";
|
|
630
|
+
const enc = new TextEncoder();
|
|
631
|
+
const head = enc.encode(
|
|
632
|
+
`--${boundary}${crlf}Content-Disposition: form-data; name="file"; filename="${escapeFileName(safeName)}"${crlf}Content-Type: ${mime}${crlf}${crlf}`
|
|
633
|
+
);
|
|
634
|
+
const tail = enc.encode(`${crlf}--${boundary}--${crlf}`);
|
|
635
|
+
const body = new Uint8Array(head.byteLength + fileBytes.byteLength + tail.byteLength);
|
|
636
|
+
body.set(head, 0);
|
|
637
|
+
body.set(fileBytes, head.byteLength);
|
|
638
|
+
body.set(tail, head.byteLength + fileBytes.byteLength);
|
|
639
|
+
return { contentType: `multipart/form-data; boundary=${boundary}`, body };
|
|
640
|
+
}
|
|
641
|
+
async function toFileBytes(file) {
|
|
642
|
+
if (file instanceof Uint8Array) {
|
|
643
|
+
return file;
|
|
644
|
+
}
|
|
645
|
+
if (file instanceof ArrayBuffer) {
|
|
646
|
+
return new Uint8Array(file);
|
|
647
|
+
}
|
|
648
|
+
if (typeof Blob !== "undefined" && file instanceof Blob) {
|
|
649
|
+
const ab = await file.arrayBuffer();
|
|
650
|
+
return new Uint8Array(ab);
|
|
651
|
+
}
|
|
652
|
+
throw new PushPlusError(`\u4E0D\u652F\u6301\u7684\u4E0A\u4F20\u6587\u4EF6\u7C7B\u578B: ${Object.prototype.toString.call(file)}`);
|
|
653
|
+
}
|
|
654
|
+
function escapeFileName(name) {
|
|
655
|
+
return name.replace(/"/g, "_").replace(/\r/g, " ").replace(/\n/g, " ");
|
|
656
|
+
}
|
|
657
|
+
function randomBoundarySuffix() {
|
|
658
|
+
let s = "";
|
|
659
|
+
for (let i = 0; i < 32; i++) {
|
|
660
|
+
s += Math.floor(Math.random() * 16).toString(16);
|
|
661
|
+
}
|
|
662
|
+
return s;
|
|
663
|
+
}
|
|
664
|
+
|
|
568
665
|
// src/api/doc-api.ts
|
|
569
666
|
var DocApi = class extends OpenAbstractApi {
|
|
570
667
|
constructor(config, http, mgr) {
|
|
@@ -582,6 +679,19 @@ var DocApi = class extends OpenAbstractApi {
|
|
|
582
679
|
create(title) {
|
|
583
680
|
return this.executeOpen("POST", "/push/api/open/doc/create", { title });
|
|
584
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* 导入 Word(.docx)创建文档。
|
|
684
|
+
*
|
|
685
|
+
* 标题默认取文件名;创建后默认关闭分享,需再调用 publish 才会同步到分享页。
|
|
686
|
+
*/
|
|
687
|
+
async importWord(file, fileName = "document.docx") {
|
|
688
|
+
const bytes = await toFileBytes(file);
|
|
689
|
+
const name = fileName && fileName.trim() ? fileName : "document.docx";
|
|
690
|
+
return this.executeOpenMultipart(
|
|
691
|
+
"/push/api/open/doc/import",
|
|
692
|
+
buildFileMultipart(name, guessDocxContentType(name), bytes)
|
|
693
|
+
);
|
|
694
|
+
}
|
|
585
695
|
/** 获取文档元信息与 HTML 草稿正文。 */
|
|
586
696
|
content(docCode) {
|
|
587
697
|
return this.executeOpen(
|
|
@@ -623,6 +733,9 @@ var DocApi = class extends OpenAbstractApi {
|
|
|
623
733
|
return this.executeOpen("POST", "/push/api/open/doc/updateShare", body);
|
|
624
734
|
}
|
|
625
735
|
};
|
|
736
|
+
function guessDocxContentType(name) {
|
|
737
|
+
return name.toLowerCase().endsWith(".docx") ? "application/vnd.openxmlformats-officedocument.wordprocessingml.document" : "application/octet-stream";
|
|
738
|
+
}
|
|
626
739
|
|
|
627
740
|
// src/api/excel-api.ts
|
|
628
741
|
var ExcelApi = class extends OpenAbstractApi {
|
|
@@ -641,6 +754,19 @@ var ExcelApi = class extends OpenAbstractApi {
|
|
|
641
754
|
create(title) {
|
|
642
755
|
return this.executeOpen("POST", "/push/api/open/excel/create", { title });
|
|
643
756
|
}
|
|
757
|
+
/**
|
|
758
|
+
* 导入 Excel(.xlsx / .xls)创建表格。
|
|
759
|
+
*
|
|
760
|
+
* 标题默认取文件名;创建后默认关闭分享,需再调用 publish 才会同步到分享页。
|
|
761
|
+
*/
|
|
762
|
+
async importExcel(file, fileName = "workbook.xlsx") {
|
|
763
|
+
const bytes = await toFileBytes(file);
|
|
764
|
+
const name = fileName && fileName.trim() ? fileName : "workbook.xlsx";
|
|
765
|
+
return this.executeOpenMultipart(
|
|
766
|
+
"/push/api/open/excel/import",
|
|
767
|
+
buildFileMultipart(name, guessExcelContentType(name), bytes)
|
|
768
|
+
);
|
|
769
|
+
}
|
|
644
770
|
/** 获取表格元信息与整表 JSON 草稿。 */
|
|
645
771
|
content(docCode) {
|
|
646
772
|
return this.executeOpen(
|
|
@@ -711,6 +837,16 @@ function stringifyJsonContent(content) {
|
|
|
711
837
|
throw new PushPlusError(`\u5E8F\u5217\u5316\u8868\u683C\u5185\u5BB9\u5931\u8D25: ${e.message}`, -1, { cause: e });
|
|
712
838
|
}
|
|
713
839
|
}
|
|
840
|
+
function guessExcelContentType(name) {
|
|
841
|
+
const lower = name.toLowerCase();
|
|
842
|
+
if (lower.endsWith(".xlsx")) {
|
|
843
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
844
|
+
}
|
|
845
|
+
if (lower.endsWith(".xls")) {
|
|
846
|
+
return "application/vnd.ms-excel";
|
|
847
|
+
}
|
|
848
|
+
return "application/octet-stream";
|
|
849
|
+
}
|
|
714
850
|
|
|
715
851
|
// src/api/form-api.ts
|
|
716
852
|
var FormApi = class extends OpenAbstractApi {
|
|
@@ -806,6 +942,40 @@ var FriendApi = class extends OpenAbstractApi {
|
|
|
806
942
|
async editRemark(id, remark) {
|
|
807
943
|
await this.executeOpen("POST", "/api/open/friend/editRemark", { id, remark });
|
|
808
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* 5. 将好友加入黑名单。
|
|
947
|
+
*
|
|
948
|
+
* 加入后将解除双方好友关系,对方无法再添加你。不能将自己加入黑名单,仅可将已有好友加入黑名单。
|
|
949
|
+
*
|
|
950
|
+
* @param friendId 好友 id(好友列表中的 friendId 字段)
|
|
951
|
+
*/
|
|
952
|
+
async addBlacklist(friendId) {
|
|
953
|
+
await this.executeOpen(
|
|
954
|
+
"POST",
|
|
955
|
+
this.appendQuery("/api/open/friend/addBlacklist", { friendId })
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
/** 6. 好友黑名单列表。 */
|
|
959
|
+
blacklistList(query) {
|
|
960
|
+
return this.executeOpen(
|
|
961
|
+
"POST",
|
|
962
|
+
"/api/open/friend/blacklistList",
|
|
963
|
+
query != null ? query : {}
|
|
964
|
+
);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* 7. 解除好友黑名单。
|
|
968
|
+
*
|
|
969
|
+
* 解除后不会自动恢复好友关系,需重新扫码添加。
|
|
970
|
+
*
|
|
971
|
+
* @param id 黑名单记录 ID(黑名单列表中的 id 字段)
|
|
972
|
+
*/
|
|
973
|
+
async removeBlacklist(id) {
|
|
974
|
+
await this.executeOpen(
|
|
975
|
+
"POST",
|
|
976
|
+
this.appendQuery("/api/open/friend/removeBlacklist", { id })
|
|
977
|
+
);
|
|
978
|
+
}
|
|
809
979
|
};
|
|
810
980
|
|
|
811
981
|
// src/api/image-api.ts
|
|
@@ -854,7 +1024,7 @@ var ImageApi = class extends OpenAbstractApi {
|
|
|
854
1024
|
}
|
|
855
1025
|
const fileName = options.fileName || "file";
|
|
856
1026
|
const contentType = options.contentType || guessContentTypeByName(fileName) || "application/octet-stream";
|
|
857
|
-
const boundary = "----PushPlusBoundary" +
|
|
1027
|
+
const boundary = "----PushPlusBoundary" + randomBoundarySuffix2();
|
|
858
1028
|
const body = buildMultipartBody(boundary, uploadToken, fileName, contentType, bytes);
|
|
859
1029
|
const resp = await callExecuteRaw(this.http, {
|
|
860
1030
|
method: "POST",
|
|
@@ -940,7 +1110,7 @@ function buildMultipartBody(boundary, uploadToken, fileName, contentType, fileBy
|
|
|
940
1110
|
const crlf = "\r\n";
|
|
941
1111
|
const enc = new TextEncoder();
|
|
942
1112
|
const head = enc.encode(
|
|
943
|
-
`--${boundary}${crlf}Content-Disposition: form-data; name="token"${crlf}${crlf}${uploadToken}${crlf}--${boundary}${crlf}Content-Disposition: form-data; name="file"; filename="${
|
|
1113
|
+
`--${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}`
|
|
944
1114
|
);
|
|
945
1115
|
const tail = enc.encode(`${crlf}--${boundary}--${crlf}`);
|
|
946
1116
|
const out = new Uint8Array(head.byteLength + fileBytes.byteLength + tail.byteLength);
|
|
@@ -949,7 +1119,7 @@ function buildMultipartBody(boundary, uploadToken, fileName, contentType, fileBy
|
|
|
949
1119
|
out.set(tail, head.byteLength + fileBytes.byteLength);
|
|
950
1120
|
return out;
|
|
951
1121
|
}
|
|
952
|
-
function
|
|
1122
|
+
function escapeFileName2(name) {
|
|
953
1123
|
return name.replace(/"/g, "_").replace(/\r/g, " ").replace(/\n/g, " ");
|
|
954
1124
|
}
|
|
955
1125
|
function guessContentTypeByName(name) {
|
|
@@ -962,7 +1132,7 @@ function guessContentTypeByName(name) {
|
|
|
962
1132
|
if (lower.endsWith(".svg")) return "image/svg+xml";
|
|
963
1133
|
return null;
|
|
964
1134
|
}
|
|
965
|
-
function
|
|
1135
|
+
function randomBoundarySuffix2() {
|
|
966
1136
|
let s = "";
|
|
967
1137
|
for (let i = 0; i < 32; i++) {
|
|
968
1138
|
s += Math.floor(Math.random() * 16).toString(16);
|
|
@@ -1305,6 +1475,42 @@ var TopicUserApi = class extends OpenAbstractApi {
|
|
|
1305
1475
|
async editRemark(id, remark) {
|
|
1306
1476
|
await this.executeOpen("POST", "/api/open/topicUser/editRemark", { id, remark });
|
|
1307
1477
|
}
|
|
1478
|
+
/**
|
|
1479
|
+
* 4. 将订阅人加入黑名单。
|
|
1480
|
+
*
|
|
1481
|
+
* 加入后将移出群组,对方无法再加入该群组。积分群组不支持黑名单。不能将自己加入黑名单。
|
|
1482
|
+
*
|
|
1483
|
+
* @param topicRelationId 用户编号(订阅人列表中的 id 字段)
|
|
1484
|
+
*/
|
|
1485
|
+
async addBlacklist(topicRelationId) {
|
|
1486
|
+
const path = this.appendQuery("/api/open/topicUser/addBlacklist", { topicRelationId });
|
|
1487
|
+
await this.executeOpen("POST", path);
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* 5. 订阅人黑名单列表。
|
|
1491
|
+
*
|
|
1492
|
+
* `query.params.topicId` 必填。
|
|
1493
|
+
*/
|
|
1494
|
+
blacklistList(query) {
|
|
1495
|
+
return this.executeOpen(
|
|
1496
|
+
"POST",
|
|
1497
|
+
"/api/open/topicUser/blacklistList",
|
|
1498
|
+
query
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* 6. 解除订阅人黑名单。
|
|
1503
|
+
*
|
|
1504
|
+
* 解除后不会自动恢复群组订阅,对方可重新加入该群组。
|
|
1505
|
+
*
|
|
1506
|
+
* @param id 黑名单记录 ID(黑名单列表中的 id 字段)
|
|
1507
|
+
*/
|
|
1508
|
+
async removeBlacklist(id) {
|
|
1509
|
+
await this.executeOpen(
|
|
1510
|
+
"POST",
|
|
1511
|
+
this.appendQuery("/api/open/topicUser/removeBlacklist", { id })
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1308
1514
|
};
|
|
1309
1515
|
|
|
1310
1516
|
// src/api/user-api.ts
|
|
@@ -1369,7 +1575,7 @@ function resolveConfig(input) {
|
|
|
1369
1575
|
logRequest: (_g = cfg.logRequest) != null ? _g : false,
|
|
1370
1576
|
rateLimitGuardEnabled: (_h = cfg.rateLimitGuardEnabled) != null ? _h : true,
|
|
1371
1577
|
rateLimitCooldownMs: (_i = cfg.rateLimitCooldownMs) != null ? _i : 0,
|
|
1372
|
-
userAgent: (_j = cfg.userAgent) != null ? _j : `@perk-net/perk-pushplus-sdk/1.2.
|
|
1578
|
+
userAgent: (_j = cfg.userAgent) != null ? _j : `@perk-net/perk-pushplus-sdk/1.2.1`
|
|
1373
1579
|
};
|
|
1374
1580
|
}
|
|
1375
1581
|
|