@meet-im/meet-bot-jssdk 0.0.5 → 0.0.7

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.cjs CHANGED
@@ -1,5 +1,11 @@
1
1
  'use strict';
2
2
 
3
+ var crypto = require('crypto');
4
+
5
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
6
+
7
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
8
+
3
9
  // @meet/meet-bot-jssdk - MeetIM Chatbot JavaScript SDK
4
10
  var __defProp = Object.defineProperty;
5
11
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -92,7 +98,9 @@ var HTTP = {
92
98
  /** 请求超时时间(毫秒),默认 60000ms */
93
99
  DEFAULT_TIMEOUT: 6e4,
94
100
  /** 长轮询额外超时缓冲时间(毫秒),默认 10000ms */
95
- POLLING_TIMEOUT_BUFFER: 1e4
101
+ POLLING_TIMEOUT_BUFFER: 1e4,
102
+ /** 上传超时时间(毫秒),默认 1 小时 */
103
+ UPLOAD_TIMEOUT: 3600 * 1e3
96
104
  };
97
105
  var API = {
98
106
  /** getUpdates 默认超时时间(秒),默认 30(long polling) */
@@ -100,6 +108,56 @@ var API = {
100
108
  /** getUpdates 默认拉取条数,默认 100 条 */
101
109
  DEFAULT_LIMIT: 100
102
110
  };
111
+ var UPLOAD = {
112
+ /** 分片上传阈值,>= 5MB 使用分片上传 */
113
+ MULTIPART_THRESHOLD: 5 * 1024 * 1024,
114
+ /** 分片上传最大重试次数 */
115
+ MAX_RETRY_COUNT: 3,
116
+ /** 分片上传重试间隔(毫秒) */
117
+ RETRY_DELAY: 1e3,
118
+ /** 最大并发上传数 */
119
+ MAX_CONCURRENCY: 4
120
+ };
121
+ var CHUNK_RULES = [
122
+ { maxSize: 1 * 1024 * 1024, chunks: 1 },
123
+ // < 1MB: 1 片
124
+ { maxSize: 20 * 1024 * 1024, chunks: 5 },
125
+ // 1-20MB: 5 片
126
+ { maxSize: 100 * 1024 * 1024, chunks: 20 },
127
+ // 20-100MB: 20 片
128
+ { maxSize: Infinity, chunks: 50 }
129
+ // > 100MB: 50 片
130
+ ];
131
+ function getChunkNum(size) {
132
+ for (const rule of CHUNK_RULES) {
133
+ if (size < rule.maxSize) {
134
+ return rule.chunks;
135
+ }
136
+ }
137
+ return 50;
138
+ }
139
+
140
+ // src/utils/logger.ts
141
+ var Logger = class {
142
+ level = "silent";
143
+ setLevel(level) {
144
+ this.level = level;
145
+ }
146
+ getLevel() {
147
+ return this.level;
148
+ }
149
+ info(...args) {
150
+ if (this.level === "info") {
151
+ console.log("[meetBotSDK]", ...args);
152
+ }
153
+ }
154
+ error(...args) {
155
+ if (this.level === "info") {
156
+ console.error("[meetBotSDK]", ...args);
157
+ }
158
+ }
159
+ };
160
+ var logger = new Logger();
103
161
 
104
162
  // src/http/request.ts
105
163
  function validateToken(token) {
@@ -136,9 +194,9 @@ async function request(options) {
136
194
  Accept: "application/json",
137
195
  Authorization: `Bot ${extractBotToken(token)}`
138
196
  };
139
- console.log("[meetBotSDK] [Request]", method, url);
197
+ logger.info("[Request]", method, url);
140
198
  if (body) {
141
- console.log("[meetBotSDK] [Body]", JSON.stringify(body));
199
+ logger.info("[Body]", JSON.stringify(body));
142
200
  }
143
201
  try {
144
202
  const response = await fetch(url, {
@@ -152,9 +210,21 @@ async function request(options) {
152
210
  response.headers.forEach((value, key) => {
153
211
  responseHeaders[key] = value;
154
212
  });
155
- console.log("[meetBotSDK] [Response Status]", response.status);
156
- const data = await response.json();
157
- console.log("[meetBotSDK] [Response Body]", JSON.stringify(data));
213
+ logger.info("[Response Status]", response.status);
214
+ const contentType = response.headers.get("content-type") || "";
215
+ const responseText = await response.text();
216
+ if (!contentType.includes("application/json")) {
217
+ logger.error("[Response] Non-JSON response:", responseText.substring(0, 500));
218
+ throw new exports.NetworkError(`Server returned non-JSON response (status ${response.status})`, void 0);
219
+ }
220
+ let data;
221
+ try {
222
+ data = JSON.parse(responseText);
223
+ } catch {
224
+ logger.error("[Response] Failed to parse JSON:", responseText.substring(0, 500));
225
+ throw new exports.NetworkError("Failed to parse server response", void 0);
226
+ }
227
+ logger.info("[Response Body]", JSON.stringify(data));
158
228
  if (!response.ok || !data.ok) {
159
229
  const errorData = data;
160
230
  throw new exports.ApiError(
@@ -198,6 +268,256 @@ function mapStatusCodeToErrorCode(status) {
198
268
  }
199
269
  }
200
270
 
271
+ // src/api/file.ts
272
+ async function getUploadURL(params) {
273
+ const { token, baseUrl, ...body } = params;
274
+ return request({
275
+ token,
276
+ baseUrl,
277
+ method: "POST",
278
+ path: "getUploadURL",
279
+ body: {
280
+ originFileName: body.originFileName,
281
+ contentType: body.contentType,
282
+ md5: body.md5,
283
+ size: body.size,
284
+ fullImage: body.fullImage,
285
+ videoLength: body.videoLength,
286
+ bestDomain: body.bestDomain,
287
+ uploadId: body.uploadId,
288
+ chunkNum: body.chunkNum
289
+ },
290
+ timeout: HTTP.UPLOAD_TIMEOUT
291
+ });
292
+ }
293
+ async function getMultiPartUploadURL(params) {
294
+ const { token, baseUrl, ...body } = params;
295
+ return request({
296
+ token,
297
+ baseUrl,
298
+ method: "POST",
299
+ path: "getMultiPartUploadURL",
300
+ body: {
301
+ originFileName: body.originFileName,
302
+ contentType: body.contentType,
303
+ md5: body.md5,
304
+ size: body.size,
305
+ fullImage: body.fullImage,
306
+ videoLength: body.videoLength,
307
+ bestDomain: body.bestDomain
308
+ },
309
+ timeout: HTTP.UPLOAD_TIMEOUT
310
+ });
311
+ }
312
+ async function completeMultipartUpload(params) {
313
+ const { token, baseUrl, ...body } = params;
314
+ return request({
315
+ token,
316
+ baseUrl,
317
+ method: "POST",
318
+ path: "completeMultipartUpload",
319
+ body: {
320
+ originFileName: body.originFileName,
321
+ md5: body.md5,
322
+ UploadParts: body.UploadParts
323
+ },
324
+ timeout: HTTP.UPLOAD_TIMEOUT
325
+ });
326
+ }
327
+ async function getAccessURL(params) {
328
+ const { token, baseUrl, ...queryParams } = params;
329
+ return request({
330
+ token,
331
+ baseUrl,
332
+ method: "GET",
333
+ path: "getAccessURL",
334
+ params: {
335
+ firstId: queryParams.firstId,
336
+ secondId: queryParams.secondId,
337
+ sessionType: queryParams.sessionType,
338
+ seqId: queryParams.seqId,
339
+ fileId: queryParams.fileId,
340
+ "x-oss-process": queryParams["x-oss-process"],
341
+ origin: queryParams.origin,
342
+ bestDomain: queryParams.bestDomain,
343
+ printResult: queryParams.printResult ?? "1"
344
+ }
345
+ });
346
+ }
347
+ async function computeMD5(buffer) {
348
+ return crypto__default.default.createHash("md5").update(buffer).digest("hex");
349
+ }
350
+ async function uploadToOSS(signedUrl, buffer, contentType, callback, onProgress) {
351
+ const startTime = Date.now();
352
+ const total = buffer.length;
353
+ const response = await fetch(signedUrl, {
354
+ method: "PUT",
355
+ headers: {
356
+ "Content-Type": contentType,
357
+ "X-Oss-Callback": callback
358
+ },
359
+ body: buffer
360
+ });
361
+ if (!response.ok) {
362
+ const errorBody = await response.text().catch(() => "");
363
+ logger.error(`OSS upload failed: ${response.status} ${response.statusText}`);
364
+ logger.error(`OSS error body: ${errorBody}`);
365
+ throw new Error(`OSS upload failed: ${response.status} - ${errorBody}`);
366
+ }
367
+ const responseText = await response.text();
368
+ logger.info(`[uploadToOSS] OSS response status: ${response.status}`);
369
+ logger.info(`[uploadToOSS] OSS response: ${responseText}`);
370
+ let result;
371
+ try {
372
+ const parsed = JSON.parse(responseText);
373
+ result = {
374
+ id: parsed.id || parsed.ID || 0,
375
+ path: parsed.path || "",
376
+ size: parsed.size || 0
377
+ };
378
+ } catch {
379
+ const idMatch = responseText.match(/<id>(\d+)<\/id>/i);
380
+ const pathMatch = responseText.match(/<path>([^<]*)<\/path>/i);
381
+ const sizeMatch = responseText.match(/<size>(\d+)<\/size>/i);
382
+ result = {
383
+ id: idMatch?.[1] ? parseInt(idMatch[1], 10) : 0,
384
+ path: pathMatch?.[1] || "",
385
+ size: sizeMatch?.[1] ? parseInt(sizeMatch[1], 10) : 0
386
+ };
387
+ }
388
+ if (onProgress) {
389
+ const seconds = Math.round((Date.now() - startTime) / 1e3) || 1;
390
+ const speedPerSecond = formatSpeed(total / seconds);
391
+ onProgress({
392
+ percent: "100%",
393
+ loaded: total,
394
+ total,
395
+ speedPerSecond,
396
+ percentRate: 1
397
+ });
398
+ }
399
+ return result;
400
+ }
401
+ function formatSpeed(bytesPerSecond) {
402
+ if (bytesPerSecond < 1024) {
403
+ return `${bytesPerSecond.toFixed(0)}B/s`;
404
+ } else if (bytesPerSecond < 1024 * 1024) {
405
+ return `${(bytesPerSecond / 1024).toFixed(2)}KB/s`;
406
+ } else {
407
+ return `${(bytesPerSecond / 1024 / 1024).toFixed(2)}MB/s`;
408
+ }
409
+ }
410
+ async function uploadFile(token, buffer, options, baseUrl) {
411
+ const { fileName, contentType, onProgress } = options;
412
+ const size = buffer.length;
413
+ const md5 = await computeMD5(buffer);
414
+ logger.info(`[uploadFile] Starting upload: ${fileName}, size: ${size}, md5: ${md5}`);
415
+ if (size < UPLOAD.MULTIPART_THRESHOLD) {
416
+ return uploadSingleFile(token, buffer, fileName, contentType, md5, baseUrl, onProgress);
417
+ } else {
418
+ return uploadMultipartFile(token, buffer, fileName, contentType, md5, baseUrl, onProgress);
419
+ }
420
+ }
421
+ async function uploadSingleFile(token, buffer, fileName, contentType, md5, baseUrl, onProgress) {
422
+ const result = await getUploadURL({
423
+ token,
424
+ baseUrl,
425
+ originFileName: fileName,
426
+ contentType,
427
+ md5,
428
+ size: buffer.length
429
+ });
430
+ if (result.id > 0 && !result.signedUrl) {
431
+ logger.info(`[uploadFile] Instant upload (MD5 match): ${result.id}`);
432
+ return { fileID: result.id, path: result.path, size: result.size };
433
+ }
434
+ const ossResult = await uploadToOSS(result.signedUrl, buffer, contentType, result.callback || "", onProgress);
435
+ return { fileID: ossResult.id, path: ossResult.path, size: ossResult.size };
436
+ }
437
+ async function uploadMultipartFile(token, buffer, fileName, contentType, md5, baseUrl, onProgress) {
438
+ const size = buffer.length;
439
+ const chunkNum = getChunkNum(size);
440
+ logger.info(`[uploadMultipartFile] Starting multipart upload: ${chunkNum} chunks`);
441
+ const result = await getMultiPartUploadURL({
442
+ token,
443
+ baseUrl,
444
+ originFileName: fileName,
445
+ contentType,
446
+ md5,
447
+ size
448
+ });
449
+ if (result.ID && result.ID > 0) {
450
+ logger.info(`[uploadMultipartFile] Instant upload (MD5 match): ${result.ID}`);
451
+ return { fileID: result.ID, path: result.path, size: result.size };
452
+ }
453
+ const uploadParts = [];
454
+ const chunkSize = Math.ceil(size / chunkNum);
455
+ const startTime = Date.now();
456
+ let totalUploaded = 0;
457
+ for (let i = 1; i <= chunkNum; i++) {
458
+ const start = (i - 1) * chunkSize;
459
+ const end = Math.min(start + chunkSize, size);
460
+ const chunk = buffer.subarray(start, end);
461
+ const signedUrl = result.mapSignURLs[String(i)];
462
+ if (!signedUrl) {
463
+ throw new Error(`Missing signed URL for chunk ${i}`);
464
+ }
465
+ const resp = await fetch(signedUrl, {
466
+ method: "PUT",
467
+ headers: { "Content-Type": contentType },
468
+ body: chunk
469
+ });
470
+ if (!resp.ok) {
471
+ throw new Error(`Chunk ${i} upload failed: ${resp.status}`);
472
+ }
473
+ const eTag = resp.headers.get("ETag") || "";
474
+ uploadParts.push({ partNumber: i, eTag: JSON.parse(eTag) });
475
+ totalUploaded += chunk.length;
476
+ if (onProgress) {
477
+ const seconds = Math.round((Date.now() - startTime) / 1e3) || 1;
478
+ const speedPerSecond = formatSpeed(totalUploaded / seconds);
479
+ onProgress({
480
+ percent: `${Math.round(totalUploaded / size * 100)}%`,
481
+ loaded: totalUploaded,
482
+ total: size,
483
+ speedPerSecond,
484
+ percentRate: totalUploaded / size
485
+ });
486
+ }
487
+ }
488
+ const complete = await completeMultipartUpload({
489
+ token,
490
+ baseUrl,
491
+ originFileName: fileName,
492
+ md5,
493
+ UploadParts: uploadParts
494
+ });
495
+ logger.info(`[uploadMultipartFile] Upload complete: ${complete.ID}`);
496
+ return { fileID: complete.ID, path: complete.path, size };
497
+ }
498
+ async function sendMediaMessage(token, sessionInfo, options, baseUrl) {
499
+ const { buffer, fileName, contentType, content, onProgress } = options;
500
+ const { fileID, path } = await uploadFile(token, buffer, { fileName, contentType, onProgress }, baseUrl);
501
+ logger.info(`[sendMediaMessage] fileID=${fileID}, path=${path}`);
502
+ return sendMessage({
503
+ token,
504
+ baseUrl,
505
+ sessionInfo,
506
+ msgContent: {
507
+ content: content || "",
508
+ extraInfo: {
509
+ attechmentInfo: {
510
+ fileID: String(fileID),
511
+ fileName,
512
+ filePath: path,
513
+ fileSize: buffer.length,
514
+ mimeType: contentType
515
+ }
516
+ }
517
+ }
518
+ });
519
+ }
520
+
201
521
  // src/api/index.ts
202
522
  async function getUpdates(params) {
203
523
  const { token, baseUrl, timeout = API.DEFAULT_TIMEOUT, offset, limit = API.DEFAULT_LIMIT } = params;
@@ -216,9 +536,11 @@ async function getUpdates(params) {
216
536
  }
217
537
  async function sendMessage(params) {
218
538
  const { token, baseUrl, sessionInfo, msgContent } = params;
219
- if (!msgContent.content || msgContent.content.trim() === "") {
539
+ const hasContent = msgContent.content && msgContent.content.trim() !== "";
540
+ const hasAttachment = msgContent.extraInfo?.attechmentInfo || msgContent.extraInfo?.attechmentInfos && msgContent.extraInfo.attechmentInfos.length > 0;
541
+ if (!hasContent && !hasAttachment) {
220
542
  const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_error(), error_exports));
221
- throw new ValidationError2("Message text cannot be empty", "content", msgContent.content);
543
+ throw new ValidationError2("Message content or attachment is required", "msgContent", msgContent);
222
544
  }
223
545
  return request({
224
546
  token,
@@ -257,6 +579,7 @@ var MeetBot = class {
257
579
  this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
258
580
  this.pollingLimit = config.pollingLimit ?? POLLING.DEFAULT_LIMIT;
259
581
  this.longPollingTimeout = config.longPollingTimeout ?? POLLING.DEFAULT_TIMEOUT;
582
+ logger.setLevel(config.logLevel ?? "silent");
260
583
  }
261
584
  on(event, handler) {
262
585
  if (!this.eventHandlers.has(event)) {
@@ -279,7 +602,7 @@ var MeetBot = class {
279
602
  try {
280
603
  handler(data);
281
604
  } catch (e) {
282
- console.error("[meetBotSDK]", `Error in ${event} handler:`, e);
605
+ logger.error(`Error in ${event} handler:`, e);
283
606
  }
284
607
  }
285
608
  }
@@ -295,7 +618,7 @@ var MeetBot = class {
295
618
  this.abortController = new AbortController();
296
619
  const limit = options?.limit ?? this.pollingLimit;
297
620
  const timeout = options?.timeout ?? this.longPollingTimeout;
298
- console.log("[meetBotSDK]", `\u5F00\u59CB\u957F\u8F6E\u8BE2\u6D88\u606F... (\u6761\u6570: ${limit}, \u8D85\u65F6: ${timeout}s)`);
621
+ logger.info(`\u5F00\u59CB\u957F\u8F6E\u8BE2\u6D88\u606F... (\u6761\u6570: ${limit}, \u8D85\u65F6: ${timeout}s)`);
299
622
  this.emit("polling_start", void 0);
300
623
  const retryDelay = options?.retryDelay ?? POLLING.DEFAULT_RETRY_DELAY;
301
624
  const maxRetries = options?.maxRetries ?? POLLING.DEFAULT_MAX_RETRIES;
@@ -355,6 +678,58 @@ var MeetBot = class {
355
678
  msgContent
356
679
  });
357
680
  }
681
+ /**
682
+ * 获取单文件上传签名地址
683
+ */
684
+ async getUploadURL(params) {
685
+ return getUploadURL({
686
+ token: this.token,
687
+ baseUrl: this.baseUrl,
688
+ ...params
689
+ });
690
+ }
691
+ /**
692
+ * 获取分片上传签名地址
693
+ */
694
+ async getMultiPartUploadURL(params) {
695
+ return getMultiPartUploadURL({
696
+ token: this.token,
697
+ baseUrl: this.baseUrl,
698
+ ...params
699
+ });
700
+ }
701
+ /**
702
+ * 完成分片上传
703
+ */
704
+ async completeMultipartUpload(params) {
705
+ return completeMultipartUpload({
706
+ token: this.token,
707
+ baseUrl: this.baseUrl,
708
+ ...params
709
+ });
710
+ }
711
+ /**
712
+ * 获取文件下载地址
713
+ */
714
+ async getAccessURL(params) {
715
+ return getAccessURL({
716
+ token: this.token,
717
+ baseUrl: this.baseUrl,
718
+ ...params
719
+ });
720
+ }
721
+ /**
722
+ * 上传文件(自动选择单文件或分片上传)
723
+ */
724
+ async uploadFile(buffer, options) {
725
+ return uploadFile(this.token, buffer, options, this.baseUrl);
726
+ }
727
+ /**
728
+ * 发送媒体消息(上传并发送)
729
+ */
730
+ async sendMedia(sessionInfo, options) {
731
+ return sendMediaMessage(this.token, sessionInfo, options, this.baseUrl);
732
+ }
358
733
  sleep(ms) {
359
734
  return new Promise((resolve) => setTimeout(resolve, ms));
360
735
  }
@@ -364,11 +739,21 @@ var MeetBot = class {
364
739
  init_error();
365
740
 
366
741
  exports.API = API;
742
+ exports.CHUNK_RULES = CHUNK_RULES;
367
743
  exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
368
744
  exports.HTTP = HTTP;
369
745
  exports.MeetBot = MeetBot;
370
746
  exports.POLLING = POLLING;
747
+ exports.UPLOAD = UPLOAD;
748
+ exports.completeMultipartUpload = completeMultipartUpload;
749
+ exports.computeMD5 = computeMD5;
750
+ exports.getAccessURL = getAccessURL;
751
+ exports.getChunkNum = getChunkNum;
752
+ exports.getMultiPartUploadURL = getMultiPartUploadURL;
371
753
  exports.getUpdates = getUpdates;
754
+ exports.getUploadURL = getUploadURL;
755
+ exports.sendMediaMessage = sendMediaMessage;
372
756
  exports.sendMessage = sendMessage;
757
+ exports.uploadFile = uploadFile;
373
758
  //# sourceMappingURL=index.cjs.map
374
759
  //# sourceMappingURL=index.cjs.map