Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions shortcuts/common/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,20 @@ func (ctx *RuntimeContext) DoAPI(req *larkcore.ApiReq, opts ...larkcore.RequestO
return ac.DoSDKRequest(ctx.ctx, req, ctx.As(), opts...)
}

// DoAPIAsBot executes a raw Lark SDK request using bot identity (tenant access token),
// regardless of the current --as flag. Use this for bot-only APIs (e.g. image/file upload)
// that must be called with TAT even when the surrounding shortcut runs as user.
func (ctx *RuntimeContext) DoAPIAsBot(req *larkcore.ApiReq, opts ...larkcore.RequestOptionFunc) (*larkcore.ApiResp, error) {
ac, err := ctx.getAPIClient()
if err != nil {
return nil, err
}
if optFn := cmdutil.ShortcutHeaderOpts(ctx.ctx); optFn != nil {
opts = append(opts, optFn)
}
return ac.DoSDKRequest(ctx.ctx, req, core.AsBot, opts...)
}

type cancelOnCloseReadCloser struct {
io.ReadCloser
cancel context.CancelFunc
Expand Down
4 changes: 2 additions & 2 deletions shortcuts/im/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,7 +876,7 @@ func uploadImageToIM(ctx context.Context, runtime *common.RuntimeContext, filePa
fd.AddField("image_type", imageType)
fd.AddFile("image", f)

apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
apiResp, err := runtime.DoAPIAsBot(&larkcore.ApiReq{
HttpMethod: http.MethodPost,
ApiPath: "/open-apis/im/v1/images",
Body: fd,
Expand Down Expand Up @@ -922,7 +922,7 @@ func uploadFileToIM(ctx context.Context, runtime *common.RuntimeContext, filePat
}
fd.AddFile("file", f)

apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
apiResp, err := runtime.DoAPIAsBot(&larkcore.ApiReq{
HttpMethod: http.MethodPost,
ApiPath: "/open-apis/im/v1/files",
Body: fd,
Expand Down
6 changes: 4 additions & 2 deletions shortcuts/im/im_messages_reply.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import (
var ImMessagesReply = common.Shortcut{
Service: "im",
Command: "+messages-reply",
Description: "Reply to a message (supports thread replies) with bot identity; bot-only; supports text/markdown/post/media replies, reply-in-thread, idempotency key",
Description: "Reply to a message (supports thread replies); user/bot; supports text/markdown/post/media replies, reply-in-thread, idempotency key",
Risk: "write",
Scopes: []string{"im:message:send_as_bot"},
AuthTypes: []string{"bot"},
UserScopes: []string{"im:message.send_as_user"},
BotScopes: []string{"im:message:send_as_bot"},
AuthTypes: []string{"bot", "user"},
Flags: []common.Flag{
{Name: "message-id", Desc: "message ID (om_xxx)", Required: true},
{Name: "msg-type", Default: "text", Desc: "message type for --content JSON; when using --text/--markdown/--image/--file/--video/--audio, the effective type is inferred automatically", Enum: []string{"text", "post", "image", "file", "audio", "media", "interactive", "share_chat", "share_user"}},
Expand Down
6 changes: 4 additions & 2 deletions shortcuts/im/im_messages_send.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import (
var ImMessagesSend = common.Shortcut{
Service: "im",
Command: "+messages-send",
Description: "Send a message to a chat or direct message with bot identity; bot-only; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key",
Description: "Send a message to a chat or direct message; user/bot; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key",
Risk: "write",
Scopes: []string{"im:message:send_as_bot"},
AuthTypes: []string{"bot"},
UserScopes: []string{"im:message.send_as_user"},
BotScopes: []string{"im:message:send_as_bot"},
Comment thread
greptile-apps[bot] marked this conversation as resolved.
AuthTypes: []string{"bot", "user"},
Flags: []common.Flag{
{Name: "chat-id", Desc: "(required, mutually exclusive with --user-id) chat ID (oc_xxx)"},
{Name: "user-id", Desc: "(required, mutually exclusive with --chat-id) user open_id (ou_xxx)"},
Expand Down
4 changes: 2 additions & 2 deletions skills/lark-im/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ Shortcut 是对常用操作的高级封装(`lark-cli im +<verb> [flags]`)。
| [`+chat-search`](references/lark-im-chat-search.md) | Search visible group chats by keyword and/or member open_ids (e.g. look up chat_id by group name); user/bot; supports member/type filters, sorting, and pagination |
| [`+chat-update`](references/lark-im-chat-update.md) | Update group chat name or description; user/bot; updates a chat's name or description |
| [`+messages-mget`](references/lark-im-messages-mget.md) | Batch get messages by IDs; user/bot; fetches up to 50 om_ message IDs, formats sender names, expands thread replies |
| [`+messages-reply`](references/lark-im-messages-reply.md) | Reply to a message (supports thread replies) with bot identity; bot-only; supports text/markdown/post/media replies, reply-in-thread, idempotency key |
| [`+messages-reply`](references/lark-im-messages-reply.md) | Reply to a message (supports thread replies); user/bot; supports text/markdown/post/media replies, reply-in-thread, idempotency key |
| [`+messages-resources-download`](references/lark-im-messages-resources-download.md) | Download images/files from a message; user/bot; downloads image/file resources by message-id and file-key to a safe relative output path |
| [`+messages-search`](references/lark-im-messages-search.md) | Search messages across chats (supports keyword, sender, time range filters) with user identity; user-only; filters by chat/sender/attachment/time, supports auto-pagination via `--page-all` / `--page-limit`, enriches results via batched mget and chats batch_query |
| [`+messages-send`](references/lark-im-messages-send.md) | Send a message to a chat or direct message with bot identity; bot-only; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key |
| [`+messages-send`](references/lark-im-messages-send.md) | Send a message to a chat or direct message; user/bot; sends to chat-id or user-id with text/markdown/post/media, supports idempotency key |
| [`+threads-messages-list`](references/lark-im-threads-messages-list.md) | List messages in a thread; user/bot; accepts om_/omt_ input, resolves message IDs to thread_id, supports sort/pagination |

## API Resources
Expand Down
13 changes: 8 additions & 5 deletions skills/lark-im/references/lark-im-messages-reply.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Prerequisite:** Read [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) first to understand authentication, global parameters, and safety rules.

Reply to a specific message. Only supports bot identity. Also supports thread replies.
Reply to a specific message. Supports both user identity (`--as user`) and bot identity (`--as bot`). Also supports thread replies.

This skill maps to the shortcut: `lark-cli im +messages-reply` (internally calls `POST /open-apis/im/v1/messages/:message_id/reply`).

Expand All @@ -12,12 +12,14 @@ Replies sent by this tool are visible to other people. Before calling it, you **

1. Which message to reply to
2. The reply content
3. Which identity to use (bot only)
3. Which identity to use (user or bot)

**Do not** send a reply without explicit user approval.

When using `--as bot`, the reply 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 reply is sent as the authorized end user and requires the `im:message.send_as_user` scope.

## Choose The Right Content Flag

| Need | Recommended flag | Why |
Expand Down Expand Up @@ -152,7 +154,7 @@ lark-cli im +messages-reply --message-id om_xxx --markdown $'## Test\n\nhello' -
| `--audio <path\|key>` | 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 <key>` | No | Idempotency key; the same key sends only one reply within 1 hour |
| `--as <identity>` | No | Identity type: `bot` only |
| `--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.
Expand Down Expand Up @@ -209,11 +211,12 @@ 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
- `--image`/`--file`/`--video`/`--audio`/`--video-cover` support local file paths; the shortcut uploads first and then sends the reply; file/image upload is bot-only, so when using `--as user`, the upload step is automatically performed with bot identity, and only the final send uses user identity
- 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
- 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 error codes and messages
- `--as bot` uses a tenant access token (TAT), and requires the `im:message:send_as_bot` scope
- `--as user` uses a user access token (UAT) and requires the `im:message.send_as_user` scope; the reply is sent as the authorized end user
- `--as bot` uses a tenant access token (TAT), and requires the `im:message:send_as_bot` scope
13 changes: 8 additions & 5 deletions skills/lark-im/references/lark-im-messages-send.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **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. Only supports bot identity.
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`).

Expand All @@ -12,12 +12,14 @@ Messages sent by this tool are visible to other people. Before calling it, you *

1. The recipient (which person or which group)
2. The message content
3. The sending identity (bot only)
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` scope.

## Choose The Right Content Flag

| Need | Recommended flag | Why |
Expand Down Expand Up @@ -158,7 +160,7 @@ lark-cli im +messages-send --chat-id oc_xxx --markdown $'## Test\n\nhello' --dry
| `--audio <path\|key>` | One content option | Local audio path or `file_key`. Local paths are uploaded automatically |
| `--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; the same key sends only one message within 1 hour |
| `--as <identity>` | No | Identity type: `bot` only |
| `--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.
Expand Down Expand Up @@ -209,12 +211,13 @@ 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
- `--image`/`--file`/`--video`/`--audio` support local file paths; the shortcut uploads first and then sends the message; file/image upload is bot-only, so when using `--as user`, the upload step is automatically performed with bot identity, and only the final send uses user identity
- 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` scope; 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 sending as a bot, the app must already be in the target group or already have a direct-message relationship with the target user
Loading