@perk-net/perk-pushplus-sdk 1.0.0 → 1.0.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.
@@ -186,6 +186,25 @@ var PerkPushPlus = (function (exports) {
186
186
  };
187
187
 
188
188
  // src/http.ts
189
+ async function callExecuteRaw(requester, options) {
190
+ if (typeof requester.executeRaw === "function") {
191
+ return requester.executeRaw(options);
192
+ }
193
+ const { method, url, headers, body } = options;
194
+ let text = null;
195
+ if (body != null) {
196
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
197
+ text = await body.text();
198
+ } else if (body instanceof Uint8Array) {
199
+ text = new TextDecoder("utf-8").decode(body);
200
+ } else if (body instanceof ArrayBuffer) {
201
+ text = new TextDecoder("utf-8").decode(new Uint8Array(body));
202
+ } else {
203
+ text = String(body);
204
+ }
205
+ }
206
+ return requester.execute({ method, url, headers, body: text });
207
+ }
189
208
  var FetchHttpRequester = class {
190
209
  constructor(config, fetchImpl) {
191
210
  this.readTimeoutMs = config.readTimeoutMs;
@@ -201,7 +220,29 @@ var PerkPushPlus = (function (exports) {
201
220
  }
202
221
  async execute(options) {
203
222
  var _a, _b;
204
- const { method, url, headers, body } = options;
223
+ return this.doExecute({
224
+ method: options.method,
225
+ url: options.url,
226
+ headers: options.headers,
227
+ body: (_a = options.body) != null ? _a : null,
228
+ bodyForLog: (_b = options.body) != null ? _b : null,
229
+ defaultContentType: "application/json;charset=UTF-8"
230
+ });
231
+ }
232
+ async executeRaw(options) {
233
+ var _a;
234
+ return this.doExecute({
235
+ method: options.method,
236
+ url: options.url,
237
+ headers: options.headers,
238
+ body: (_a = options.body) != null ? _a : null,
239
+ bodyForLog: null,
240
+ defaultContentType: "application/octet-stream"
241
+ });
242
+ }
243
+ async doExecute(args) {
244
+ var _a, _b;
245
+ const { method, url, headers, body, bodyForLog, defaultContentType } = args;
205
246
  const finalHeaders = {};
206
247
  let hasContentType = false;
207
248
  if (headers) {
@@ -211,14 +252,19 @@ var PerkPushPlus = (function (exports) {
211
252
  if (k.toLowerCase() === "content-type") hasContentType = true;
212
253
  }
213
254
  }
214
- if (body != null && !hasContentType) {
215
- finalHeaders["Content-Type"] = "application/json;charset=UTF-8";
255
+ if (body != null && !hasContentType && defaultContentType) {
256
+ finalHeaders["Content-Type"] = defaultContentType;
216
257
  }
217
258
  if (typeof window === "undefined" && !finalHeaders["User-Agent"] && !finalHeaders["user-agent"]) {
218
259
  finalHeaders["User-Agent"] = this.userAgent;
219
260
  }
220
261
  if (this.logRequest) {
221
- console.debug("[pushplus] >>>", method, url, "body=", body);
262
+ if (bodyForLog != null) {
263
+ console.debug("[pushplus] >>>", method, url, "body=", bodyForLog);
264
+ } else {
265
+ const len = bodyLength(body);
266
+ console.debug("[pushplus] >>>", method, url, "bodyBytes=", len);
267
+ }
222
268
  }
223
269
  const controller = new AbortController();
224
270
  const timer = this.readTimeoutMs > 0 ? setTimeout(() => controller.abort(), this.readTimeoutMs) : null;
@@ -250,6 +296,14 @@ var PerkPushPlus = (function (exports) {
250
296
  }
251
297
  }
252
298
  };
299
+ function bodyLength(body) {
300
+ if (body == null) return 0;
301
+ if (typeof body === "string") return body.length;
302
+ if (body instanceof Uint8Array) return body.byteLength;
303
+ if (body instanceof ArrayBuffer) return body.byteLength;
304
+ if (typeof Blob !== "undefined" && body instanceof Blob) return body.size;
305
+ return -1;
306
+ }
253
307
  function isSuccessfulHttpStatus(status) {
254
308
  return status >= 200 && status < 300;
255
309
  }
@@ -521,6 +575,168 @@ var PerkPushPlus = (function (exports) {
521
575
  }
522
576
  };
523
577
 
578
+ // src/api/image-api.ts
579
+ var ImageApi = class extends OpenAbstractApi {
580
+ constructor(config, http, mgr) {
581
+ super(config, http, mgr);
582
+ }
583
+ /** 1. 获取上传凭证。 */
584
+ getUploadToken() {
585
+ return this.executeOpen("GET", "/api/open/userImage/uploadToken");
586
+ }
587
+ /**
588
+ * 2. 上传图片到七牛云。
589
+ *
590
+ * 使用「获取上传凭证」返回的 `uploadUrl` 与 `uploadToken`,
591
+ * 按七牛云表单上传规范以 `multipart/form-data` 提交。该请求
592
+ * **不会** 携带 PushPlus 的 `access-key` 头。
593
+ */
594
+ async upload(token, file, options) {
595
+ if (token == null) {
596
+ throw new PushPlusError("\u4E0A\u4F20\u51ED\u8BC1 token \u4E0D\u80FD\u4E3A null");
597
+ }
598
+ if (!token.uploadToken) {
599
+ throw new PushPlusError("\u4E0A\u4F20\u51ED\u8BC1 uploadToken \u4E0D\u80FD\u4E3A\u7A7A");
600
+ }
601
+ const uploadUrl = token.uploadUrl || token.uploadHost;
602
+ if (!uploadUrl) {
603
+ throw new PushPlusError("\u4E0A\u4F20\u51ED\u8BC1\u672A\u8FD4\u56DE uploadUrl/uploadHost");
604
+ }
605
+ return this.uploadToQiniu(uploadUrl, token.uploadToken, file, options);
606
+ }
607
+ /**
608
+ * 2. 上传图片到七牛云(低层方法)。直接指定上传地址与 token。
609
+ */
610
+ async uploadToQiniu(uploadUrl, uploadToken, file, options) {
611
+ var _a, _b;
612
+ if (!uploadUrl) {
613
+ throw new PushPlusError("uploadUrl \u4E0D\u80FD\u4E3A\u7A7A");
614
+ }
615
+ if (!uploadToken) {
616
+ throw new PushPlusError("uploadToken \u4E0D\u80FD\u4E3A\u7A7A");
617
+ }
618
+ const bytes = await toUint8Array(file);
619
+ if (bytes.byteLength === 0) {
620
+ throw new PushPlusError("\u4E0A\u4F20\u6587\u4EF6\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A");
621
+ }
622
+ const fileName = options.fileName || "file";
623
+ const contentType = options.contentType || guessContentTypeByName(fileName) || "application/octet-stream";
624
+ const boundary = "----PushPlusBoundary" + randomBoundarySuffix();
625
+ const body = buildMultipartBody(boundary, uploadToken, fileName, contentType, bytes);
626
+ const resp = await callExecuteRaw(this.http, {
627
+ method: "POST",
628
+ url: uploadUrl,
629
+ headers: { "Content-Type": `multipart/form-data; boundary=${boundary}` },
630
+ body
631
+ });
632
+ if (!isSuccessfulHttpStatus(resp.statusCode)) {
633
+ throw new PushPlusError(
634
+ `\u4E0A\u4F20\u56FE\u7247\u5230\u4E03\u725B\u4E91\u5931\u8D25: status=${resp.statusCode}, body=${resp.body}`,
635
+ resp.statusCode
636
+ );
637
+ }
638
+ let result;
639
+ try {
640
+ result = JSON.parse(resp.body);
641
+ } catch (e) {
642
+ throw new PushPlusError(
643
+ `\u89E3\u6790\u4E03\u725B\u4E91\u54CD\u5E94\u5931\u8D25: ${e.message}, payload=${resp.body}`,
644
+ -1,
645
+ { cause: e }
646
+ );
647
+ }
648
+ if (result == null || typeof result !== "object") {
649
+ throw new PushPlusError(`\u4E03\u725B\u4E91\u8FD4\u56DE\u975E JSON \u5BF9\u8C61: ${resp.body}`);
650
+ }
651
+ if (result.errno !== 0) {
652
+ throw new PushPlusError(
653
+ `\u4E03\u725B\u4E91\u4E0A\u4F20\u5931\u8D25: errno=${result.errno}, msg=${(_a = result.msg) != null ? _a : ""}`,
654
+ (_b = result.errno) != null ? _b : -1
655
+ );
656
+ }
657
+ return result;
658
+ }
659
+ /**
660
+ * 便捷方法:自动获取上传凭证后上传字节数组 / Blob / ArrayBuffer。
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * await client.image.uploadBytes(buffer, { fileName: 'a.png' });
665
+ * await client.image.uploadBytes(blob, { fileName: 'b.jpg', contentType: 'image/jpeg' });
666
+ * ```
667
+ */
668
+ async uploadBytes(file, options) {
669
+ const token = await this.getUploadToken();
670
+ return this.upload(token, file, options);
671
+ }
672
+ /** 3. 图片列表。 */
673
+ list(query) {
674
+ return this.executeOpen(
675
+ "POST",
676
+ "/api/open/userImage/list",
677
+ query != null ? query : {}
678
+ );
679
+ }
680
+ /**
681
+ * 4. 主动删除图片;未删除的图片默认 30 天后由系统自动清理。
682
+ */
683
+ async delete(id) {
684
+ await this.executeOpen(
685
+ "DELETE",
686
+ this.appendQuery("/api/open/userImage/delete", { id })
687
+ );
688
+ }
689
+ };
690
+ async function toUint8Array(file) {
691
+ if (file == null) {
692
+ throw new PushPlusError("\u4E0A\u4F20\u6587\u4EF6\u4E0D\u80FD\u4E3A null");
693
+ }
694
+ if (file instanceof Uint8Array) {
695
+ return file;
696
+ }
697
+ if (file instanceof ArrayBuffer) {
698
+ return new Uint8Array(file);
699
+ }
700
+ if (typeof Blob !== "undefined" && file instanceof Blob) {
701
+ const ab = await file.arrayBuffer();
702
+ return new Uint8Array(ab);
703
+ }
704
+ throw new PushPlusError(`\u4E0D\u652F\u6301\u7684\u4E0A\u4F20\u6587\u4EF6\u7C7B\u578B: ${Object.prototype.toString.call(file)}`);
705
+ }
706
+ function buildMultipartBody(boundary, uploadToken, fileName, contentType, fileBytes) {
707
+ const crlf = "\r\n";
708
+ const enc = new TextEncoder();
709
+ const head = enc.encode(
710
+ `--${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}`
711
+ );
712
+ const tail = enc.encode(`${crlf}--${boundary}--${crlf}`);
713
+ const out = new Uint8Array(head.byteLength + fileBytes.byteLength + tail.byteLength);
714
+ out.set(head, 0);
715
+ out.set(fileBytes, head.byteLength);
716
+ out.set(tail, head.byteLength + fileBytes.byteLength);
717
+ return out;
718
+ }
719
+ function escapeFileName(name) {
720
+ return name.replace(/"/g, "_").replace(/\r/g, " ").replace(/\n/g, " ");
721
+ }
722
+ function guessContentTypeByName(name) {
723
+ const lower = name.toLowerCase();
724
+ if (lower.endsWith(".png")) return "image/png";
725
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
726
+ if (lower.endsWith(".gif")) return "image/gif";
727
+ if (lower.endsWith(".webp")) return "image/webp";
728
+ if (lower.endsWith(".bmp")) return "image/bmp";
729
+ if (lower.endsWith(".svg")) return "image/svg+xml";
730
+ return null;
731
+ }
732
+ function randomBoundarySuffix() {
733
+ let s = "";
734
+ for (let i = 0; i < 32; i++) {
735
+ s += Math.floor(Math.random() * 16).toString(16);
736
+ }
737
+ return s;
738
+ }
739
+
524
740
  // src/api/message-api.ts
525
741
  var MessageApi = class extends AbstractApi {
526
742
  constructor(config, http, rateLimitGuard) {
@@ -920,7 +1136,7 @@ var PerkPushPlus = (function (exports) {
920
1136
  logRequest: (_g = cfg.logRequest) != null ? _g : false,
921
1137
  rateLimitGuardEnabled: (_h = cfg.rateLimitGuardEnabled) != null ? _h : true,
922
1138
  rateLimitCooldownMs: (_i = cfg.rateLimitCooldownMs) != null ? _i : 0,
923
- userAgent: (_j = cfg.userAgent) != null ? _j : `@perk-net/perk-pushplus-sdk/1.0.0`
1139
+ userAgent: (_j = cfg.userAgent) != null ? _j : `@perk-net/perk-pushplus-sdk/1.0.1`
924
1140
  };
925
1141
  }
926
1142
 
@@ -1014,6 +1230,7 @@ var PerkPushPlus = (function (exports) {
1014
1230
  this.clawBot = new ClawBotApi(this.config, this.httpRequester, this.accessKeyManager);
1015
1231
  this.setting = new SettingApi(this.config, this.httpRequester, this.accessKeyManager);
1016
1232
  this.pre = new PreApi(this.config, this.httpRequester, this.accessKeyManager);
1233
+ this.image = new ImageApi(this.config, this.httpRequester, this.accessKeyManager);
1017
1234
  }
1018
1235
  /** 与 Java SDK 风格一致的 Builder 入口。 */
1019
1236
  static builder() {
@@ -1252,6 +1469,7 @@ var PerkPushPlus = (function (exports) {
1252
1469
  exports.ErrorCode = ErrorCode;
1253
1470
  exports.FetchHttpRequester = FetchHttpRequester;
1254
1471
  exports.FriendApi = FriendApi;
1472
+ exports.ImageApi = ImageApi;
1255
1473
  exports.MessageApi = MessageApi;
1256
1474
  exports.MessageTokenApi = MessageTokenApi;
1257
1475
  exports.OpenAbstractApi = OpenAbstractApi;
@@ -1274,6 +1492,7 @@ var PerkPushPlus = (function (exports) {
1274
1492
  exports.WebhookType = WebhookType;
1275
1493
  exports.WebhookTypeDescription = WebhookTypeDescription;
1276
1494
  exports.batchSendRequest = batchSendRequest;
1495
+ exports.callExecuteRaw = callExecuteRaw;
1277
1496
  exports.errorCodeFromValue = errorCodeFromValue;
1278
1497
  exports.isRateLimitedCode = isRateLimitedCode;
1279
1498
  exports.isSuccessfulHttpStatus = isSuccessfulHttpStatus;