280 lines
18 KiB
Markdown
280 lines
18 KiB
Markdown
# im +messages-send
|
|
|
|
> **Prerequisite:** Read [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) first to understand 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`).
|
|
|
|
## Safety Constraints
|
|
|
|
Messages sent by this tool are visible to other people. Before calling it, you **must** confirm with the user:
|
|
|
|
1. The recipient (which person or which group)
|
|
2. The message content
|
|
3. The sending identity (user or bot)
|
|
|
|
**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.
|
|
|
|
## Choose The Right Content Flag
|
|
|
|
### Default Selection Rule For Agents
|
|
|
|
- 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.
|
|
|
|
| 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 |
|
|
|
|
### `--text` vs `--markdown`
|
|
|
|
- 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.
|
|
|
|
## What `--markdown` Really Does
|
|
|
|
`--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 `` 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 `` 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 `` for predictable results. Remote URLs may work but are not guaranteed.
|
|
|
|
**Steps:**
|
|
|
|
```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\n\nSee above for details.'
|
|
```
|
|
|
|
## 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
|
|
lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Update\n\n- item 1\n- item 2'
|
|
|
|
# Send a plain one-line message
|
|
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)
|
|
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\n\nDone.'
|
|
|
|
# If you need exact post structure, send JSON directly
|
|
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)
|
|
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 a voice message
|
|
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
|
|
|
|
# ===== Interactive Card =====
|
|
# 🚫 STOP — before constructing ANY interactive card JSON, you MUST read
|
|
# card/lark-im-card-create.md and follow its workflow. Do NOT
|
|
# hand-write or copy a card payload from the examples below. The JSON passed
|
|
# to --content must be the OUTPUT of that workflow. This is non-negotiable.
|
|
|
|
# Once the workflow has produced the card JSON, send it:
|
|
lark-cli im +messages-send --chat-id oc_xxx --msg-type interactive --content '<card_json_from_workflow>'
|
|
```
|
|
|
|
## Media Input Rules
|
|
|
|
- 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.
|
|
- `--audio` sends a voice message and accepts only Opus audio (`.opus` or Ogg Opus `.ogg`) for local paths and URLs. For `mp3`, `wav`, or other non-Opus audio, convert to `.opus` before using `--audio`, or use `--file` to send the original audio as an attachment.
|
|
|
|
## Parameters
|
|
|
|
| Parameter | Required | Description |
|
|
|------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
| `--chat-id <id>` | One of two | Group chat ID (`oc_xxx`) |
|
|
| `--user-id <id>` | One of two | User open_id (`ou_xxx`) for direct messages |
|
|
| `--text <string>` | One content option | Plain text message. Use when exact text and formatting preservation matter. Automatically wrapped as `{"text":"..."}` |
|
|
| `--markdown <string>` | 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 <json>` | 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 <path\|url\|key>` | One content option | Cwd-relative local image path, URL, or `image_key` (`img_xxx`). Local paths and URLs are uploaded automatically |
|
|
| `--file <path\|url\|key>` | One content option | Cwd-relative local file path, URL, or `file_key` (`file_xxx`). Local paths and URLs are uploaded automatically |
|
|
| `--video <path\|url\|key>` | 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 <path\|url\|key>` | **Required with `--video`** | Cwd-relative local cover image path, URL, or `image_key` (`img_xxx`). Local paths and URLs are uploaded automatically |
|
|
| `--audio <path\|url\|key>` | One content option | Voice-message audio key, URL, or cwd-relative local path. Local paths and URLs must be Opus (`.opus` or Ogg Opus `.ogg`) |
|
|
| `--msg-type <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 <key>` | No | Idempotency key, max 50 characters; the same key sends only one message within 1 hour |
|
|
| `--as <identity>` | 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 ``. `--markdown` does not auto-upload those paths.
|
|
- **Using local file paths inside Markdown image syntax** (e.g. ``) 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
|
|
|
|
| `msg_type` | Example `content` |
|
|
|----------|-------------|
|
|
| `text` | `{"text":"Hello <at user_id=\"ou_xxx\">name</at>"}` |
|
|
| `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 — **MUST** be produced by the [`card/lark-im-card-create.md`](card/lark-im-card-create.md) workflow. Read it before writing any card; never hand-craft the JSON here |
|
|
|
|
> **`post` vs `interactive`:** `post` is a static rich-text message (title, paragraphs, @mentions, links, inline images) — content is fixed once sent. `interactive` is a card with structured layout and UI components (buttons, forms, selects, date pickers, charts) — content can be updated after sending and supports user-action callbacks. Use `post` for read-only content; use `interactive` when the message needs user interaction or dynamic updates.
|
|
|
|
`interactive` cards support callback events (`card.action.trigger`) — see [`lark-im-card-action-reply.md`](lark-im-card-action-reply.md).
|
|
|
|
## Return Value
|
|
|
|
```json
|
|
{
|
|
"message_id": "om_xxx",
|
|
"chat_id": "oc_xxx",
|
|
"create_time": "1234567890"
|
|
}
|
|
```
|
|
|
|
## @Mention Format
|
|
|
|
The `<at>` 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`
|
|
|
|
- `<at user_id="ou_xxx">name</at>` — the inner text is the mentioned user's display name and is optional (`<at user_id="ou_xxx"></at>` also works)
|
|
- @all: `<at user_id="all"></at>`
|
|
|
|
### `post`
|
|
|
|
- Inside a `text` or `md` element, the same inline form as `text` works: `<at user_id="ou_xxx">name</at>`
|
|
- 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 `<at>` syntax inside a `lark_md` / `markdown` element:
|
|
|
|
- single user by open_id: `<at id=ou_xxx></at>`
|
|
- multiple users: `<at ids=ou_xxx1,ou_xxx2></at>`
|
|
- by email: `<at email=user@example.com></at>`
|
|
|
|
## 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
|
|
- **Interactive cards are gated:** you MUST read and follow the [`card/lark-im-card-create.md`](card/lark-im-card-create.md) workflow to produce the card JSON *before* sending. Do not hand-write or copy a card payload — the JSON given to `--msg-type interactive --content` must be the workflow's output. This applies every time, with no exception
|