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

@gotong/im-lark

v0.1.0

Published

Gotong IM bridge for Lark / Feishu — implements ImBridge over the OFFICIAL long connection (@larksuiteoapi/node-sdk WSClient). Outbound, works behind NAT (no public webhook). Sends still go through the REST client (fetch). Card-button callbacks are not de

Readme

@gotong/im-lark

A Lark / Feishu Open Platform Bot bridge implemented against @gotong/im-adapter's ImBridge interface.

Inbound runs over the official long connection (@larksuiteoapi/node-sdk WSClient + EventDispatcher) — the bridge dials OUT to Lark and receives im.message.receive_v1 events over a persistent socket. No public callback URL, no TLS, no reverse proxy, no verification token: it works behind NAT like Telegram / Discord / Matrix. This matches how OpenClaw / Hermes connect Feishu.

Outbound (sendMessage) still goes through the REST client (POST /open-apis/im/v1/messages). Only the inbound transport is the long connection.

国内(Feishu)和国际(Lark)切换只需要换 baseUrl

What you get

import { LarkBridge } from '@gotong/im-lark'
import { parseImCommand } from '@gotong/im-adapter'

const bridge = new LarkBridge({
  appId: process.env.LARK_APP_ID!,
  appSecret: process.env.LARK_APP_SECRET!,
  // baseUrl: 'https://open.larksuite.com',  // 国际版 (via clientOptions)
  // baseUrl is 'https://open.feishu.cn' by default (国内)
  onError: (err) => console.error('[lark]', err),
})

bridge.onMessage(async (msg) => {
  const cmd = parseImCommand(msg.text)
  switch (cmd.kind) {
    case 'help':
      await bridge.sendMessage(msg.from, '发 /bind <code> 来连接', {
        chatId: msg.chatId,
      })
      break
    case 'bind':
      // hand off to ImBindingResolver (host wiring)
      break
    case 'free':
      // dispatch to Hub
      break
    // …
  }
})

await bridge.start()   // dials the long connection
// later
await bridge.stop()

Setup at the Open Platform admin panel

  1. 创建应用(cli_xxx)— 拿到 App ID + App Secret
  2. 在「凭证与基础信息」页找到 App Secret
  3. 启用「机器人」能力,在「事件订阅」页:
    • 传输方式选「长连接」(不是「将事件发送至开发者服务器」)。 长连接由 bot 主动外拨,无需公网回调地址、无需 HTTPS、无需配 Verification Token / Encrypt Key。
    • 添加事件 im.message.receive_v1
  4. 给机器人加权限:im:message(接收)+ im:message:send_as_bot(回消息)。
  5. 「版本管理与发布」发布到企业内部测试 / 全量。

国际版 Lark 的配置在 open.larksuite.com,流程一致。

为什么长连接,不是 webhook?

webhook(事件订阅推到开发者服务器)要求一个公网 HTTPS 回调地址 —— 家 用机得做内网穿透 / 反代。官方长连接(@larksuiteoapi/node-sdkWSClient)是出站的:bot 拨一条持久 WebSocket 给 Lark,事件经它推 回来。这跟 Telegram long-poll / Discord gateway / Matrix /sync 同一 类「官方 + 免穿透」模式,也是 OpenClaw / Hermes 接 Feishu 的路径。

SDK 负责 socket、重连、事件分帧;bridge 只剩三件事:去重、映射、派发

连接通过一个可注入工厂connectionFactory)创建,默认工厂惰性 import('@larksuiteoapi/node-sdk')。hermetic 测试注入 fake 工厂,喂合成 im.message.receive_v1 事件,不碰真 SDK、不开 socket。

Surface

