@meet-im/meet-bot-jssdk 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 +0 -366
- package/dist/index.cjs +241 -11
- package/dist/index.d.cts +74 -4
- package/dist/index.d.ts +74 -4
- package/dist/index.js +240 -12
- package/package.json +1 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,367 +1 @@
|
|
|
1
1
|
# @meet-im/meet-bot-jssdk
|
|
2
|
-
|
|
3
|
-
MeetIM Chatbot JavaScript SDK - 支持 Long Polling 消息获取、消息发送和媒体文件处理
|
|
4
|
-
|
|
5
|
-
## 安装
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @meet-im/meet-bot-jssdk
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## 快速开始
|
|
12
|
-
|
|
13
|
-
### 类实例风格
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import { MeetBot } from '@meet-im/meet-bot-jssdk'
|
|
17
|
-
|
|
18
|
-
const bot = new MeetBot({
|
|
19
|
-
token: 'bot_id:secret',
|
|
20
|
-
useV2: true, // 启用 getUpdatesV2,支持引用消息
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
bot.on('message', ({ message, quoteMsgMap }) => {
|
|
24
|
-
console.log('收到消息:', message.content)
|
|
25
|
-
|
|
26
|
-
// 处理引用消息
|
|
27
|
-
if (message.quoteSeqID && message.sessionInfo) {
|
|
28
|
-
const { getConvID, getQuoteMsgKey } = await import('@meet-im/meet-bot-jssdk')
|
|
29
|
-
const convID = getConvID(
|
|
30
|
-
message.sessionInfo.firstID,
|
|
31
|
-
message.sessionInfo.secondID,
|
|
32
|
-
message.sessionInfo.sessionType,
|
|
33
|
-
message.sessionInfo.companyID
|
|
34
|
-
)
|
|
35
|
-
const quoteKey = getQuoteMsgKey(convID, message.quoteSeqID)
|
|
36
|
-
const quoteMsg = quoteMsgMap[quoteKey]
|
|
37
|
-
if (quoteMsg) {
|
|
38
|
-
console.log('引用消息:', quoteMsg.content)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (message.sessionInfo) {
|
|
43
|
-
bot.sendMessage(message.sessionInfo, { content: `收到: ${message.content}` })
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
bot.startPolling()
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### 函数式风格
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import { getUpdatesV2, sendMessage } from '@meet-im/meet-bot-jssdk'
|
|
54
|
-
|
|
55
|
-
const token = 'bot_id:secret'
|
|
56
|
-
const baseUrl = 'https://staging-meet-api.miyachat.com'
|
|
57
|
-
|
|
58
|
-
const { msgs, quoteMsgMap } = await getUpdatesV2({ token, baseUrl })
|
|
59
|
-
|
|
60
|
-
for (const { message } of msgs) {
|
|
61
|
-
if (message.sessionInfo) {
|
|
62
|
-
console.log('收到消息:', message.content)
|
|
63
|
-
|
|
64
|
-
await sendMessage({
|
|
65
|
-
token,
|
|
66
|
-
baseUrl,
|
|
67
|
-
sessionInfo: message.sessionInfo,
|
|
68
|
-
msgContent: { content: '收到!' },
|
|
69
|
-
})
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## API 文档
|
|
75
|
-
|
|
76
|
-
### MeetBot 类
|
|
77
|
-
|
|
78
|
-
#### 构造函数
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
new MeetBot(config: MeetBotConfig)
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
85
|
-
| ------------------ | ------------------ | ---- | ------------------------------------------------------ |
|
|
86
|
-
| token | string | 是\* | Bot Token,格式:`bot_id:secret` |
|
|
87
|
-
| botId | string \| number | 是\* | Bot ID(与 botToken 配合使用) |
|
|
88
|
-
| botToken | string | 是\* | Bot Token(与 botId 配合使用) |
|
|
89
|
-
| baseUrl | string | 否 | API 地址,默认 `https://staging-meet-api.miyachat.com` |
|
|
90
|
-
| pollingLimit | number | 否 | 每次拉取消息条数,默认 100 |
|
|
91
|
-
| longPollingTimeout | number | 否 | 长轮询超时时间(秒),默认 30 |
|
|
92
|
-
| useV2 | boolean | 否 | 使用 getUpdatesV2 接口,默认 false |
|
|
93
|
-
| logLevel | 'silent' \| 'info' | 否 | 日志级别,默认 'silent' |
|
|
94
|
-
|
|
95
|
-
\*token 或 (botId + botToken) 二选一
|
|
96
|
-
|
|
97
|
-
#### 方法
|
|
98
|
-
|
|
99
|
-
| 方法 | 说明 |
|
|
100
|
-
| -------------------------------------- | ------------------ |
|
|
101
|
-
| `on(event, handler)` | 监听事件 |
|
|
102
|
-
| `off(event, handler)` | 移除事件监听 |
|
|
103
|
-
| `startPolling(options?)` | 启动长轮询 |
|
|
104
|
-
| `stopPolling()` | 停止轮询 |
|
|
105
|
-
| `isPolling()` | 获取轮询状态 |
|
|
106
|
-
| `getUpdates(options?)` | 手动获取消息(V1) |
|
|
107
|
-
| `getUpdatesV2(options?)` | 手动获取消息(V2) |
|
|
108
|
-
| `sendMessage(sessionInfo, msgContent)` | 发送消息 |
|
|
109
|
-
| `getUploadURL(params)` | 获取单文件上传地址 |
|
|
110
|
-
| `getMultiPartUploadURL(params)` | 获取分片上传地址 |
|
|
111
|
-
| `completeMultipartUpload(params)` | 完成分片上传 |
|
|
112
|
-
| `getAccessURL(params)` | 获取文件下载地址 |
|
|
113
|
-
| `uploadFile(buffer, options)` | 上传文件 |
|
|
114
|
-
| `sendMedia(sessionInfo, options)` | 发送媒体消息 |
|
|
115
|
-
|
|
116
|
-
#### startPolling options
|
|
117
|
-
|
|
118
|
-
| 参数 | 类型 | 默认值 | 说明 |
|
|
119
|
-
| -------------- | ------------------------ | ------ | ------------------------------ |
|
|
120
|
-
| timeout | number | 30 | 长轮询超时时间(秒) |
|
|
121
|
-
| limit | number | 100 | 每次拉取消息条数 |
|
|
122
|
-
| retryDelay | number | 1000 | 重试延迟基础时间(毫秒) |
|
|
123
|
-
| maxRetries | number | 0 | 最大重试次数(0 表示无限重试) |
|
|
124
|
-
| onOffsetUpdate | (offset: number) => void | - | offset 更新回调 |
|
|
125
|
-
|
|
126
|
-
#### 事件
|
|
127
|
-
|
|
128
|
-
| 事件 | 参数 | 说明 |
|
|
129
|
-
| --------------- | -------------------------------------- | ---------- |
|
|
130
|
-
| `message` | `{ message: MsgContent, quoteMsgMap }` | 收到新消息 |
|
|
131
|
-
| `error` | `Error` | 发生错误 |
|
|
132
|
-
| `polling_start` | `void` | 轮询开始 |
|
|
133
|
-
| `polling_stop` | `void` | 轮询停止 |
|
|
134
|
-
|
|
135
|
-
### 函数式 API
|
|
136
|
-
|
|
137
|
-
#### getUpdates(已废弃)
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
getUpdates(params: GetUpdatesParams): Promise<BotUpdate[]>
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
144
|
-
| ------- | ------ | ---- | ------ | ----------------------- |
|
|
145
|
-
| token | string | 是 | - | Bot Token |
|
|
146
|
-
| baseUrl | string | 否 | - | API 地址 |
|
|
147
|
-
| timeout | number | 否 | 0 | Long Polling 超时(秒) |
|
|
148
|
-
| offset | number | 否 | 0 | 从该 seqId 之后获取 |
|
|
149
|
-
| limit | number | 否 | 100 | 最大返回条数 |
|
|
150
|
-
|
|
151
|
-
#### getUpdatesV2(推荐)
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
getUpdatesV2(params: GetUpdatesV2Params): Promise<GetUpdatesV2Result>
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|
158
|
-
| ------- | ------ | ---- | ------ | ----------------------- |
|
|
159
|
-
| token | string | 是 | - | Bot Token |
|
|
160
|
-
| baseUrl | string | 否 | - | API 地址 |
|
|
161
|
-
| timeout | number | 否 | 0 | Long Polling 超时(秒) |
|
|
162
|
-
| limit | number | 否 | 100 | 最大返回条数 |
|
|
163
|
-
|
|
164
|
-
返回值:
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
interface GetUpdatesV2Result {
|
|
168
|
-
msgs: { message: MsgContent }[]
|
|
169
|
-
quoteMsgMap: Record<string, MsgContent> // key: "convID:seqID"
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
#### sendMessage
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
sendMessage(params: SendMessageParams): Promise<SendMessageResult>
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
180
|
-
| ----------- | ----------- | ---- | --------- |
|
|
181
|
-
| token | string | 是 | Bot Token |
|
|
182
|
-
| baseUrl | string | 否 | API 地址 |
|
|
183
|
-
| sessionInfo | SessionInfo | 是 | 会话信息 |
|
|
184
|
-
| msgContent | MsgContent | 是 | 消息内容 |
|
|
185
|
-
|
|
186
|
-
### 媒体文件 API
|
|
187
|
-
|
|
188
|
-
#### getUploadURL
|
|
189
|
-
|
|
190
|
-
获取单文件上传签名地址。
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
getUploadURL(params): Promise<UploadURLResult>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
197
|
-
| -------------- | ------ | ---- | ---------------- |
|
|
198
|
-
| token | string | 是 | Bot Token |
|
|
199
|
-
| originFileName | string | 是 | 原始文件名 |
|
|
200
|
-
| contentType | string | 是 | MIME 类型 |
|
|
201
|
-
| md5 | string | 是 | 格式:`md5_hash` |
|
|
202
|
-
| size | number | 是 | 文件大小(字节) |
|
|
203
|
-
|
|
204
|
-
#### getAccessURL
|
|
205
|
-
|
|
206
|
-
获取文件下载地址。
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
// 方式1:完整参数
|
|
210
|
-
getAccessURL({ token, firstId, secondId, sessionType, seqId, fileId })
|
|
211
|
-
|
|
212
|
-
// 方式2:使用 sessionInfo
|
|
213
|
-
getAccessURL({ token, sessionInfo, seqId, fileId })
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
#### uploadFile
|
|
217
|
-
|
|
218
|
-
上传文件(自动选择单文件或分片上传)。
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
uploadFile(token, buffer, { fileName, contentType, onProgress? })
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
#### sendMedia
|
|
225
|
-
|
|
226
|
-
发送媒体消息(上传并发送)。
|
|
227
|
-
|
|
228
|
-
```typescript
|
|
229
|
-
sendMedia(token, sessionInfo, { buffer, fileName, contentType, content?, onProgress? })
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### 工具函数
|
|
233
|
-
|
|
234
|
-
#### getConvID
|
|
235
|
-
|
|
236
|
-
生成会话 ID。
|
|
237
|
-
|
|
238
|
-
```typescript
|
|
239
|
-
import { getConvID } from '@meet-im/meet-bot-jssdk'
|
|
240
|
-
|
|
241
|
-
const convID = getConvID(firstID, secondID, sessionType, companyID?)
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
| 会话类型 | convID 格式 |
|
|
245
|
-
| -------- | -------------------------------------- |
|
|
246
|
-
| 私聊 (1) | `min:max` 或 `min:max:companyID` |
|
|
247
|
-
| 群聊 (3) | `firstID+secondID` 或追加 `+companyID` |
|
|
248
|
-
| 频道 | `firstID_secondID` 或追加 `_companyID` |
|
|
249
|
-
|
|
250
|
-
#### getQuoteMsgKey
|
|
251
|
-
|
|
252
|
-
生成引用消息的 key。
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
import { getQuoteMsgKey } from '@meet-im/meet-bot-jssdk'
|
|
256
|
-
|
|
257
|
-
const key = getQuoteMsgKey(convID, seqID) // "convID:seqID"
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
## 类型定义
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
type SessionType = 1 | 3 // 1: 私聊, 3: 群聊
|
|
264
|
-
|
|
265
|
-
interface SessionInfo {
|
|
266
|
-
firstID: number
|
|
267
|
-
secondID: number
|
|
268
|
-
sessionType: SessionType
|
|
269
|
-
companyID?: number
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
type MsgType = 'NORMAL' | 'RECALL' | 'QUOTE'
|
|
273
|
-
|
|
274
|
-
interface AttachmentInfo {
|
|
275
|
-
fileID: string | number
|
|
276
|
-
fileName?: string
|
|
277
|
-
filePath?: string
|
|
278
|
-
fileSize?: number
|
|
279
|
-
mimeType?: string
|
|
280
|
-
fileUrl?: string
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
interface ExtraInfo {
|
|
284
|
-
msgType?: MsgType
|
|
285
|
-
attechmentInfo?: AttachmentInfo // 单附件
|
|
286
|
-
attechmentInfos?: AttachmentInfo[] // 多附件
|
|
287
|
-
[key: string]: unknown
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
interface MsgContent {
|
|
291
|
-
content: string
|
|
292
|
-
seqId?: number
|
|
293
|
-
timestamp?: number
|
|
294
|
-
fromUid?: number
|
|
295
|
-
atIds?: number[]
|
|
296
|
-
quoteSeqID?: number // 引用消息 seqID
|
|
297
|
-
extraInfo?: ExtraInfo
|
|
298
|
-
sessionInfo?: SessionInfo
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
interface BotUpdate {
|
|
302
|
-
message?: MsgContent
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
interface GetUpdatesV2Result {
|
|
306
|
-
msgs: { message: MsgContent }[]
|
|
307
|
-
quoteMsgMap: Record<string, MsgContent>
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
interface SendMessageResult {
|
|
311
|
-
msgContent: MsgContent
|
|
312
|
-
quoteMsg: unknown
|
|
313
|
-
userProfileMap: Record<number, unknown>
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
## 错误处理
|
|
318
|
-
|
|
319
|
-
SDK 提供以下错误类型:
|
|
320
|
-
|
|
321
|
-
| 错误类 | 说明 |
|
|
322
|
-
| ----------------- | -------------- |
|
|
323
|
-
| `MeetBotError` | 基础错误类 |
|
|
324
|
-
| `ApiError` | API 返回的错误 |
|
|
325
|
-
| `NetworkError` | 网络连接错误 |
|
|
326
|
-
| `TimeoutError` | 请求超时错误 |
|
|
327
|
-
| `ValidationError` | 参数验证错误 |
|
|
328
|
-
|
|
329
|
-
```typescript
|
|
330
|
-
import { ApiError, NetworkError } from '@meet-im/meet-bot-jssdk'
|
|
331
|
-
|
|
332
|
-
try {
|
|
333
|
-
await bot.sendMessage(sessionInfo, { content: 'Hello' })
|
|
334
|
-
} catch (error) {
|
|
335
|
-
if (error instanceof ApiError) {
|
|
336
|
-
console.error('API 错误:', error.code, error.statusCode)
|
|
337
|
-
} else if (error instanceof NetworkError) {
|
|
338
|
-
console.error('网络错误:', error.message)
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## 开发
|
|
344
|
-
|
|
345
|
-
```bash
|
|
346
|
-
# 安装依赖
|
|
347
|
-
pnpm install
|
|
348
|
-
|
|
349
|
-
# 构建
|
|
350
|
-
pnpm build
|
|
351
|
-
|
|
352
|
-
# 测试
|
|
353
|
-
pnpm test
|
|
354
|
-
|
|
355
|
-
# 类型检查
|
|
356
|
-
pnpm typecheck
|
|
357
|
-
|
|
358
|
-
# 代码格式化
|
|
359
|
-
pnpm format
|
|
360
|
-
|
|
361
|
-
# 代码检查
|
|
362
|
-
pnpm lint
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
## License
|
|
366
|
-
|
|
367
|
-
MIT
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
@@ -202,8 +220,8 @@ function extractBotToken(token) {
|
|
|
202
220
|
const parts = token.split(":");
|
|
203
221
|
return parts.length >= 2 ? parts.slice(1).join(":") : token;
|
|
204
222
|
}
|
|
205
|
-
function buildUrl(baseUrl, path, params) {
|
|
206
|
-
const url = new URL(
|
|
223
|
+
function buildUrl(baseUrl, path, pathPrefix, params) {
|
|
224
|
+
const url = new URL(`${pathPrefix}${path}`, baseUrl);
|
|
207
225
|
if (params) {
|
|
208
226
|
for (const [key, value] of Object.entries(params)) {
|
|
209
227
|
if (value !== void 0) {
|
|
@@ -214,9 +232,19 @@ function buildUrl(baseUrl, path, params) {
|
|
|
214
232
|
return url.toString();
|
|
215
233
|
}
|
|
216
234
|
async function request(options) {
|
|
217
|
-
const {
|
|
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;
|
|
218
246
|
validateToken(token);
|
|
219
|
-
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);
|
|
220
248
|
const controller = new AbortController();
|
|
221
249
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
222
250
|
const headers = {
|
|
@@ -245,18 +273,40 @@ async function request(options) {
|
|
|
245
273
|
const responseText = await response.text();
|
|
246
274
|
if (!contentType.includes("application/json")) {
|
|
247
275
|
logger.error("[Response] Non-JSON response:", responseText.substring(0, 500));
|
|
248
|
-
|
|
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
|
+
);
|
|
249
285
|
}
|
|
250
286
|
let data;
|
|
251
287
|
try {
|
|
252
288
|
data = JSON.parse(responseText);
|
|
253
289
|
} catch {
|
|
254
290
|
logger.error("[Response] Failed to parse JSON:", responseText.substring(0, 500));
|
|
255
|
-
throw new exports.NetworkError(
|
|
291
|
+
throw new exports.NetworkError(
|
|
292
|
+
"Failed to parse server response",
|
|
293
|
+
void 0,
|
|
294
|
+
response.status,
|
|
295
|
+
responseText.substring(0, 200)
|
|
296
|
+
);
|
|
256
297
|
}
|
|
257
298
|
logger.info("[Response Body]", JSON.stringify(data));
|
|
299
|
+
if (raw) {
|
|
300
|
+
return data;
|
|
301
|
+
}
|
|
258
302
|
if (!response.ok || !data.ok) {
|
|
259
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
|
+
}
|
|
260
310
|
throw new exports.ApiError(
|
|
261
311
|
errorData.description || `HTTP ${response.status}`,
|
|
262
312
|
response.status,
|
|
@@ -272,7 +322,7 @@ async function request(options) {
|
|
|
272
322
|
}
|
|
273
323
|
if (error instanceof Error) {
|
|
274
324
|
if (error.name === "AbortError") {
|
|
275
|
-
throw new exports.TimeoutError(`Request timeout after ${timeout}ms
|
|
325
|
+
throw new exports.TimeoutError(`Request timeout after ${timeout}ms`, true);
|
|
276
326
|
}
|
|
277
327
|
throw new exports.NetworkError(`Network error: ${error.message}`, error);
|
|
278
328
|
}
|
|
@@ -547,6 +597,25 @@ async function sendMediaMessage(token, sessionInfo, options, baseUrl) {
|
|
|
547
597
|
});
|
|
548
598
|
}
|
|
549
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
|
+
|
|
550
619
|
// src/api/index.ts
|
|
551
620
|
async function getUpdates(params) {
|
|
552
621
|
const { token, baseUrl, timeout = API.DEFAULT_TIMEOUT, offset, limit = API.DEFAULT_LIMIT } = params;
|
|
@@ -599,6 +668,115 @@ async function sendMessage(params) {
|
|
|
599
668
|
|
|
600
669
|
// src/client.ts
|
|
601
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
|
|
602
780
|
var MeetBot = class {
|
|
603
781
|
token;
|
|
604
782
|
baseUrl;
|
|
@@ -609,6 +787,7 @@ var MeetBot = class {
|
|
|
609
787
|
polling = false;
|
|
610
788
|
offset = 0;
|
|
611
789
|
abortController = null;
|
|
790
|
+
userCache;
|
|
612
791
|
constructor(config) {
|
|
613
792
|
if (config.token) {
|
|
614
793
|
if (!config.token.includes(":")) {
|
|
@@ -624,6 +803,7 @@ var MeetBot = class {
|
|
|
624
803
|
this.pollingLimit = config.pollingLimit ?? POLLING.DEFAULT_LIMIT;
|
|
625
804
|
this.longPollingTimeout = config.longPollingTimeout ?? POLLING.DEFAULT_TIMEOUT;
|
|
626
805
|
this.useV2 = config.useV2 ?? false;
|
|
806
|
+
this.userCache = new UserCache(config.userCacheOptions);
|
|
627
807
|
logger.setLevel(config.logLevel ?? "silent");
|
|
628
808
|
}
|
|
629
809
|
on(event, handler) {
|
|
@@ -805,6 +985,56 @@ var MeetBot = class {
|
|
|
805
985
|
async sendMedia(sessionInfo, options) {
|
|
806
986
|
return sendMediaMessage(this.token, sessionInfo, options, this.baseUrl);
|
|
807
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
|
+
}
|
|
808
1038
|
sleep(ms) {
|
|
809
1039
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
810
1040
|
}
|
|
@@ -821,6 +1051,7 @@ exports.MeetBot = MeetBot;
|
|
|
821
1051
|
exports.POLLING = POLLING;
|
|
822
1052
|
exports.SESSION_TYPE = SESSION_TYPE;
|
|
823
1053
|
exports.UPLOAD = UPLOAD;
|
|
1054
|
+
exports.UserCache = UserCache;
|
|
824
1055
|
exports.completeMultipartUpload = completeMultipartUpload;
|
|
825
1056
|
exports.computeMD5 = computeMD5;
|
|
826
1057
|
exports.getAccessURL = getAccessURL;
|
|
@@ -831,8 +1062,7 @@ exports.getQuoteMsgKey = getQuoteMsgKey;
|
|
|
831
1062
|
exports.getUpdates = getUpdates;
|
|
832
1063
|
exports.getUpdatesV2 = getUpdatesV2;
|
|
833
1064
|
exports.getUploadURL = getUploadURL;
|
|
1065
|
+
exports.getUsers = getUsers;
|
|
834
1066
|
exports.sendMediaMessage = sendMediaMessage;
|
|
835
1067
|
exports.sendMessage = sendMessage;
|
|
836
1068
|
exports.uploadFile = uploadFile;
|
|
837
|
-
//# sourceMappingURL=index.cjs.map
|
|
838
|
-
//# sourceMappingURL=index.cjs.map
|