npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@starim-io/bot-sdk

v0.1.17

Published

Official Node.js SDK for StarIM Bot API and Webhooks

Readme

@starim-io/bot-sdk

npm version npm downloads license

StarIM 机器人开放平台的 官方 Node.js / TypeScript SDK,封装:

  • Bot APIAuthorization: Bearer <bot_token> 调用网关 /api/v1/bots/**
  • Webhook:校验 X-StarIM-Signature-V2(HMAC-SHA256,带 X-StarIM-Timestamp 防重放)、update_id 幂等、高阶事件路由

已发布到 npm:@starim-io/bot-sdk · npm install @starim-io/bot-sdk(版本号以 npm registry 为准)

要求

  • Node.js ≥ 18(依赖全局 fetch

安装

npm install @starim-io/bot-sdk
# 或
pnpm add @starim-io/bot-sdk

本仓库已将 botsdk/nodejs 加入根目录 pnpm workspace,在仓库根执行 pnpm install 即可安装依赖。单独构建/测试:

pnpm --filter @starim-io/bot-sdk run build
pnpm --filter @starim-io/bot-sdk run test

对外即可在任意项目中:

npm install @starim-io/bot-sdk
# 或
pnpm add @starim-io/bot-sdk

5 分钟快速上手

  1. 在 StarIM 开放平台开发者控制台提交机器人申请,由平台 Admin 审核通过后取得 sbot_... Token。
  2. 为机器人配置 HTTPS Webhook,并设置 secret_token(与下文代码中 secretToken 一致)。
  3. 编写 Webhook 服务(需保留 原始 JSON 字节 用于验签,见下文「Webhook 签名校验」)。
import { StarIMBot } from '@starim-io/bot-sdk';

const bot = new StarIMBot({
  token: process.env.STARIM_BOT_TOKEN!,
  secretToken: process.env.STARIM_WEBHOOK_SECRET!,
  baseUrl: process.env.STARIM_API_BASE // 例如 https://gateway.example/api/v1
});

bot.on('message', async ctx => {
  if (ctx.message.text) {
    await ctx.reply(`你说: ${ctx.message.text}`);
  }
});

await bot.start({ port: 8787, path: '/webhook' });

将公网 HTTPS 反代到 http://<host>:8787/webhook,在开放平台把 Webhook URL 设为 https://你的域名/webhook。在会话中 @机器人 或发 /command,即可在控制台看到 Update 并收到自动回复。

环境变量

| 变量 | 说明 | |------|------| | STARIM_API_BASE | 网关根 URL,须含 /api/v1,如 https://gw.example.com/api/v1 | | STARIM_BOT_TOKEN | 审核通过后签发的 Bot Token(sbot_ 前缀) | | STARIM_WEBHOOK_SECRET | setWebhook 时配置的 secret_token |

未设置 STARIM_API_BASE 时,客户端默认使用占位地址 https://api.starim.example/api/v1请务必改为真实网关

低阶客户端 StarIMBotClient

不跑 Webhook、仅调 Bot API 时使用:

import { StarIMBotClient } from '@starim-io/bot-sdk';

const client = new StarIMBotClient({
  token: process.env.STARIM_BOT_TOKEN!,
  baseUrl: process.env.STARIM_API_BASE
});

await client.setWebhook({
  url: 'https://example.com/webhook',
  secret_token: 'your-secret',
  allowed_updates: ['message']
});

const me = await client.getMe();
await client.sendMessage({
  chat_id: '<conversationId>',
  text: 'hello',
  // 引用回复:被引用消息的 ID(与 TG reply_to_message_id 同义,文本/媒体等发送方法均支持)
  reply_to_message_id: '<messageId>',
  reply_markup: {
    inline_keyboard: [[{ text: '收到', callback_data: 'ack' }]]
  }
});

// sendMessage 一次只创建一条消息:去除首尾空白后最多 5000 个 UTF-16 code units。
// SDK 不会自动截断或拆分,超限会收到 HTTP 400;需要分条时由调用方显式发送多次。

// 在事件处理器中可直接引用触发本次回调的消息
bot.on('message', async ctx => {
  await ctx.reply('已收到你的消息', { quote: true });
});

await client.setMyCommands({
  commands: [
    { command: 'start', description: '开始使用' },
    { command: 'help', description: '帮助' }
  ],
  scope: 'all_private_chats'
});

主要方法

| 方法 | 说明 | |------|------| | getMe() | GET /bots/me | | sendMessage | 单条文本消息(1-5000 个 UTF-16 code units;不自动截断或拆分;可选 reply_to_message_id 引用回复、reply_markup) | | setMyCommands / getMyCommands / deleteMyCommands | 命令菜单(按 scopelanguage_code 分组) | | sendPhoto / sendDocument / sendVideo / sendAudio | file_id 为上传完成后的文件 ID;可选 caption(附件配文本,混合消息)、reply_to_message_id(引用回复) | | sendLocation | 经纬度 | | editMessage / editMessageReplyMarkup / deleteMessage | 仅允许操作本机器人发送的消息;editMessageReplyMarkup 可更新或清空 InlineKeyboard | | answerCallbackQuery | 回应 callback_query 按钮点击(toast / alert / url) | | answerInlineQuery | 应答 Inline Mode 查询结果 | | answerFriendRequest | 处理用户发起的 pending 好友申请(friend_request_mode = manualfriendship_id + accept/reject) | | getFriendRequests | 列出待处理好友申请(GET /bots/getFriendRequests,与 DB pending 同源) | | setMyFriendRequestMode / getMyFriendRequestMode | 配置 / 读取好友申请策略(auto_accept / auto_reject / manual) | | setMyGroupPrivacy / getMyGroupPrivacy | 配置 / 读取群隐私模式(对标 TG /setprivacyenabled=false 时群里收全部普通消息) | | kickChatMember / banChatMember / unbanChatMember | 群治理(chat_id 为群会话 Mongo _id;机器人须为群主或群管理员;ban 为先踢后黑,非原子) | | setWebhook / deleteWebhook | Webhook 配置 | | issueUploadCredentials / completeUpload | S3 直传两步;两步都必须传同一份 64 位 SHA-256 checksum | | sendPhotoFromFile / sendDocumentFromFile | 本地路径 → 上传 → 发送 | | verifyToken() | 公开接口校验 Token | | listChatVirtualMembers / simulateSendMessage(增值能力) | 列群内可调度成员 / 以指定成员身份发送文本、图片、文件、音频、视频或位置;type 缺省为 text,默认不对外开放,需联系开放平台支持申请开通 |

调度群内虚拟成员(增值能力)

该能力默认不对外开放,需联系开放平台支持申请开通后使用。 支持文本、图片、文件、音频、视频和位置;type 不传时按 text 兼容。文本和媒体说明单条 ≤ 1000 字,所有调用均会被记录用于合规追溯。

消息类型与主要字段:

| type | 必填业务字段 | 可选字段 | |------|------------|---------| | 缺省或 text | text | reply_to_message_id | | photo / image | file_id | captionwidthheightthumbnail_urlreply_to_message_id | | document / file | file_id | captionthumbnail_urlreply_to_message_id | | audio | file_id | captiondurationperformertitlereply_to_message_id | | video | file_id | captiondurationwidthheightthumbnail_urlreply_to_message_id | | location | latitudelongitude | nameaddressreply_to_message_id |

媒体消息的 file_id 须先通过 issueUploadCredentials → S3 直传 → completeUpload 取得。位置参数范围为纬度 -90..90、经度 -180..180durationwidthheight 如填写须大于 0,reply_to_message_id 最长 128 字符。

import { StarIMBotClient } from '@starim-io/bot-sdk';

const client = new StarIMBotClient({ token: process.env.STARIM_TOKEN! });

// 1) 列出群内可被调度的成员(深页传上一页 pagination.next_cursor)
const { items, pagination } = await client.listChatVirtualMembers({
  chat_id: '<conversation_id>',
  limit: 50
});
console.log('display count', pagination.total, 'candidates', items.slice(0, 3));

// pagination.total 是展示人数;以 has_more 判断是否继续,不能以 items 为空结束。
// 下一页:client.listChatVirtualMembers({ chat_id, limit: 50, cursor: pagination.next_cursor! })
// offset 仅兼容 0–1000;409 表示名册版本变化或尚未完成独立关系切换。
// 列表不是发送许可;提交前再次校验,撤权、停用、禁言或踢出后返回 403。

// 2) 发送文本:type 可省略,服务端按 text 处理
if (items[0]) {
  const memberId = items[0].virtual_user_id;

  await client.simulateSendMessage({
    chat_id: '<conversation_id>',
    virtual_user_id: memberId,
    text: '大家好~'
  });

  // 3) 发送图片:file_id 来自 completeUpload
  await client.simulateSendMessage({
    chat_id: '<conversation_id>',
    virtual_user_id: memberId,
    type: 'photo',
    file_id: '<uploaded_file_id>',
    caption: '现场照片',
    width: 1280,
    height: 720
  });

  // 4) 发送位置
  await client.simulateSendMessage({
    chat_id: '<conversation_id>',
    virtual_user_id: memberId,
    type: 'location',
    latitude: 22.3193,
    longitude: 114.1694,
    name: '尖沙咀',
    address: '香港九龙'
  });
}

常见错误:

| HTTP | 触发 | |------|------| | 400 | 入参缺失 / virtual_user_id 格式不合法 / 消息类型不支持 / 文本或说明超长 / 媒体缺少 file_id / 坐标或媒体元数据不合法 | | 403 | 能力未开通 / 机器人不在该群 / 目标成员不在该群或状态不可用 | | 404 | virtual_user_id 不存在 |

InlineKeyboard / Callback Query

bot.command('menu', async ctx => {
  await ctx.reply('请选择:', {
    reply_markup: {
      inline_keyboard: [
        [
          { text: '点赞', callback_data: 'like' },
          { text: '文档', url: 'https://open.starim.io/' }
        ],
        [
          {
            text: '联系客服',
            action: { type: 'user_profile', user_id: '<supportUserId>' }
          },
          {
            text: '查看公开群',
            action: { type: 'public_group', group_id: '<groupId>' }
          }
        ]
      ]
    }
  });
});

bot.on('callback_query', async ctx => {
  await ctx.answerCallback({ text: `收到: ${ctx.callbackQuery?.data}` });
});

user_profile 会在 Web、App 和桌面客户端内部打开机器人或普通用户资料;public_group 仅允许打开公开群的群详情页,私有群不会跳转。user_idgroup_id 都应使用稳定的系统 ID。

Webhook 签名校验原理

平台投递时(见 WebhookDeliveryWorker)下发的签名头:

| 头 | 算法 | 防 replay | 状态 | | --- | --- | --- | --- | | X-StarIM-Signature-V2 | sha256=HMAC(secret, "${ts}.${body}") | (结合 X-StarIM-Timestamp 判 5min 时窗) | 当前唯一推荐 | | X-StarIM-Timestamp | unix 秒 | — | V2 必带 | | X-StarIM-Update-Id | 透传 update.update_id | — | 用于幂等 |

SDK 的 verifyWebhookSignature / processWebhook / webhookMiddleware 默认使用 V2 校验。

服务端必须用 与计算签名时完全相同的字节 验签。若使用 express.json() 默认中间件,会丢失原始字节,签名将无法对齐。推荐之一:

方式 A:express.raw

import express from 'express';
import { StarIMBot } from '@starim-io/bot-sdk';

const app = express();
const bot = new StarIMBot({ token: '...', secretToken: '...' });

app.post('/webhook', express.raw({ type: 'application/json' }), bot.webhookCallback());

方式 B:express.jsonverify 保存 rawBody

app.use(
  express.json({
    verify: (req: express.Request & { rawBody?: Buffer }, _res, buf) => {
      req.rawBody = buf;
    }
  })
);
// 再挂载 bot.webhookCallback(),SDK 会优先读取 req.rawBody

手写验签示例(V2):

import { createHmac, timingSafeEqual } from 'node:crypto';

function verifyV2(
  secret: string,
  rawBody: Buffer,
  sigHeader: string | undefined,
  tsHeader: string | undefined
) {
  if (!sigHeader?.startsWith('sha256=') || !tsHeader) throw new Error('bad sig');
  const ts = Number(tsHeader);
  if (!Number.isFinite(ts) || Math.abs(Math.floor(Date.now() / 1000) - ts) > 300) {
    throw new Error('replay window');
  }
  const expected = Buffer.from(sigHeader.slice(7), 'hex');
  const actual = createHmac('sha256', secret)
    .update(`${ts}.`)
    .update(rawBody)
    .digest();
  if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) {
    throw new Error('bad sig');
  }
}

多媒体:本地 / Buffer / URL

import {
  StarIMBotClient,
  sendPhotoFromBuffer,
  sendPhotoFromUrl,
  sendVideoFromUrl
} from '@starim-io/bot-sdk';
import { readFile } from 'node:fs/promises';

const client = new StarIMBotClient({ token: '...', baseUrl: '...' });

await client.sendPhotoFromFile(chatId, './a.png', { caption: '本地文件' });

const buf = await readFile('./a.png');
await sendPhotoFromBuffer(client, chatId, buf, { fileName: 'a.png', fileType: 'image/png' });

await sendPhotoFromUrl(client, chatId, 'https://example.com/p.png', { fileName: 'p.png' });
await sendVideoFromUrl(client, chatId, 'https://example.com/v.mp4', {
  fileName: 'v.mp4',
  fileType: 'video/mp4',
  caption: '可选说明',
  duration: 10
});

URL 一步法只接受 HTTP(S),默认最多下载 100MB、最长等待 120 秒,读取响应体过程中也会持续检查 实际字节数,避免无 Content-Length 的异常响应撑爆进程内存。可通过 maxBytestimeoutMssignal 收紧限制或主动取消;远程 URL 应来自可信来源,SDK 不替业务方开放任意内网抓取能力。

一步法会先计算完整文件 SHA-256,并在凭证请求中显式发送 checksumreuseExisting: truereuseRequestIdisPublic: false。 服务端命中同 Bot、同类别、文件名、大小和访问级别的可信内容后,会复用物理对象但为本次操作 创建新的逻辑 File,再返回 uploadRequired: false;SDK 会跳过 PUT 与 complete。直接调用 issueUploadCredentials / completeUpload 时,checksum 同样必填且必须是完整文件的 64 位 SHA-256 十六进制摘要;缺失或格式错误会在网络请求前失败。低层调用默认不开启秒传;调用方启用时 必须为同一次重试保持相同 reuseRequestId,不同逻辑上传必须使用不同值。SDK 不会跨消息缓存或复用旧 file_id:即使字节 完全相同,每次业务发送也会取得独立逻辑 File,只有底层 FileBlob/S3 物理对象被共享。

错误码说明(常见)

平台与网关可能返回 success: false 或 HTTP 4xx/5xx,SDK 统一抛出 StarIMApiError(含 statusCodemessagecode 若存在)。文档与产品侧常见语义如下(具体以网关响应为准):

| 语义 | 可能场景 | |------|----------| | invalid_token / 401 | Token 错误、吊销、格式不对 | | bot_disabled / 403 | 机器人未审核通过、已停用 | | chat_forbidden / 4xx | 无权限向目标会话发消息 | | rate_limited / 429 | 触发发送配额 | | webhook_invalid | Webhook URL 非 HTTPS 或配置无效(配置接口返回消息) |

幂等与重复投递

平台重试 Webhook 时 保持同一 update_idStarIMBot 内置 LRU(默认 1024 条)去重:重复 update_id 返回 200 ok 且不再执行业务 handler。可传入自定义 DedupeStore(见 processWebhook / 源码 webhook.ts)。

示例脚本

examples/ 目录:

  • echo-bot.ts — 回声
  • slash-command.ts/start/help
  • send-photo.ts — 本地图片上传并发送
cd botsdk/nodejs
pnpm install
npx tsx examples/echo-bot.ts

构建

pnpm run build   # 输出 dist/(ESM + CJS + 类型声明)
pnpm run test    # Vitest

License

MIT © StarIM