# 飞书群发消息 + @人(已验证配方 2026-06-16) 目标:把消息发到**正确的飞书群**并 **@ 正确的人**,一次做对。依赖 `lark-cli`(lark-im 技能)。 ## 步骤 ### 1. 找出 bot 实际所在的群(不要猜 chat_id) ```bash cd ~ && lark-cli im +chat-list --as bot ``` 返回每个群的 `chat_id`、`name`、`description`、`owner_id`。挑出你和目标人**共处**的那个群。 - 本环境已知群:"Doro, Maggie, 魏玮"(描述"小Maggie工作群")= `oc_a927f86118216c36cb9394b0e95f2a11`。 - 若有多个候选群,按成员和描述确认,仍不确定就问用户,别赌。 ### 2. @人的格式(两种已验证写法,均触发真实可点击 @ + 通知) - **text 模式**:`--msg-type text`,content 形如 `{"text":" 正文"}`,@ 标签 `显示名`(用 **open_id**;嵌进 JSON 字符串时内层引号按 JSON 规则转义)。@所有人 ``。 - **post 模式**(多行/结构化汇报推荐,2026-06-16 已验证):post JSON 里 @ 用 at 元素 `{"tag":"at","user_id":"ou_xxx"}`,与文本元素 `{"tag":"text","text":"…"}` **同行**拼接,配 `--msg-type post`。结构: `{"zh_cn":{"content":[[{"tag":"at","user_id":"ou_757f053c9d7aff6c73b18aa60c337756"},{"tag":"text","text":" 进展汇报…"}],[{"tag":"text","text":"第二行"}]]}}` - **不要用 `--markdown`** 发 @:会强制转 post 且 @ 处理不可靠,用显式 post JSON 自己控制。 ### 3. 先 dry-run 验证 payload ```bash lark-cli im +messages-send --chat-id oc_xxx --as bot \ --content '{"text":"Doro 正文…"}' \ --msg-type text --dry-run ``` 检查 body 里 chat_id、msg_type、@标签是否正确。 ### 4. 去掉 --dry-run 正式发送 成功返回 `message_id` + `create_time`。把 message_id 和**北京时间**报给用户便于核对。 ## 发送前如何核实「这个 open_id 确实是目标本人」 @错人是红线。理想是查通讯录,但本 bot **常缺 contact / chat 读权限**,按可靠性从高到低: 1. **gateway.log 取地面真值(最可靠,无需任何 scope)**——平台事件原始数据,比 API、比记忆都硬: ```bash grep "oc_<群id>" ~/.hermes/logs/gateway.log | grep -oE "ou_[a-z0-9]{20,}" | sort | uniq -c | sort -rn ``` 再看「当前这条消息」的入站行确认发送者: ```bash grep "inbound message: platform=feishu" ~/.hermes/logs/gateway.log | tail # → user=ou_xxx chat=oc_xxx msg='…' 即是谁在这个群说了这句话 ``` 把刚收到那句话的 `user=ou_xxx` 与已知映射比对,一致才发。 2. **已知 open_id 映射**(下方表 + memory),ID 没映射到人 → 先问不猜。 3. **通讯录 API(常被 scope 挡)**:`lark-cli contact +get-user --user-id ou_xxx --user-id-type open_id --as user`。本环境 2026-06-16 报缺 `contact:user.basic_profile:readonly`;`im chat.members get` 也缺 `im:chat.members:read` 等。缺权限是**正常状态**,别卡在这里——退回方法 1。 ## 已知 open_id(核对身份用,新增/纠正后同步到 memory) - Doro = `ou_757f053c9d7aff6c73b18aa60c337756`(与 bot 私聊 chat=`oc_9292e11bd98ddb5a69ea2c2da10d4f12`) - Scott(魏玮) = `ou_04fade9a9335c09ad09846da2051b3c0` ## 注意 - `--as bot`:消息以应用 bot 名义发出,bot 必须已在目标群里。 - lark-cli 是 API 工具,不是消息网关;bot 身份不代理用户(Scott 已纠正)。 - 终端里 lark-cli 输出常带 HTTPS_PROXY 的 WARN 和版本更新 notice,是噪音,不影响结果;可 `grep -v "WARN\|proxy"` 过滤。 ## 发文件附件到群里(md/pdf/docx 报告,已验证) 要把一份**文件**(错误分析 md、合同 docx、报告 pdf)发到群里,用 `--file`。常见组合:**先发文件附件,再发一条 @某人 的 post 说明**(文件本身不能 @ 人,说明消息负责 @)。 ```bash # 先发文件(--file 接 cwd 相对路径),成功返回 message_id 并打印 "uploading file: xxx" cd ~/lark_send_tmp && lark-cli im +messages-send \ --chat-id oc_a927f86118216c36cb9394b0e95f2a11 \ --as bot --file "./报告.md" \ --dry-run 2>&1 | grep -v "WARN\|proxy" # 先 dry-run,确认后去掉 --dry-run # 再发 @某人 的 post 说明(见上「@人的格式」post 模式),把文件背景+要点写清楚 ``` ### ⚠️ 关键坑:`--file` 只接 cwd 相对路径,绝对路径被拒 lark-cli 安全限制:`--file`(及 `--image`/`--video`/`--audio`)**拒绝绝对路径**(如 `/tmp/x.md`),路径解析 `..`/symlink 后必须仍在 cwd 内。 **解法**:把文件复制到干净工作目录,`cd` 进去用 `./文件名` 发: ```bash mkdir -p ~/lark_send_tmp && cp "/tmp/报告.md" ~/lark_send_tmp/ cd ~/lark_send_tmp && lark-cli im +messages-send --chat-id oc_xxx --as bot --file "./报告.md" rm -rf ~/lark_send_tmp # 发完清理 ``` - 本地文件 lark-cli 会**先自动上传**再发 file 消息,无需手动 `images.create`/拿 file_key。 - dry-run 时 file_key 显示占位符 `file_dryrun_upload` 是正常的,正式发送才真上传。 - 同理发图片用 `--image ./x.png`,视频 `--video ./x.mp4 --video-cover ./cover.png`。 ## 已知群与人 ID(核对身份用) | 对象 | ID | |------|-----| | 飞书工作群"Doro, Maggie, 魏玮"(小Maggie工作群) | `oc_a927f86118216c36cb9394b0e95f2a11` | | Doro | `ou_757f053c9d7aff6c73b18aa60c337756`(私聊 chat `oc_9292e11bd98ddb5a69ea2c2da10d4f12`,别和群混) | | Scott / 魏玮 | `ou_04fade9a9335c09ad09846da2051b3c0` | | @所有人 | `all` |