源码副本已存档至 raw/repos/feishu-ids/(feishu_ids.js + README.md)。 单文件 Node.js 18+ 脚本,零第三方依赖(仅用内置 fetch):通过飞书自建应用凭证查询用户 user_id/open_id 和群 chat_id。

用法

node feishu_ids.js \
  --app-id <larkAppId> --app-secret <larkAppSecret> \
  [--user-name <姓名>] [--mobile <手机号>] [--chat-name <群名>]

凭证与查询条件也可走环境变量:LARK_APP_ID / LARK_APP_SECRET / FEISHU_USER_NAME / FEISHU_MOBILE / FEISHU_CHAT_NAME。

飞书 ID 体系(本工具要解决的核心问题)

ID前缀作用域说明
user_id无前缀租户内唯一租户内标识用户,跨应用通用
open_idou_应用内唯一同一用户在不同应用里 open_id 不同,机器人消息/事件里拿到的就是它
union_idon_开发者内唯一同一开发者的多个应用间打通
chat_idoc_群即通常说的 group_id,机器人发消息/管理群的凭据

botmux 机器人协作时 @ 人用的就是 open_id(--mention ou_xxx),本脚本是拿到这些 ID 的最快途径(见 botmux)。

鉴权:tenant_access_token

// POST /auth/v3/tenant_access_token/internal  { app_id, app_secret }
// 返回 { code, msg, tenant_access_token, expire } —— 注意没有 data 字段

自建应用(internal)模式换取租户级 token,有效期 2 小时,脚本每次运行重新获取,不做缓存。后续所有请求带 Authorization: Bearer <token>。

通用请求封装与飞书成功约定

async function api(method, path, body) {
  const res = await fetch(BASE + path, { ... });
  const data = await res.json();
  if (data.code !== 0) { ... throw err with err.code ... }
  return data.data || {};
}

飞书 OpenAPI 约定:HTTP 200 不代表成功,必须 body.code === 0。错误码在 code/msg 中,脚本把 code 挂到异常对象上供上层做分支(权限降级就靠它)。

查用户:两级降级策略

优先 —— 手机号精确换取(需权限 contact:user.id:readonly):

// POST /contact/v3/users/batch_get_id?user_id_type=user_id|open_id
// body: { mobiles: ["13800000000"] }

对 user_id、open_id 两种类型各调一次拿全。遇到权限错误码 99991672/99991663 返回 null,触发降级。

降级 —— 遍历可见范围按姓名/手机号匹配(需权限 contact:user.base:readonly):

// GET /contact/v3/scopes           → 应用可见范围内全部用户 open_id 列表
// GET /contact/v3/users/{open_id}  → 逐个取详情,name 精确匹配或 mobile 尾部匹配

手机号匹配用 u.mobile.replace(/[^0-9]/g, "").endsWith(mobileNum) 容忍国际区号前缀(如 +86)。单个用户查详情失败跳过不中断。

关键边界:只能查到应用「可用范围」内的用户(飞书默认仅创建者可见,需去开放平台扩可用范围)。

查群:分页拉取 + 群名匹配

// GET /im/v1/chats?page_size=100[&page_token=...]  (需权限 im:chat:readonly)
do {
  const data = await api("GET", `/im/v1/chats?page_size=100${...}`);
  chats.push(...(data.items || []));
  pageToken = data.has_more ? data.page_token : "";
} while (pageToken);

飞书列表接口统一分页协议:page_size + page_token 请求,has_more + page_token 响应,do-while 翻到底。

关键边界:只返回机器人已加入的群;查不到时脚本会列出机器人当前所有群并提示「先把机器人拉进目标群」——这是 API 限制,无降级方案。

所需权限

权限用途必需?
contact:user.id:readonly手机号/邮箱换取 user_id否(缺失自动降级)
contact:user.base:readonly遍历可见范围用户拿详情是(降级方案依赖)
im:chat:readonly列出机器人所在群是

权限修改后需在开放平台发布新版本才生效。

常见错误码

code含义处理
99991672Access denied,权限未开通按提示链接申请权限并发布版本
99991663 / 99991661token 缺失/权限校验失败脚本重新取 token / 走降级
查不到用户不在应用可用范围开放平台扩「可用范围」
查不到群机器人未入群拉机器人进群后重查

关键技术点小结

技术点实现为什么这么做
鉴权internal 模式 tenant_access_token自建应用最简路径,无需用户授权流程
成功判断body.code === 0 而非 HTTP 状态码飞书 OpenAPI 统一约定
权限降级捕获 99991672/99991663 转 null → 换方案不同租户开通权限不一,脚本自适应
ID 获取batch_get_id 两次调用(user_id/open_id 两种 user_id_type)该接口一次只返回一种类型
分页do-while + page_token/has_more飞书列表接口标准协议
零依赖Node 18+ 内置 fetch运维脚本免 npm install

相关页面

  • botmux — 飞书话题群桥接,@mention 用 open_id,本脚本可查 open_id
  • web-terminal-pty — code-reading 目录首篇(源码讲解)