@meet-im/meet-bot-jssdk 0.0.7 → 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/dist/index.cjs CHANGED
@@ -50,16 +50,34 @@ var init_error = __esm({
50
50
  };
51
51
  exports.NetworkError = class extends exports.MeetBotError {
52
52
  cause;
53
- constructor(message, cause) {
53
+ /**
54
+ * HTTP 状态码(如果有的话)
55
+ * 例如:504 表示网关超时,0 表示无 HTTP 响应
56
+ */
57
+ httpStatus;
58
+ /**
59
+ * 响应内容片段(用于调试)
60
+ */
61
+ responseSnippet;
62
+ constructor(message, cause, httpStatus, responseSnippet) {
54
63
  super(message, "NETWORK_ERROR");
55
64
  this.name = "NetworkError";
56
65
  this.cause = cause;
66
+ this.httpStatus = httpStatus;
67
+ this.responseSnippet = responseSnippet;
57
68
  }
58
69
  };
59
70
  exports.TimeoutError = class extends exports.MeetBotError {
60
- constructor(message) {
71
+ /**
72
+ * 是否为本地客户端超时(AbortController 触发)
73
+ * true: 本地 HTTP 请求超时
74
+ * false: 服务端/网关返回的超时(如 504 Gateway Timeout)
75
+ */
76
+ isLocal;
77
+ constructor(message, isLocal = true) {
61
78
  super(message, "TIMEOUT_ERROR");
62
79
  this.name = "TimeoutError";
80
+ this.isLocal = isLocal;
63
81
  }
64
82
  };
65
83
  exports.ValidationError = class extends exports.MeetBotError {
@@ -128,6 +146,11 @@ var CHUNK_RULES = [
128
146
  { maxSize: Infinity, chunks: 50 }
129
147
  // > 100MB: 50 片
130
148
  ];
149
+ var SESSION_TYPE = {
150
+ PRIVATE: 1,
151
+ GROUP: 3,
152
+ CHANNEL: 4
153
+ };
131
154
  function getChunkNum(size) {
132
155
  for (const rule of CHUNK_RULES) {
133
156
  if (size < rule.maxSize) {
@@ -136,6 +159,31 @@ function getChunkNum(size) {
136
159
  }
137
160
  return 50;
138
161
  }
162
+ function getConvID(firstID, secondID, sessionType, companyID = 1) {
163
+ let convID;
164
+ if (sessionType === SESSION_TYPE.PRIVATE) {
165
+ const min = Math.min(firstID, secondID);
166
+ const max = Math.max(firstID, secondID);
167
+ convID = `${min}:${max}`;
168
+ if (companyID > 1) {
169
+ convID += `:${companyID}`;
170
+ }
171
+ } else if (sessionType === SESSION_TYPE.GROUP) {
172
+ convID = `${firstID}+${secondID}`;
173
+ if (companyID > 1) {
174
+ convID += `+${companyID}`;
175
+ }
176
+ } else {
177
+ convID = `${firstID}_${secondID}`;
178
+ if (companyID > 1) {
179
+ convID += `_${companyID}`;
180
+ }
181
+ }
182
+ return convID;
183
+ }
184
+ function getQuoteMsgKey(convID, seqID) {
185
+ return `${convID}:${seqID}`;
186
+ }
139
187
 
140
188
  // src/utils/logger.ts
141
189
  var Logger = class {
@@ -172,8 +220,8 @@ function extractBotToken(token) {
172
220
  const parts = token.split(":");
173
221
  return parts.length >= 2 ? parts.slice(1).join(":") : token;
174
222
  }
175
- function buildUrl(baseUrl, path, params) {
176
- const url = new URL(`/im/bot/${path}`, baseUrl);
223
+ function buildUrl(baseUrl, path, pathPrefix, params) {
224
+ const url = new URL(`${pathPrefix}${path}`, baseUrl);
177
225
  if (params) {
178
226
  for (const [key, value] of Object.entries(params)) {
179
227
  if (value !== void 0) {
@@ -184,9 +232,19 @@ function buildUrl(baseUrl, path, params) {
184
232
  return url.toString();
185
233
  }
186
234
  async function request(options) {
187
- const { token, baseUrl, method = "GET", path, params, body, timeout = HTTP.DEFAULT_TIMEOUT } = options;
235
+ const {
236
+ token,
237
+ baseUrl,
238
+ method = "GET",
239
+ path,
240
+ pathPrefix = "/im/bot/",
241
+ params,
242
+ body,
243
+ timeout = HTTP.DEFAULT_TIMEOUT,
244
+ raw = false
245
+ } = options;
188
246
  validateToken(token);
189
- const url = buildUrl(baseUrl || DEFAULT_BASE_URL, path, method === "GET" ? params : void 0);
247
+ const url = buildUrl(baseUrl || DEFAULT_BASE_URL, path, pathPrefix, method === "GET" ? params : void 0);
190
248
  const controller = new AbortController();
191
249
  const timeoutId = setTimeout(() => controller.abort(), timeout);
192
250
  const headers = {
@@ -215,18 +273,40 @@ async function request(options) {
215
273
  const responseText = await response.text();
216
274
  if (!contentType.includes("application/json")) {
217
275
  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);
276
+ if (response.status === 504) {
277
+ throw new exports.TimeoutError(`Gateway timeout (status ${response.status})`, false);
278
+ }
279
+ throw new exports.NetworkError(
280
+ `Server returned non-JSON response (status ${response.status})`,
281
+ void 0,
282
+ response.status,
283
+ responseText.substring(0, 200)
284
+ );
219
285
  }
220
286
  let data;
221
287
  try {
222
288
  data = JSON.parse(responseText);
223
289
  } catch {
224
290
  logger.error("[Response] Failed to parse JSON:", responseText.substring(0, 500));
225
- throw new exports.NetworkError("Failed to parse server response", void 0);
291
+ throw new exports.NetworkError(
292
+ "Failed to parse server response",
293
+ void 0,
294
+ response.status,
295
+ responseText.substring(0, 200)
296
+ );
226
297
  }
227
298
  logger.info("[Response Body]", JSON.stringify(data));
299
+ if (raw) {
300
+ return data;
301
+ }
228
302
  if (!response.ok || !data.ok) {
229
303
  const errorData = data;
304
+ if (response.status === 504) {
305
+ throw new exports.TimeoutError(
306
+ errorData.description || `Gateway timeout (status ${response.status})`,
307
+ false
308
+ );
309
+ }
230
310
  throw new exports.ApiError(
231
311
  errorData.description || `HTTP ${response.status}`,
232
312
  response.status,
@@ -242,7 +322,7 @@ async function request(options) {
242
322
  }
243
323
  if (error instanceof Error) {
244
324
  if (error.name === "AbortError") {
245
- throw new exports.TimeoutError(`Request timeout after ${timeout}ms`);
325
+ throw new exports.TimeoutError(`Request timeout after ${timeout}ms`, true);
246
326
  }
247
327
  throw new exports.NetworkError(`Network error: ${error.message}`, error);
248
328
  }
@@ -337,9 +417,8 @@ async function getAccessURL(params) {
337
417
  sessionType: queryParams.sessionType,
338
418
  seqId: queryParams.seqId,
339
419
  fileId: queryParams.fileId,
420
+ companyId: queryParams.companyId,
340
421
  "x-oss-process": queryParams["x-oss-process"],
341
- origin: queryParams.origin,
342
- bestDomain: queryParams.bestDomain,
343
422
  printResult: queryParams.printResult ?? "1"
344
423
  }
345
424
  });
@@ -518,6 +597,25 @@ async function sendMediaMessage(token, sessionInfo, options, baseUrl) {
518
597
  });
519
598
  }
520
599
 
600
+ // src/api/user.ts
601
+ async function getUsers(params) {
602
+ const { token, baseUrl } = params;
603
+ const result = await request({
604
+ token,
605
+ baseUrl,
606
+ method: "POST",
607
+ path: "general/SyncCompanyUser",
608
+ pathPrefix: "/",
609
+ body: { syncAt: 0 },
610
+ timeout: HTTP.DEFAULT_TIMEOUT,
611
+ raw: true
612
+ });
613
+ return Object.values(result.users).map((user) => ({
614
+ userID: user.userID,
615
+ nickName: user.nickName
616
+ }));
617
+ }
618
+
521
619
  // src/api/index.ts
522
620
  async function getUpdates(params) {
523
621
  const { token, baseUrl, timeout = API.DEFAULT_TIMEOUT, offset, limit = API.DEFAULT_LIMIT } = params;
@@ -534,6 +632,20 @@ async function getUpdates(params) {
534
632
  timeout: (timeout || 0) * 1e3 + HTTP.POLLING_TIMEOUT_BUFFER
535
633
  });
536
634
  }
635
+ async function getUpdatesV2(params) {
636
+ const { token, baseUrl, timeout = API.DEFAULT_TIMEOUT, limit = API.DEFAULT_LIMIT } = params;
637
+ return request({
638
+ token,
639
+ baseUrl,
640
+ method: "POST",
641
+ path: "getUpdatesV2",
642
+ body: {
643
+ timeout,
644
+ limit
645
+ },
646
+ timeout: (timeout || 0) * 1e3 + HTTP.POLLING_TIMEOUT_BUFFER
647
+ });
648
+ }
537
649
  async function sendMessage(params) {
538
650
  const { token, baseUrl, sessionInfo, msgContent } = params;
539
651
  const hasContent = msgContent.content && msgContent.content.trim() !== "";
@@ -556,15 +668,126 @@ async function sendMessage(params) {
556
668
 
557
669
  // src/client.ts
558
670
  init_error();
671
+
672
+ // src/user-cache.ts
673
+ var UserCache = class {
674
+ byId = /* @__PURE__ */ new Map();
675
+ byNickName = /* @__PURE__ */ new Map();
676
+ byAliasName = /* @__PURE__ */ new Map();
677
+ loadedAt = 0;
678
+ ttl;
679
+ loading = null;
680
+ constructor(options) {
681
+ this.ttl = options?.ttl ?? 60 * 60 * 1e3;
682
+ }
683
+ isExpired() {
684
+ return this.byId.size === 0 || this.ttl > 0 && Date.now() - this.loadedAt > this.ttl;
685
+ }
686
+ async load(fetchAll) {
687
+ if (this.loading) {
688
+ return this.loading;
689
+ }
690
+ this.loading = (async () => {
691
+ try {
692
+ const users = await fetchAll();
693
+ this.rebuild(users);
694
+ logger.info(`\u7528\u6237\u7F13\u5B58\u5DF2\u52A0\u8F7D\uFF0C\u5171 ${users.length} \u4E2A\u7528\u6237`);
695
+ } finally {
696
+ this.loading = null;
697
+ }
698
+ })();
699
+ return this.loading;
700
+ }
701
+ rebuild(users) {
702
+ this.byId.clear();
703
+ this.byNickName.clear();
704
+ this.byAliasName.clear();
705
+ for (const user of users) {
706
+ this.byId.set(user.userID, user);
707
+ const nickKey = user.nickName.toLowerCase();
708
+ let nickList = this.byNickName.get(nickKey);
709
+ if (!nickList) {
710
+ nickList = [];
711
+ this.byNickName.set(nickKey, nickList);
712
+ }
713
+ nickList.push(user);
714
+ if (user.aliasName) {
715
+ const aliasKey = user.aliasName.toLowerCase();
716
+ let aliasList = this.byAliasName.get(aliasKey);
717
+ if (!aliasList) {
718
+ aliasList = [];
719
+ this.byAliasName.set(aliasKey, aliasList);
720
+ }
721
+ aliasList.push(user);
722
+ }
723
+ }
724
+ this.loadedAt = Date.now();
725
+ }
726
+ async ensureLoaded(fetchAll) {
727
+ if (!this.isExpired()) return;
728
+ await this.load(fetchAll);
729
+ }
730
+ getById(userId) {
731
+ return this.byId.get(userId);
732
+ }
733
+ getByIds(userIds) {
734
+ const result = /* @__PURE__ */ new Map();
735
+ for (const id of userIds) {
736
+ const user = this.byId.get(id);
737
+ if (user) {
738
+ result.set(id, user);
739
+ }
740
+ }
741
+ return result;
742
+ }
743
+ getByName(name) {
744
+ const key = name.toLowerCase();
745
+ const byNick = this.byNickName.get(key);
746
+ const byAlias = this.byAliasName.get(key);
747
+ if (!byNick && !byAlias) return [];
748
+ if (!byNick) return [...byAlias];
749
+ if (!byAlias) return [...byNick];
750
+ const seen = /* @__PURE__ */ new Set();
751
+ const result = [];
752
+ for (const u of [...byNick, ...byAlias]) {
753
+ if (!seen.has(u.userID)) {
754
+ seen.add(u.userID);
755
+ result.push(u);
756
+ }
757
+ }
758
+ return result;
759
+ }
760
+ searchByName(query) {
761
+ const q = query.toLowerCase();
762
+ const result = [];
763
+ const seen = /* @__PURE__ */ new Set();
764
+ for (const user of this.byId.values()) {
765
+ if (user.nickName.toLowerCase().includes(q) || user.aliasName && user.aliasName.toLowerCase().includes(q) || user.nickNamePinyin && user.nickNamePinyin.toLowerCase().includes(q) || user.aliasNamePinyin && user.aliasNamePinyin.toLowerCase().includes(q)) {
766
+ if (!seen.has(user.userID)) {
767
+ seen.add(user.userID);
768
+ result.push(user);
769
+ }
770
+ }
771
+ }
772
+ return result;
773
+ }
774
+ get size() {
775
+ return this.byId.size;
776
+ }
777
+ };
778
+
779
+ // src/client.ts
559
780
  var MeetBot = class {
560
781
  token;
561
782
  baseUrl;
562
783
  pollingLimit;
563
784
  longPollingTimeout;
785
+ useV2;
564
786
  eventHandlers = /* @__PURE__ */ new Map();
565
787
  polling = false;
566
788
  offset = 0;
567
789
  abortController = null;
790
+ userCache;
568
791
  constructor(config) {
569
792
  if (config.token) {
570
793
  if (!config.token.includes(":")) {
@@ -579,6 +802,8 @@ var MeetBot = class {
579
802
  this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
580
803
  this.pollingLimit = config.pollingLimit ?? POLLING.DEFAULT_LIMIT;
581
804
  this.longPollingTimeout = config.longPollingTimeout ?? POLLING.DEFAULT_TIMEOUT;
805
+ this.useV2 = config.useV2 ?? false;
806
+ this.userCache = new UserCache(config.userCacheOptions);
582
807
  logger.setLevel(config.logLevel ?? "silent");
583
808
  }
584
809
  on(event, handler) {
@@ -618,7 +843,7 @@ var MeetBot = class {
618
843
  this.abortController = new AbortController();
619
844
  const limit = options?.limit ?? this.pollingLimit;
620
845
  const timeout = options?.timeout ?? this.longPollingTimeout;
621
- logger.info(`\u5F00\u59CB\u957F\u8F6E\u8BE2\u6D88\u606F... (\u6761\u6570: ${limit}, \u8D85\u65F6: ${timeout}s)`);
846
+ logger.info(`\u5F00\u59CB\u957F\u8F6E\u8BE2\u6D88\u606F... (\u6761\u6570: ${limit}, \u8D85\u65F6: ${timeout}s, V2: ${this.useV2})`);
622
847
  this.emit("polling_start", void 0);
623
848
  const retryDelay = options?.retryDelay ?? POLLING.DEFAULT_RETRY_DELAY;
624
849
  const maxRetries = options?.maxRetries ?? POLLING.DEFAULT_MAX_RETRIES;
@@ -626,20 +851,35 @@ var MeetBot = class {
626
851
  let retryCount = 0;
627
852
  while (this.polling) {
628
853
  try {
629
- const updates = await getUpdates({
630
- token: this.token,
631
- baseUrl: this.baseUrl,
632
- timeout,
633
- offset: this.offset,
634
- limit
635
- });
636
- retryCount = 0;
637
- for (const update of updates) {
638
- if (update.message) {
639
- this.emit("message", update.message);
640
- this.offset = (update.message.seqId || 0) + 1;
854
+ if (this.useV2) {
855
+ const result = await getUpdatesV2({
856
+ token: this.token,
857
+ baseUrl: this.baseUrl,
858
+ timeout,
859
+ limit
860
+ });
861
+ retryCount = 0;
862
+ for (const msgUpdate of result.msgs) {
863
+ this.emit("message", { message: msgUpdate.message, quoteMsgMap: result.quoteMsgMap });
864
+ this.offset = (msgUpdate.message.seqId || 0) + 1;
641
865
  onOffsetUpdate?.(this.offset);
642
866
  }
867
+ } else {
868
+ const updates = await getUpdates({
869
+ token: this.token,
870
+ baseUrl: this.baseUrl,
871
+ timeout,
872
+ offset: this.offset,
873
+ limit
874
+ });
875
+ retryCount = 0;
876
+ for (const update of updates) {
877
+ if (update.message) {
878
+ this.emit("message", { message: update.message, quoteMsgMap: {} });
879
+ this.offset = (update.message.seqId || 0) + 1;
880
+ onOffsetUpdate?.(this.offset);
881
+ }
882
+ }
643
883
  }
644
884
  await this.sleep(POLLING.SUCCESS_DELAY);
645
885
  } catch (error) {
@@ -670,6 +910,13 @@ var MeetBot = class {
670
910
  ...options
671
911
  });
672
912
  }
913
+ async getUpdatesV2(options) {
914
+ return getUpdatesV2({
915
+ token: this.token,
916
+ baseUrl: this.baseUrl,
917
+ ...options
918
+ });
919
+ }
673
920
  async sendMessage(sessionInfo, msgContent) {
674
921
  return sendMessage({
675
922
  token: this.token,
@@ -712,10 +959,18 @@ var MeetBot = class {
712
959
  * 获取文件下载地址
713
960
  */
714
961
  async getAccessURL(params) {
962
+ const apiParams = "sessionInfo" in params ? {
963
+ firstId: params.sessionInfo.firstID,
964
+ secondId: params.sessionInfo.secondID,
965
+ sessionType: params.sessionInfo.sessionType,
966
+ seqId: params.seqId,
967
+ fileId: params.fileId,
968
+ printResult: params.printResult
969
+ } : params;
715
970
  return getAccessURL({
716
971
  token: this.token,
717
972
  baseUrl: this.baseUrl,
718
- ...params
973
+ ...apiParams
719
974
  });
720
975
  }
721
976
  /**
@@ -730,6 +985,56 @@ var MeetBot = class {
730
985
  async sendMedia(sessionInfo, options) {
731
986
  return sendMediaMessage(this.token, sessionInfo, options, this.baseUrl);
732
987
  }
988
+ /**
989
+ * 刷新用户缓存
990
+ */
991
+ async refreshUserCache() {
992
+ await this.userCache.load(async () => {
993
+ return getUsers({ token: this.token, baseUrl: this.baseUrl });
994
+ });
995
+ }
996
+ /**
997
+ * 根据用户 ID 获取用户信息
998
+ */
999
+ async getUserById(userId) {
1000
+ await this.userCache.ensureLoaded(async () => {
1001
+ return getUsers({ token: this.token, baseUrl: this.baseUrl });
1002
+ });
1003
+ return this.userCache.getById(userId);
1004
+ }
1005
+ /**
1006
+ * 批量根据用户 ID 获取用户信息
1007
+ */
1008
+ async getUserByIds(userIds) {
1009
+ await this.userCache.ensureLoaded(async () => {
1010
+ return getUsers({ token: this.token, baseUrl: this.baseUrl });
1011
+ });
1012
+ return this.userCache.getByIds(userIds);
1013
+ }
1014
+ /**
1015
+ * 根据昵称或别名精确查找用户
1016
+ */
1017
+ async getUserByName(name) {
1018
+ await this.userCache.ensureLoaded(async () => {
1019
+ return getUsers({ token: this.token, baseUrl: this.baseUrl });
1020
+ });
1021
+ return this.userCache.getByName(name);
1022
+ }
1023
+ /**
1024
+ * 根据昵称或别名模糊搜索用户
1025
+ */
1026
+ async searchUserByName(query) {
1027
+ await this.userCache.ensureLoaded(async () => {
1028
+ return getUsers({ token: this.token, baseUrl: this.baseUrl });
1029
+ });
1030
+ return this.userCache.searchByName(query);
1031
+ }
1032
+ /**
1033
+ * 获取缓存用户数
1034
+ */
1035
+ get userCacheSize() {
1036
+ return this.userCache.size;
1037
+ }
733
1038
  sleep(ms) {
734
1039
  return new Promise((resolve) => setTimeout(resolve, ms));
735
1040
  }
@@ -744,16 +1049,20 @@ exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
744
1049
  exports.HTTP = HTTP;
745
1050
  exports.MeetBot = MeetBot;
746
1051
  exports.POLLING = POLLING;
1052
+ exports.SESSION_TYPE = SESSION_TYPE;
747
1053
  exports.UPLOAD = UPLOAD;
1054
+ exports.UserCache = UserCache;
748
1055
  exports.completeMultipartUpload = completeMultipartUpload;
749
1056
  exports.computeMD5 = computeMD5;
750
1057
  exports.getAccessURL = getAccessURL;
751
1058
  exports.getChunkNum = getChunkNum;
1059
+ exports.getConvID = getConvID;
752
1060
  exports.getMultiPartUploadURL = getMultiPartUploadURL;
1061
+ exports.getQuoteMsgKey = getQuoteMsgKey;
753
1062
  exports.getUpdates = getUpdates;
1063
+ exports.getUpdatesV2 = getUpdatesV2;
754
1064
  exports.getUploadURL = getUploadURL;
1065
+ exports.getUsers = getUsers;
755
1066
  exports.sendMediaMessage = sendMediaMessage;
756
1067
  exports.sendMessage = sendMessage;
757
1068
  exports.uploadFile = uploadFile;
758
- //# sourceMappingURL=index.cjs.map
759
- //# sourceMappingURL=index.cjs.map