From c98a49f2a3e9099bb11bf72abfdb52ac568f5a2b Mon Sep 17 00:00:00 2001 From: YangJunzhou-01 Date: Wed, 20 May 2026 17:39:14 +0800 Subject: [PATCH] docs(im): clarify media key formats for message media flags (#991) * docs(im): clarify media path restrictions * docs(im): clarify file key formats for message file flags Change-Id: I329ca0db9e7a01b774846d522d1b2a64da74233c --------- Co-authored-by: mtsui-cmyk --- shortcuts/im/im_messages_reply.go | 10 ++--- shortcuts/im/im_messages_send.go | 10 ++--- shortcuts/im/validate_media_test.go | 36 +++++++++++++++++ .../references/lark-im-messages-reply.md | 40 +++++++++++-------- .../references/lark-im-messages-send.md | 40 +++++++++++-------- 5 files changed, 92 insertions(+), 44 deletions(-) diff --git a/shortcuts/im/im_messages_reply.go b/shortcuts/im/im_messages_reply.go index 806ee739c..31795dbd2 100644 --- a/shortcuts/im/im_messages_reply.go +++ b/shortcuts/im/im_messages_reply.go @@ -29,11 +29,11 @@ var ImMessagesReply = common.Shortcut{ {Name: "content", Desc: "(one of --content/--text/--markdown/--image/--file/--video/--audio required) message content JSON"}, {Name: "text", Desc: "plain text message (auto-wrapped as JSON)"}, {Name: "markdown", Desc: "markdown text (auto-wrapped as post format with style optimization; image URLs auto-resolved)"}, - {Name: "image", Desc: "image_key, local file path"}, - {Name: "file", Desc: "file_key, local file path"}, - {Name: "video", Desc: "video file_key, local file path; must be used together with --video-cover"}, - {Name: "video-cover", Desc: "video cover image_key, local file path; required when using --video"}, - {Name: "audio", Desc: "audio file_key, local file path"}, + {Name: "image", Desc: "image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"}, + {Name: "file", Desc: "file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"}, + {Name: "video", Desc: "video file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); must be used together with --video-cover"}, + {Name: "video-cover", Desc: "video cover image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); required when using --video"}, + {Name: "audio", Desc: "audio file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"}, {Name: "reply-in-thread", Type: "bool", Desc: "reply in thread (message appears in thread stream instead of main chat)"}, {Name: "idempotency-key", Desc: "idempotency key (prevents duplicate sends)"}, }, diff --git a/shortcuts/im/im_messages_send.go b/shortcuts/im/im_messages_send.go index efaa54852..4b8a30984 100644 --- a/shortcuts/im/im_messages_send.go +++ b/shortcuts/im/im_messages_send.go @@ -33,11 +33,11 @@ var ImMessagesSend = common.Shortcut{ {Name: "text", Desc: "plain text message (auto-wrapped as JSON)"}, {Name: "markdown", Desc: "markdown text (auto-wrapped as post format with style optimization; image URLs auto-resolved)"}, {Name: "idempotency-key", Desc: "idempotency key (prevents duplicate sends)"}, - {Name: "image", Desc: "image_key, local file path"}, - {Name: "file", Desc: "file_key, local file path"}, - {Name: "video", Desc: "video file_key, local file path; must be used together with --video-cover"}, - {Name: "video-cover", Desc: "video cover image_key, local file path; required when using --video"}, - {Name: "audio", Desc: "audio file_key, local file path"}, + {Name: "image", Desc: "image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"}, + {Name: "file", Desc: "file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"}, + {Name: "video", Desc: "video file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); must be used together with --video-cover"}, + {Name: "video-cover", Desc: "video cover image key (img_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected); required when using --video"}, + {Name: "audio", Desc: "audio file key (file_xxx), URL, or cwd-relative local path (absolute paths and .. are rejected)"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { chatFlag := runtime.Str("chat-id") diff --git a/shortcuts/im/validate_media_test.go b/shortcuts/im/validate_media_test.go index b6c63efdc..94ec17f9b 100644 --- a/shortcuts/im/validate_media_test.go +++ b/shortcuts/im/validate_media_test.go @@ -6,9 +6,11 @@ package im import ( "os" "path/filepath" + "strings" "testing" "github.com/larksuite/cli/internal/vfs/localfileio" + "github.com/larksuite/cli/shortcuts/common" ) func TestValidateMediaFlagPath(t *testing.T) { @@ -49,3 +51,37 @@ func TestValidateMediaFlagPath(t *testing.T) { }) } } + +func TestIMMediaFlagDescriptionsDocumentPathRestrictions(t *testing.T) { + shortcuts := []struct { + name string + flags []common.Flag + }{ + {name: "messages-send", flags: ImMessagesSend.Flags}, + {name: "messages-reply", flags: ImMessagesReply.Flags}, + } + mediaFlags := []string{"image", "file", "video", "video-cover", "audio"} + for _, sc := range shortcuts { + for _, flagName := range mediaFlags { + t.Run(sc.name+"/"+flagName, func(t *testing.T) { + desc := findFlagDesc(t, sc.flags, flagName) + for _, want := range []string{"URL", "cwd-relative local path", "absolute paths", ".. are rejected"} { + if !strings.Contains(desc, want) { + t.Fatalf("%s --%s description = %q, want it to mention %q", sc.name, flagName, desc, want) + } + } + }) + } + } +} + +func findFlagDesc(t *testing.T, flags []common.Flag, name string) string { + t.Helper() + for _, flag := range flags { + if flag.Name == name { + return flag.Desc + } + } + t.Fatalf("flag %q not found", name) + return "" +} diff --git a/skills/lark-im/references/lark-im-messages-reply.md b/skills/lark-im/references/lark-im-messages-reply.md index cc73a5993..b6106150c 100644 --- a/skills/lark-im/references/lark-im-messages-reply.md +++ b/skills/lark-im/references/lark-im-messages-reply.md @@ -27,7 +27,7 @@ When using `--as user`, the reply is sent as the authorized end user and require | Reply with plain text exactly as written | `--text` | Wrapped directly to `{"text":"..."}` | | Reply with simple Markdown and accept conversion | `--markdown` | Automatically converted to `post` JSON | | Precisely control the reply payload | `--content` | You provide the exact JSON | -| Reply with media | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads local files automatically | +| Reply with media | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads URLs, or cwd-relative local files automatically | ### `--text` vs `--markdown` @@ -138,24 +138,30 @@ lark-cli im +messages-reply --message-id om_xxx --text "Received" --idempotency- lark-cli im +messages-reply --message-id om_xxx --markdown $'## Test\n\nhello' --dry-run ``` +## 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. + ## Parameters -| Parameter | Required | Description | -|------|------|------| -| `--message-id ` | Yes | ID of the message being replied to (`om_xxx`) | +| Parameter | Required | Description | +|------|------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--message-id ` | Yes | ID of the message being replied to (`om_xxx`) | | `--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 | -| `--content ` | One content option | Exact reply content as JSON. The JSON must match the effective `--msg-type` | -| `--text ` | One content option | Plain text reply. Best default when you need exact text and formatting preservation | -| `--markdown ` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization | -| `--image ` | One content option | Local image path or `image_key` (`img_xxx`) | -| `--file ` | One content option | Local file path or `file_key` (`file_xxx`) | -| `--video ` | One content option | Local video path or `file_key`; **must be used together with `--video-cover`** | -| `--video-cover ` | **Required with `--video`** | Video cover image path or `image_key` (`img_xxx`) | -| `--audio ` | One content option | Local audio path or `file_key` | -| `--reply-in-thread` | No | Reply inside the thread. The reply appears in the target message's thread instead of the main chat stream | -| `--idempotency-key ` | No | Idempotency key; the same key sends only one reply within 1 hour | -| `--as ` | No | Identity type: `bot` or `user` (default `bot`) | -| `--dry-run` | No | Print the request only, do not execute it | +| `--content ` | One content option | Exact reply content as JSON. The JSON must match the effective `--msg-type` | +| `--text ` | One content option | Plain text reply. Best default when you need exact text and formatting preservation | +| `--markdown ` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization | +| `--image ` | One content option | Cwd-relative local image path, URL, or `image_key` (`img_xxx`) | +| `--file ` | One content option | Cwd-relative local file path, URL, or `file_key` (`file_xxx`) | +| `--video ` | One content option | Cwd-relative local video path, URL, or `file_key` (`file_xxx`); **must be used together with `--video-cover`** | +| `--video-cover ` | **Required with `--video`** | Cwd-relative local cover image path, URL, or `image_key` (`img_xxx`) | +| `--audio ` | One content option | Cwd-relative local audio path, URL, or `file_key` (`file_xxx`) | +| `--reply-in-thread` | No | Reply inside the thread. The reply appears in the target message's thread instead of the main chat stream | +| `--idempotency-key ` | No | Idempotency key; the same key sends only one reply 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. > @@ -211,7 +217,7 @@ The reply appears in the target message's thread and does not show up in the mai - When using `--content`, you are responsible for making the JSON structure match the effective `msg_type` - `--reply-in-thread` adds `reply_in_thread=true` to the API request - `--reply-in-thread` is mainly meaningful in chats that support thread replies -- `--image`/`--file`/`--video`/`--audio`/`--video-cover` support local file paths; the shortcut uploads first and then sends the reply; both the upload and send steps use the same identity (UAT when `--as user`, TAT when `--as bot`) +- `--image`/`--file`/`--video`/`--audio`/`--video-cover` support existing keys, URLs, and cwd-relative local file paths; the shortcut uploads local paths and URLs first, then sends the reply; 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` - If you explicitly set `--msg-type` and it conflicts with the chosen content flag, validation fails diff --git a/skills/lark-im/references/lark-im-messages-send.md b/skills/lark-im/references/lark-im-messages-send.md index a328063a3..ce34a533d 100644 --- a/skills/lark-im/references/lark-im-messages-send.md +++ b/skills/lark-im/references/lark-im-messages-send.md @@ -27,7 +27,7 @@ When using `--as user`, the message is sent as the authorized end user and requi | Send plain text exactly as written | `--text` | Wrapped directly to `{"text":"..."}`; no Markdown conversion | | Send simple Markdown and accept Feishu-style rendering | `--markdown` | Automatically converted to `post` JSON | | 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 local files automatically | +| Send image / file / video / audio | `--image` / `--file` / `--video` / `--audio` | Shortcut uploads URLs, or cwd-relative local files automatically | ### `--text` vs `--markdown` @@ -144,24 +144,30 @@ lark-cli im +messages-send --chat-id oc_xxx --text "Hello" --idempotency-key my- lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Test\n\nhello' --dry-run ``` +## 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. + ## 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. Best default for exact text and preserved formatting. Automatically wrapped as `{"text":"..."}` | -| `--markdown ` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization; not full Markdown passthrough | -| `--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 | Local image path or `image_key` (`img_xxx`). Local paths are uploaded automatically | -| `--file ` | One content option | Local file path or `file_key` (`file_xxx`). Local paths are uploaded automatically | -| `--video ` | One content option | Local video path or `file_key`. Local paths are uploaded automatically. **Must be paired with `--video-cover`** | -| `--video-cover ` | **Required with `--video`** | Video cover image path or `image_key` (`img_xxx`). Local paths are uploaded automatically | -| `--audio ` | One content option | Local audio path or `file_key`. Local paths are uploaded automatically | +| 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. Best default for exact text and preserved formatting. Automatically wrapped as `{"text":"..."}` | +| `--markdown ` | One content option | Convenience Markdown input. Internally converted to `post` JSON with Feishu-specific normalization; not full Markdown passthrough | +| `--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 | +| `--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. > @@ -211,7 +217,7 @@ lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Test\n\nhello' --dry - `--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 local file paths; the shortcut uploads first and then sends the message; both the upload and send steps use the same identity (UAT when `--as user`, TAT when `--as bot`) +- `--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