npm package: @meet-im/meet-bot-jssdk
Description: MeetIM Chatbot JavaScript SDK - 支持 Long Polling 消息获取和消息发送
Latest version: 1.0.0

# @meet-im/meet-bot-jssdk

MeetIM Chatbot JavaScript SDK - 支持 Long Polling 消息获取、消息发送和媒体文件处理

## 安装

```bash
pnpm add @meet-im/meet-bot-jssdk
```

## 快速开始

### 类实例风格

```typescript
import { MeetBot } from '@meet-im/meet-bot-jssdk'

const bot = new MeetBot({
  token: 'bot_id:secret',
  useV2: true, // 启用 getUpdatesV2，支持引用消息
})

bot.on('message', ({ message, quoteMsgMap }) => {
  console.log('收到消息:', message.content)

  // 处理引用消息
  if (message.quoteSeqID && message.sessionInfo) {
    const { getConvID, getQuoteMsgKey } = await import('@meet-im/meet-bot-jssdk')
    const convID = getConvID(
      message.sessionInfo.firstID,
      message.sessionInfo.secondID,
      message.sessionInfo.sessionType,
      message.sessionInfo.companyID
    )
    const quoteKey = getQuoteMsgKey(convID, message.quoteSeqID)
    const quoteMsg = quoteMsgMap[quoteKey]
    if (quoteMsg) {
      console.log('引用消息:', quoteMsg.content)
    }
  }

  if (message.sessionInfo) {
    bot.sendMessage(message.sessionInfo, { content: `收到: ${message.content}` })
  }
})

bot.startPolling()
```

### 函数式风格

```typescript
import { getUpdatesV2, sendMessage } from '@meet-im/meet-bot-jssdk'

const token = 'bot_id:secret'
const baseUrl = 'https://staging-meet-api.miyachat.com'

const { msgs, quoteMsgMap } = await getUpdatesV2({ token, baseUrl })

for (const { message } of msgs) {
  if (message.sessionInfo) {
    console.log('收到消息:', message.content)

    await sendMessage({
      token,
      baseUrl,
      sessionInfo: message.sessionInfo,
      msgContent: { content: '收到！' },
    })
  }
}
```

## API 文档

### MeetBot 类

#### 构造函数

```typescript
new MeetBot(config: MeetBotConfig)
```

| 参数               | 类型               | 必填 | 说明                                                   |
| ------------------ | ------------------ | ---- | ------------------------------------------------------ |
| token              | string             | 是\* | Bot Token，格式：`bot_id:secret`                       |
| botId              | string \| number   | 是\* | Bot ID（与 botToken 配合使用）                         |
| botToken           | string             | 是\* | Bot Token（与 botId 配合使用）                         |
| baseUrl            | string             | 否   | API 地址，默认 `https://staging-meet-api.miyachat.com` |
| pollingLimit       | number             | 否   | 每次拉取消息条数，默认 100                             |
| longPollingTimeout | number             | 否   | 长轮询超时时间（秒），默认 30                          |
| useV2              | boolean            | 否   | 使用 getUpdatesV2 接口，默认 false                     |
| logLevel           | 'silent' \| 'info' | 否   | 日志级别，默认 'silent'                                |

\*token 或 (botId + botToken) 二选一

#### 方法

| 方法                                   | 说明               |
| -------------------------------------- | ------------------ |
| `on(event, handler)`                   | 监听事件           |
| `off(event, handler)`                  | 移除事件监听       |
| `startPolling(options?)`               | 启动长轮询         |
| `stopPolling()`                        | 停止轮询           |
| `isPolling()`                          | 获取轮询状态       |
| `getUpdates(options?)`                 | 手动获取消息（V1） |
| `getUpdatesV2(options?)`               | 手动获取消息（V2） |
| `sendMessage(sessionInfo, msgContent)` | 发送消息           |
| `getUploadURL(params)`                 | 获取单文件上传地址 |
| `getMultiPartUploadURL(params)`        | 获取分片上传地址   |
| `completeMultipartUpload(params)`      | 完成分片上传       |
| `getAccessURL(params)`                 | 获取文件下载地址   |
| `uploadFile(buffer, options)`          | 上传文件           |
| `sendMedia(sessionInfo, options)`      | 发送媒体消息       |

#### startPolling options

| 参数           | 类型                     | 默认值 | 说明                           |
| -------------- | ------------------------ | ------ | ------------------------------ |
| timeout        | number                   | 30     | 长轮询超时时间（秒）           |
| limit          | number                   | 100    | 每次拉取消息条数               |
| retryDelay     | number                   | 1000   | 重试延迟基础时间（毫秒）       |
| maxRetries     | number                   | 0      | 最大重试次数（0 表示无限重试） |
| onOffsetUpdate | (offset: number) => void | -      | offset 更新回调                |

#### 事件

| 事件            | 参数                                   | 说明       |
| --------------- | -------------------------------------- | ---------- |
| `message`       | `{ message: MsgContent, quoteMsgMap }` | 收到新消息 |
| `error`         | `Error`                                | 发生错误   |
| `polling_start` | `void`                                 | 轮询开始   |
| `polling_stop`  | `void`                                 | 轮询停止   |

### 函数式 API

#### getUpdates（已废弃）

```typescript
getUpdates(params: GetUpdatesParams): Promise<BotUpdate[]>
```

| 参数    | 类型   | 必填 | 默认值 | 说明                    |
| ------- | ------ | ---- | ------ | ----------------------- |
| token   | string | 是   | -      | Bot Token               |
| baseUrl | string | 否   | -      | API 地址                |
| timeout | number | 否   | 0      | Long Polling 超时（秒） |
| offset  | number | 否   | 0      | 从该 seqId 之后获取     |
| limit   | number | 否   | 100    | 最大返回条数            |