| Export | Purpose | |-------------------------------------|---------------------------------------------------------------| | LarkBridge | ImBridge impl over the official long connection | | defaultLarkConnectionFactory | 默认连接工厂(惰性 import 官方 SDK 的 WSClient/EventDispatcher)| | LarkConnectionFactory (type) | 注入点:测试 / 自定义传输实现可替换 | | createLarkClient | Fetch 封装,自动管理 tenant_access_token(出站发消息用) | | LarkApiError | 非 0 code 抛出 — 携带 code, msg, status | | larkToImMessage | 纯映射: LarkMessageReceiveEventImMessage | | larkExtractAttachments | 拉 image / audio / file 出来 | | larkUri / parseLarkUri | lark-image: / lark-audio: / lark-file: URI 工具 | | stripLarkMentions | 剥掉群消息里的 <at> 标签 | | pickLarkReceiveIdType | 根据前缀(oc_/ou_/on_)推 receive_id_type 值 |

Token 管理(出站发消息)

发消息走 REST,用短命的 tenant_access_token(约 2 小时 TTL)。 createLarkClient

  • 第一次业务调用前 POST /open-apis/auth/v3/tenant_access_token/internal 拿 token,缓存
  • 在 token 过期前 ~2 分钟(safety margin)主动 refresh
  • 并发调用合并到同一个 refresh promise(不 thundering-herd)
  • invalidateToken() 强制下次调用 refresh(在响应里看到 99991663 之类 token 失效错误时调)

附件如何工作

入向 image / audio / file 包装成 lark-<kind>:<key> URI;字节 预下载。下游需要时:

import { parseLarkUri } from '@gotong/im-lark'

const parsed = parseLarkUri(att.url)!
const endpoint = parsed.kind === 'image' ? 'images' : 'files'
const url = `https://open.feishu.cn/open-apis/im/v1/${endpoint}/${parsed.key}`
const res = await fetch(url, {
  headers: { authorization: `Bearer ${TENANT_TOKEN}` },
})
const bytes = await res.arrayBuffer()

群里 @机器人 的处理

群消息里机器人会被 <at user_id="ou_bot">@Bot</at> 提及。parseImCommand 不接受这种前缀,所以 bridge 默认 stripBotMentions: true 帮你去掉。如 果要保留原文,关掉这个选项。

国内 vs 国际

只换 baseUrl(出站 REST 用),事件 schema 完全一致;长连接由 SDK 按 appId 自动选对应域:

// 国内 Feishu — 默认
new LarkBridge({ ..., /* baseUrl: 'https://open.feishu.cn' (default) */ })

// 国际 Lark
new LarkBridge({ ...,
  clientOptions: { baseUrl: 'https://open.larksuite.com' }
})

不做的

  • 交互卡片回调按钮 (card.action.trigger). 长连接不投递卡片按钮 回调事件 —— 那只走 HTTPS 回调。纯文本聊天(bridge 的本职)完全覆盖; 需要卡片按钮的 hub 得另起一条 webhook 旁路(超出本桥范围)。
  • 出向附件 / 图片 / 卡片. sendMessage 带 attachments 会触发 onError 但 text 仍发出,跟 Telegram / Matrix 一致。
  • OAuth/SSO/Marketplace 应用流程. 假定内部企业应用 + 长期 app secret。

测试

tests/bridge.test.ts 注入 fake connectionFactory,捕获 bridge 在 start() 里挂的 onMessageReceive 回调,再用 emit() 喂合成 im.message.receive_v1 事件 —— 不碰真 SDK、不开 socket、不占端口。 去重(按 message_id)、去 @ 标签、app-sender 反环、派发全在这条接缝上 验证。FakeLarkClient 不打网络,sendMessage 验证 receive_id_type 自动 嗅探。

Status

  • IM 桥官方化 — Lark/飞书改官方长连接(替代旧 webhook 模式)。
  • 同批:QQ 改官方 Bot webhook、Slack 改 Socket Mode。

See docs/zh/IM-OFFICIAL-REARCH.md for the official-API re-architecture rationale (OpenClaw / Hermes 对照 + 各桥 transport 决策).