diff --git a/skills/lark-im/references/lark-im-messages-send.md b/skills/lark-im/references/lark-im-messages-send.md index 484c024f4..328189095 100644 --- a/skills/lark-im/references/lark-im-messages-send.md +++ b/skills/lark-im/references/lark-im-messages-send.md @@ -1,10 +1,8 @@ # im +messages-send -> **Prerequisite:** Read [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) first to understand authentication, global parameters, and safety rules. +> **Prerequisite:** Read [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) first for authentication, global parameters, and safety rules. -Send a message to a group chat or a direct message conversation. Supports both user identity (`--as user`) and bot identity (`--as bot`). - -This skill maps to the shortcut: `lark-cli im +messages-send` (internally calls `POST /open-apis/im/v1/messages`). +Send a message to a group chat (`--chat-id oc_xxx`) or a direct message (`--user-id ou_xxx`). One step, supports `--as user` and `--as bot` (default `bot`). Maps to shortcut `lark-cli im +messages-send` (`POST /open-apis/im/v1/messages`). ## Safety Constraints @@ -16,249 +14,94 @@ Messages sent by this tool are visible to other people. Before calling it, you * **Do not** send messages without explicit user approval. -When using `--as bot`, the message is sent in the app's name, so make sure the app has already been added to the target chat. - -When using `--as user`, the message is sent as the authorized end user and requires the `im:message.send_as_user` and `im:message` scopes. +- `--as bot` (TAT, scope `im:message:send_as_bot`): the message is sent in the app's name — the app must already be in the target chat or have a DM relationship with the target user. +- `--as user` (UAT, scopes `im:message.send_as_user` + `im:message`): the message is sent as the authorized end user. ## Choose The Right Content Flag -### Default Selection Rule For Agents +| Content | Flag | Why | +|---|---|---| +| Headings, lists, links, summaries, reports (lightweight formatting) | `--markdown` | Best default; converted to Feishu `post` JSON | +| Exact plain text — logs, code, indentation, literal Markdown chars that must **not** render | `--text` | Preserves literal text; no conversion | +| Exact `post` JSON, a `post` title, multiple locales, cards (`interactive`), `share_*`, or unsupported structures | `--content` | You provide the final JSON; it must match the effective `--msg-type` | +| Image / file / video / audio | `--image` / `--file` / `--video` / `--audio` | Uploads URLs or cwd-relative local files automatically | -- Prefer `--markdown` for headings, lists, links, summaries, reports, or Markdown-looking content. -- Use `--text` for exact plain text: logs, code, indentation-sensitive text, or literal Markdown. -- Use `--content` for exact `post` JSON, titles, multiple locales, cards, or unsupported structures. +These content flags (and the media flags) are **mutually exclusive** — pass exactly one. Media flags are also mutually exclusive with each other. -| Need | Recommended flag | Why | -|------|------|------| -| Send headings, lists, links, summaries, or reports | `--markdown` | Best default for lightweight formatting; converted to Feishu `post` JSON | -| Send plain text exactly as written | `--text` | Preserves literal text; no Markdown conversion | -| Precisely control the final payload | `--content` | You provide the exact JSON for `text` / `post` / `interactive` / `share_*` / media payloads | -| Send image / file / video / audio | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads URLs, or cwd-relative local files automatically | +## `--markdown` Gotchas -### `--text` vs `--markdown` +`--markdown` always forces `msg_type=post` (single `zh_cn` locale) and normalizes input for Feishu post rendering. Key boundaries (not full CommonMark/GFM): -- Use `--markdown` for lightweight formatted messages. -- Use `--text` for exact plain text, especially logs, code, indentation, or Markdown characters that should **not** render. -- Use `--content` when `--markdown` is not enough, especially if you need exact `post` JSON, a title, multiple locales, cards, or unsupported rich structures. +- **No `post` title** — if you need one, use `--content` with `post` JSON. +- **Headings rewritten**: `# Title` → `#### Title`; `##`–`######` normalized to `#####` when content has H1–H3. Code blocks preserved; excess blank lines compressed. +- **Images**: pre-upload via `im images create` and reference `![alt](img_xxx)` for reliable results. Remote `https://` URLs are auto-downloaded+uploaded at runtime (removed with a warning if that fails). Local paths in `![x](./a.png)` are **not** supported and will not auto-upload. -## What `--markdown` Really Does +## Preserving Exact Formatting -`--markdown` accepts Markdown-like input and converts it to the Feishu `post` payload required by the message API. - -The shortcut does all of the following before sending: - -1. Forces `msg_type=post` -2. Resolves remote Markdown images like `![x](https://...)` by downloading and uploading them first -3. Normalizes the Markdown for Feishu post rendering -4. Wraps the result as: - -```json -{"zh_cn":{"content":[[{"tag":"md","text":"..."}]]}} -``` - -This makes `--markdown` the simplest path for lightweight formatted messages. - -### Markdown Boundaries - -- It does **not** promise full CommonMark / GitHub Flavored Markdown support. -- It always becomes a `post` payload with a single `zh_cn` locale. -- It does **not** let you set a `post` title. If you need a title, use `--msg-type post --content ...`. -- Headings are rewritten: - - `# Title` becomes `#### Title` - - `##` to `######` are normalized to `#####` when the content contains H1-H3 -- Consecutive headings are separated with blank lines after heading normalization. -- Block spacing and line breaks may be normalized during conversion. -- Code blocks are preserved as code blocks. -- Excess blank lines are compressed. -- Already-uploaded `img_xxx` image keys are the most reliable Markdown image input. -- Local paths in Markdown image syntax like `![x](./a.png)` are **not** supported and will not be auto-uploaded. -- Remote URLs (`https://...`) will be auto-downloaded and uploaded at runtime; if the download or upload fails, the image is removed with a warning. - -If you need a title, multiple locales, cards, unsupported rich structures, or byte-for-byte post JSON control, use `--content` and provide the final JSON yourself. - -### Image Constraint for `--markdown` - -When using `--markdown` with images, prefer pre-uploading via `images.create` and referencing `![alt](img_xxx)` for predictable results. Remote URLs may work but are not guaranteed. - -**Steps:** +For multi-line text, indentation, code blocks, tabs, or many backslashes/quotes, use shell ANSI-C quoting `$'...'` so `\n` is written explicitly. Use `--text` + `$'...'` when the receiver must see the text exactly as entered: ```bash -# 1. Upload image to get image_key -lark-cli im images create --data '{"image_type":"message"}' --file ./diagram.png -# Returns: {"image_key":"img_v3_xxxx"} - -# 2. Use image_key in --markdown -lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Report\n\n![diagram](img_v3_xxxx)\n\nSee above for details.' +lark-cli im +messages-send --chat-id oc_xxx --text $'Build failed\nBranch: feature/x\nAction: check logs' ``` -## Preserving Formatting - -If the message has multiple lines, indentation, code blocks, tabs, or many quotes/backslashes, prefer shell ANSI-C quoting with `$'...'` for either `--markdown` or `--text`. - -This is especially useful in `zsh` / `bash` because it lets you write `\n` explicitly instead of relying on the shell to preserve literal newlines. - -### When formatting must be preserved - -Use `--text` plus `$'...'`: - -```bash -lark-cli im +messages-send --chat-id oc_xxx --text $'Build failed\nBranch: feature/im-docs\nAction: please check logs' -``` - -```bash -lark-cli im +messages-send --chat-id oc_xxx --text $'```bash\nmake test\nmake lint\n```' -``` - -Use this path when you want the receiver to see the text exactly as entered, not a converted Markdown post. - ## Commands ```bash -# Send a formatted update +# Formatted update (Markdown → post) lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Update\n\n- item 1\n- item 2' -# Send a plain one-line message +# Plain one-line text lark-cli im +messages-send --chat-id oc_xxx --text "Hello" -# Equivalent manual JSON -lark-cli im +messages-send --chat-id oc_xxx --content '{"text":"Hello"}' - -# Send to a direct message (pass open_id) +# Direct message (pass open_id) lark-cli im +messages-send --user-id ou_xxx --text "Hello" -# Send multi-line text while preserving formatting -lark-cli im +messages-send --chat-id oc_xxx --text $'Line 1\nLine 2\n indented line' - -# Send Markdown with an image (must pre-upload via images.create) -lark-cli im images create --data '{"image_type":"message"}' --file ./screenshot.png -# Use the returned image_key in the markdown content -lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Status\n\n![screenshot](img_v3_xxxx)\n\nDone.' - -# If you need exact post structure, send JSON directly +# Exact post structure with a title lark-cli im +messages-send --chat-id oc_xxx --msg-type post --content '{"zh_cn":{"title":"Title","content":[[{"tag":"text","text":"Body"}]]}}' -# Send a local image (uploaded automatically before sending) +# Markdown with an image (pre-upload first) +lark-cli im images create --data '{"image_type":"message"}' --file ./diagram.png # -> {"image_key":"img_v3_xxxx"} +lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Report\n\n![diagram](img_v3_xxxx)\n\nDone.' + +# Media (local files uploaded automatically; --video requires --video-cover) lark-cli im +messages-send --chat-id oc_xxx --image ./photo.png - -# Or send directly with an existing image_key -lark-cli im +messages-send --chat-id oc_xxx --image img_xxx - -# Send a local file (uploaded automatically before sending) lark-cli im +messages-send --chat-id oc_xxx --file ./report.pdf - -# Send a video (--video-cover is required as the cover) lark-cli im +messages-send --chat-id oc_xxx --video ./demo.mp4 --video-cover ./cover.png -lark-cli im +messages-send --chat-id oc_xxx --video ./demo.mp4 --video-cover img_xxx - -# Send audio lark-cli im +messages-send --chat-id oc_xxx --audio ./voice.opus -# Use an idempotency key (same key sends only once within 1 hour) -lark-cli im +messages-send --chat-id oc_xxx --text "Hello" --idempotency-key my-unique-id - -# Preview the request without executing it -lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Test\n\nhello' --dry-run +# Idempotency (same key sends only once within 1 hour) / preview without sending +lark-cli im +messages-send --chat-id oc_xxx --text "Hi" --idempotency-key my-id +lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Test\n\nhi' --dry-run ``` -## Media Input Rules +Run `lark-cli im +messages-send --help` for the full flag list and types. Load-bearing rules that `--help` may not make obvious: -- Media flags accept an existing key (`img_xxx` / `file_xxx`), an `http://` or `https://` URL, or a local file path. -- Local paths must be relative to the current working directory and stay within it after resolving `..` and symlinks. -- Absolute paths such as `/tmp/photo.png` are rejected. Run the command from the file's directory and pass `./photo.png`, or copy the file into the current directory first. +- **Media paths** accept an existing key (`img_xxx`/`file_xxx`), an `http(s)://` URL, or a **cwd-relative** local path. Absolute paths (e.g. `/tmp/x.png`) are rejected — run from the file's directory and pass `./x.png`. Upload and send use the same identity. +- **`--video` must be paired with `--video-cover`** (image key/URL/local path); `--video-cover` cannot be used alone. +- **`--msg-type`** is inferred from `--text`/`--markdown`/media flags; explicitly setting a conflicting type fails validation. -## Parameters - -| Parameter | Required | Description | -|------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `--chat-id ` | One of two | Group chat ID (`oc_xxx`) | -| `--user-id ` | One of two | User open_id (`ou_xxx`) for direct messages | -| `--text ` | One content option | Plain text message. Use when exact text and formatting preservation matter. Automatically wrapped as `{"text":"..."}` | -| `--markdown ` | One content option | Best default for lightweight formatted messages such as headings, lists, links, summaries, and reports. Internally converted to `post` JSON with Feishu-specific normalization | -| `--content ` | One content option | Exact message content JSON string; use this when you need full control over `msg_type` and payload. The JSON must match the effective `--msg-type` | -| `--image ` | One content option | Cwd-relative local image path, URL, or `image_key` (`img_xxx`). Local paths and URLs are uploaded automatically | -| `--file ` | One content option | Cwd-relative local file path, URL, or `file_key` (`file_xxx`). Local paths and URLs are uploaded automatically | -| `--video ` | One content option | Cwd-relative local video path, URL, or `file_key` (`file_xxx`). Local paths and URLs are uploaded automatically. **Must be paired with `--video-cover`** | -| `--video-cover ` | **Required with `--video`** | Cwd-relative local cover image path, URL, or `image_key` (`img_xxx`). Local paths and URLs are uploaded automatically | -| `--audio ` | One content option | Cwd-relative local audio path, URL, or `file_key` (`file_xxx`). Local paths and URLs are uploaded automatically | -| `--msg-type ` | No | Message type (default `text`). If you use `--text` / `--markdown` / media flags, the effective type is inferred automatically. Explicitly setting a conflicting `--msg-type` fails validation | -| `--idempotency-key ` | No | Idempotency key; the same key sends only one message within 1 hour | -| `--as ` | No | Identity type: `bot` or `user` (default `bot`) | -| `--dry-run` | No | Print the request only, do not execute it | - -> **Mutual exclusivity rule:** `--text`, `--markdown`, `--content`, and `--image`/`--file`/`--video`/`--audio` cannot be used together. Media flags are also mutually exclusive with each other. -> -> **Video cover rule:** `--video` **must** be accompanied by `--video-cover`. Omitting `--video-cover` when using `--video` will fail validation. `--video-cover` cannot be used without `--video`. - -## Common Mistakes - -- Choosing `--text` for headings, lists, links, summaries, or reports. Use `--markdown`. -- Choosing `--markdown` when you actually need exact plain text. If exact line breaks, spacing, logs, code, or literal Markdown characters matter, use `--text`, usually with `$'...'`. -- Assuming `--markdown` supports every Markdown feature. It is converted into a Feishu `post` payload and normalized first. -- Putting local image paths inside Markdown like `![x](./a.png)`. `--markdown` does not auto-upload those paths. -- **Using local file paths inside Markdown image syntax** (e.g. `![x](./a.png)`) with `--markdown`. Local paths are not auto-uploaded and will not render as an image. Pre-upload via `images.create` to get an `image_key` instead. -- Using `--content` without making the JSON match the effective `--msg-type`. -- Explicitly setting `--msg-type` to something that conflicts with `--text`, `--markdown`, or media flags. -- Mixing `--text`, `--markdown`, or `--content` with media flags in one command. - -## `content` Format Reference +## `content` Format Reference (for `--content`) | `msg_type` | Example `content` | -|----------|-------------| +|---|---| | `text` | `{"text":"Hello name"}` | | `post` | `{"zh_cn":{"title":"Title","content":[[{"tag":"text","text":"Body"}]]}}` | -| `image` | `{"image_key":"img_xxx"}` | -| `file` | `{"file_key":"file_xxx"}` | -| `audio` | `{"file_key":"file_xxx"}` | -| `media` | `{"file_key":"file_xxx","image_key":"img_xxx"}` (video; `image_key` is the cover from `--video-cover` — **required**) | -| `share_chat` | `{"chat_id":"oc_xxx"}` | -| `share_user` | `{"user_id":"ou_xxx"}` | -| `interactive` | Card JSON (see Feishu interactive card documentation) | +| `image` / `file` / `audio` | `{"image_key":"img_xxx"}` / `{"file_key":"file_xxx"}` / `{"file_key":"file_xxx"}` | +| `media` (video) | `{"file_key":"file_xxx","image_key":"img_xxx"}` (`image_key` is the **required** cover) | +| `share_chat` / `share_user` | `{"chat_id":"oc_xxx"}` / `{"user_id":"ou_xxx"}` | +| `interactive` (card) | Card JSON (see Feishu interactive card docs) | + +When using `--content`, you are responsible for making the JSON match the effective `msg_type`. + +## @Mention Format + +The `` syntax differs by message type; the shortcut normalizes mentions for `text` and `post` only — `interactive` cards are passed through verbatim. + +- **`text`** / inside a `post` `text`/`md` element: `name` (inner name optional); @all: ``. In `post` you may also use a node: `{"tag":"at","user_id":"ou_xxx"}` (`"all"` for everyone). +- **`interactive` (card)** — card-native syntax inside a `lark_md`/`markdown` element: ``, multiple ``, by email ``. ## Return Value ```json -{ - "message_id": "om_xxx", - "chat_id": "oc_xxx", - "create_time": "1234567890" -} +{"message_id": "om_xxx", "chat_id": "oc_xxx", "create_time": "1234567890"} ``` - -## @Mention Format - -The `` syntax differs by message type. The shortcut only normalizes mentions for `text` and `post`; `interactive` card content is passed through verbatim, so cards must use the card-native syntax below. - -### `text` - -- `name` — the inner text is the mentioned user's display name and is optional (`` also works) -- @all: `` - -### `post` - -- Inside a `text` or `md` element, the same inline form as `text` works: `name` -- Or use a dedicated `at` element node: `{"tag":"at","user_id":"ou_xxx"}` (use `"all"` to mention everyone) - -### `interactive` (card) - -Card content is **not** normalized — use the card-native `` syntax inside a `lark_md` / `markdown` element: - -- single user by open_id: `` -- multiple users: `` -- by email: `` - -## Notes - -- `--chat-id` and `--user-id` are mutually exclusive; you must provide exactly one -- `--content` must be valid JSON -- When using `--content`, you are responsible for making the JSON structure match the effective `msg_type` -- `--image`/`--file`/`--video`/`--audio` support existing keys, URLs, and cwd-relative local file paths; the shortcut uploads local paths and URLs first, then sends the message; both the upload and send steps use the same identity (UAT when `--as user`, TAT when `--as bot`) -- If the provided media value starts with `img_` or `file_`, it is treated as an existing key and used directly -- `--markdown` always sends `msg_type=post`, even if you do not explicitly set `--msg-type post` -- If you explicitly set `--msg-type` and it conflicts with the chosen content flag, validation fails -- When using `--video`, `--video-cover` is required as the video cover -- `--dry-run` uses placeholder image keys for remote Markdown images and placeholder media keys for local uploads -- Failures return an error code and message -- `--as user` uses a user access token (UAT) and requires the `im:message.send_as_user` and `im:message` scopes; the message is sent as the authorized end user -- `--as bot` uses a tenant access token (TAT) and requires the `im:message:send_as_bot` scope -- When sending as a bot, the app must already be in the target group or already have a direct-message relationship with the target user -- When using `--markdown` with images, pre-uploading via `images.create` to obtain an `image_key` is recommended for reliability; remote URLs may be auto-resolved at runtime, but if download/upload fails the image is removed with a warning; local paths are not supported