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