#### getUpdatesV2（推荐）

```typescript
getUpdatesV2(params: GetUpdatesV2Params): Promise<GetUpdatesV2Result>
```

| 参数    | 类型   | 必填 | 默认值 | 说明                    |
| ------- | ------ | ---- | ------ | ----------------------- |
| token   | string | 是   | -      | Bot Token               |
| baseUrl | string | 否   | -      | API 地址                |
| timeout | number | 否   | 0      | Long Polling 超时（秒） |
| limit   | number | 否   | 100    | 最大返回条数            |

返回值：

```typescript
interface GetUpdatesV2Result {
  msgs: { message: MsgContent }[]
  quoteMsgMap: Record<string, MsgContent> // key: "convID:seqID"
}
```

#### sendMessage

```typescript
sendMessage(params: SendMessageParams): Promise<SendMessageResult>
```

| 参数        | 类型        | 必填 | 说明      |
| ----------- | ----------- | ---- | --------- |
| token       | string      | 是   | Bot Token |
| baseUrl     | string      | 否   | API 地址  |
| sessionInfo | SessionInfo | 是   | 会话信息  |
| msgContent  | MsgContent  | 是   | 消息内容  |

### 媒体文件 API

#### getUploadURL

获取单文件上传签名地址。

```typescript
getUploadURL(params): Promise<UploadURLResult>
```

| 参数           | 类型   | 必填 | 说明             |
| -------------- | ------ | ---- | ---------------- |
| token          | string | 是   | Bot Token        |
| originFileName | string | 是   | 原始文件名       |
| contentType    | string | 是   | MIME 类型        |
| md5            | string | 是   | 格式：`md5_hash` |
| size           | number | 是   | 文件大小（字节） |

#### getAccessURL

获取文件下载地址。

```typescript
// 方式1：完整参数
getAccessURL({ token, firstId, secondId, sessionType, seqId, fileId })

// 方式2：使用 sessionInfo
getAccessURL({ token, sessionInfo, seqId, fileId })
```

#### uploadFile

上传文件（自动选择单文件或分片上传）。

```typescript
uploadFile(token, buffer, { fileName, contentType, onProgress? })
```

#### sendMedia

发送媒体消息（上传并发送）。

```typescript
sendMedia(token, sessionInfo, { buffer, fileName, contentType, content?, onProgress? })
```

### 工具函数

#### getConvID

生成会话 ID。

```typescript
import { getConvID } from '@meet-im/meet-bot-jssdk'

const convID = getConvID(firstID, secondID, sessionType, companyID?)
```

| 会话类型 | convID 格式                            |
| -------- | -------------------------------------- |
| 私聊 (1) | `min:max` 或 `min:max:companyID`       |
| 群聊 (3) | `firstID+secondID` 或追加 `+companyID` |
| 频道     | `firstID_secondID` 或追加 `_companyID` |

#### getQuoteMsgKey

生成引用消息的 key。

```typescript
import { getQuoteMsgKey } from '@meet-im/meet-bot-jssdk'

const key = getQuoteMsgKey(convID, seqID) // "convID:seqID"
```

## 类型定义

```typescript
type SessionType = 1 | 3 // 1: 私聊, 3: 群聊

interface SessionInfo {
  firstID: number
  secondID: number
  sessionType: SessionType
  companyID?: number
}

type MsgType = 'NORMAL' | 'RECALL' | 'QUOTE'

interface AttachmentInfo {
  fileID: string | number
  fileName?: string
  filePath?: string
  fileSize?: number
  mimeType?: string
  fileUrl?: string
}

interface ExtraInfo {
  msgType?: MsgType
  attechmentInfo?: AttachmentInfo // 单附件
  attechmentInfos?: AttachmentInfo[] // 多附件
  [key: string]: unknown
}

interface MsgContent {
  content: string
  seqId?: number
  timestamp?: number
  fromUid?: number
  atIds?: number[]
  quoteSeqID?: number // 引用消息 seqID
  extraInfo?: ExtraInfo
  sessionInfo?: SessionInfo
}

interface BotUpdate {
  message?: MsgContent
}

interface GetUpdatesV2Result {
  msgs: { message: MsgContent }[]
  quoteMsgMap: Record<string, MsgContent>
}

interface SendMessageResult {
  msgContent: MsgContent
  quoteMsg: unknown
  userProfileMap: Record<number, unknown>
}
```

## 错误处理

SDK 提供以下错误类型：

| 错误类            | 说明           |
| ----------------- | -------------- |
| `MeetBotError`    | 基础错误类     |
| `ApiError`        | API 返回的错误 |
| `NetworkError`    | 网络连接错误   |
| `TimeoutError`    | 请求超时错误   |
| `ValidationError` | 参数验证错误   |

```typescript
import { ApiError, NetworkError } from '@meet-im/meet-bot-jssdk'

try {
  await bot.sendMessage(sessionInfo, { content: 'Hello' })
} catch (error) {
  if (error instanceof ApiError) {
    console.error('API 错误:', error.code, error.statusCode)
  } else if (error instanceof NetworkError) {
    console.error('网络错误:', error.message)
  }
}
```

## 开发

```bash
# 安装依赖
pnpm install

# 构建
pnpm build

# 测试
pnpm test

# 类型检查
pnpm typecheck

# 代码格式化
pnpm format

# 代码检查
pnpm lint
```

## License

MIT