# 飞书群发消息 + @人（已验证配方 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":"<at>…</at> 正文"}`，@ 标签 `<at user_id="ou_xxx">显示名</at>`（用 **open_id**；嵌进 JSON 字符串时内层引号按 JSON 规则转义）。@所有人 `<at user_id="all"></at>`。
- **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":"<at user_id=\"ou_757f053c9d7aff6c73b18aa60c337756\">Doro</at> 正文…"}' \
  --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` |
