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

chatboty

v0.2.1

Published

chatboty — 企业微信(WeCom)IM 自动化 SDK。自研全生态框架, 收发消息/群管理/群事件/联系人, 经云网关连接你的探针实例。

Readme

chatboty

企业微信(WeCom)IM 自动化 SDK —— 自研全生态框架,零外部框架依赖。

用一个订阅 token,即可在 Node 里收发消息、管理群、监听群事件、读联系人。SDK 自动向平台发证、连接到分配给你的探针实例。

概念(Contact / Room / Message / 事件)类似业界常见的聊天机器人框架,但 chatboty 为完全自研实现

安装

npm install chatboty

要求 Node.js >= 18。

快速上手

import { Chatboty } from 'chatboty'

const bot = new Chatboty({
  token: 'cbt_你的订阅token',          // 来自 chatboty 控制台
  // atlasUrl: 'https://api.chatboty.ai', // 平台地址(默认本地开发地址)
})

bot.on('message', async (m) => {
  if (m.self()) return                  // 跳过自己发的
  console.log(`[${m.id}] ${m.talker()?.name()}: ${m.text()}`)
  if (m.text() === 'ping') await m.say('pong')
})

bot.on('room-join', (e) => {
  const names = e.contactList.map(c => c.name()).join('、')
  e.room.say(`欢迎 ${names} 加入!`)
})

bot.on('login', (self) => console.log('已登录:', self.name()))  // self 是 typed Contact
bot.on('error', (err) => console.error('探针错误:', err.message)) // err 是真正的 Error

await bot.start()

就这样——token 决定连到哪个企业微信实例,平台负责路由与隔离。

同步 vs 异步约定

一条简单规则贯穿所有实体:

实体已经持有的数据(来自事件 payload / 缓存)用同步读取; 任何需要回连接器 / 企微的操作(下载、拉通讯录、发送、建群、改名…)一律 async

  • 同步:m.idm.text()m.type()m.self()m.talker()m.room()m.mentionSelf()c.idc.name()c.alias()c.title()c.phone()r.idr.topic()r.owner()
  • 异步:m.say()m.toFileBox()m.download()m.mentionList()r.say()r.rename()r.memberAll()bot.currentUser()bot.contactFind*()bot.roomFind*()bot.roomCreate()bot.sayTo()

id只读属性m.id,不是 m.id())——Message / Contact / Room 皆然。

事件

| 事件 | 回调参数 | 说明 | |---|---|---| | message | Message | 收到消息(文本/图片/文件/链接/小程序/系统) | | room-join | { room, contactList, idList, timestamp } | 有人进群(含外部客户完整信息) | | room-leave | { room, contactList, idList, timestamp } | 有人退群/被移除 | | room-topic | { room, topic, oldTopic, timestamp } | 群改名 | | friendship | Friendship | 加好友/删好友 | | login | Contact | 登录成功, 回调收到 typed 的当前登录者 Contact | | logout | — | 登出 | | scan | { qrcode, status } | 登录二维码 | | error | Error | 探针/代理侧错误信号(不中断运行, 仅上报; 回调收到真正的 Error 对象) | | heartbeat | { state } | 探针心跳(~30s), 供存活/掉线判定 |

实体 API(节选)

// Message —— id 是只读属性; 其余同步读取; 下载/发送为 async
m.id; m.text(); m.type(); m.self(); m.talker(); m.room(); m.file()
await m.say(sayable)
m.mentionSelf();          // 本条是否 @ 了我/@所有人(布尔, 纯文本判定)
await m.mentionList();    // 被 @ 的 Contact[](best-effort: 解析正文@名→就近解析通讯录)
const f = await m.toFileBox();   // → ReceivedFile{dataUrl,mime,name,size} + toBuffer()/save(); 别名 m.download()
if (f) await f.save('./photo.png');   // 或 const buf = f.toBuffer()

// Contact
c.id; c.name(); c.alias(); c.avatar(); c.title(); c.phone(); c.type(); await c.say(sayable)
// 注: 企微通讯录未采集性别 → 不提供 gender()

// Room
r.id; r.topic(); r.owner();                  // 同步(缓存)
await r.say(sayable); await r.rename(topic); await r.memberAll()

// Bot —— 集合方法对称: contactFind/contactFindAll, roomFind/roomFindAll, roomCreate
bot.roomCreate(memberIds, topic)
await bot.roomFind({ topic: '项目群' });   // 或 roomFind('关键词') → Room | undefined
await bot.roomFindAll();                   // → Room[](全部已加载群; 传 query 则本地过滤)
await bot.contactFind({ name: '张三' });   // 或 contactFind('关键词') → Contact | undefined
await bot.contactFindAll('关键词');        // → Contact[](本地通讯录过滤)
await bot.currentUser(); bot.self;         // 当前登录者(Contact) / 缓存的 self getter
await bot.logout();                        // 干净登出(回扫码页, 不重启探针)

发送内容(Sayable)

await contact.say('纯文本')
await room.say({ image: 'https://.../pic.png' })   // 图片(URL/本地路径/base64)
await room.say({ file: '/path/to/doc.pdf' })       // 文件
await room.say({ url: 'https://...', title: '标题', description: '摘要', thumbUrl: '...' })  // 链接卡片
await room.say({ miniProgram: { appid: '...', title: '...', path: '...', thumb: '...' } })   // 小程序卡片

服务发现接入(推荐)

token 直连外,也可用服务令牌 + 服务发现端点接入。SDK 会先向发现端点换取一个短 TTL 的接入凭据,再连网关,并在过期前自动刷新:

const bot = new Chatboty({
  serviceToken: '你的服务令牌',                      // 长期令牌
  serviceAuthority: 'https://discovery.chatboty.ai', // 发现端点(权威 URL)
})
await bot.start()

流程:POST {serviceAuthority}Authorization: Bearer {serviceToken})→ { endpoint, accessToken, expiresIn } → 连 wss://{endpoint}/client?access={accessToken}(网关验 RS256 → seatId)。accessToken 到期前约 30s 自动重新发现刷新;断线按指数退避自愈重连。仅给 { token, endpoint } 时维持既有 ?token= 直连行为不变。

仅用传输层(高级)

不想要框架封装,只要底层连接?

import { ChatbotyClient } from 'chatboty/client'
const c = new ChatbotyClient({ token: 'cbt_...' })
await c.start()
const rooms = await c.roomList()

License

Apache-2.0