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