Files
larksuite__cli/shortcuts/im/im_messages_mget.go
sammi-bytedance 7675185f9d feat(im): show bot sender display names when reading messages (#1829)
Read the server-provided sender_name for both user and bot senders (previously
only users resolved) so message-read commands display bot names instead of raw
ids. The CLI opts into server-side name filling by sending with_sender_name=true
on chat-messages-list, threads-messages-list, messages-mget and messages-search,
as well as on the inline fetches that render nested senders: merge_forward
sub-messages and auto-expanded thread replies. Without it those nested-only
senders carry no sender_name and, with no fallback, render as raw ids.

Names come solely from the server (single source of truth): there is no contact
or mention fallback, so the contact scope is dropped from these four commands and
the contact/mention resolution code is removed. A sender the server does not name
falls back to its id; system messages show no name. The resolved name is exposed
in the existing `name` field (backward compatible); the duplicate raw
`sender_name` is stripped while the full `sender_i18n_names` map and `open_bot_id`
are preserved for consumers. No new permission scope is required. Updates the
lark-im skill docs.
2026-07-13 19:39:03 +08:00

130 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package im
import (
"context"
"fmt"
"io"
"net/http"
"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/shortcuts/common"
convertlib "github.com/larksuite/cli/shortcuts/im/convert_lib"
)
const maxMGetMessageIDs = 50
var ImMessagesMGet = common.Shortcut{
Service: "im",
Command: "+messages-mget",
Description: "Batch get messages by IDs; user/bot; fetches up to 50 om_ message IDs, formats sender names, expands thread replies",
Risk: "read",
Scopes: []string{"im:message:readonly"},
UserScopes: []string{"im:message.group_msg:get_as_user", "im:message.p2p_msg:get_as_user", "im:message.reactions:read"},
BotScopes: []string{"im:message.group_msg", "im:message.p2p_msg:readonly", "im:message.reactions:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: []common.Flag{
{Name: "message-ids", Desc: "message IDs, comma-separated (om_xxx,om_yyy)", Required: true},
{Name: "no-reactions", Type: "bool", Desc: "skip auto-fetching reactions for each message (default: enrichment enabled)"},
downloadResourcesFlag,
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
ids := common.SplitCSV(runtime.Str("message-ids"))
d := common.NewDryRunAPI().GET(buildMGetURL(ids))
if !runtime.Bool("no-reactions") {
d = d.POST("/open-apis/im/v1/messages/reactions/batch_query").
Desc("Reaction enrichment: queries returned messages in batches of up to 20 to attach the reactions block (operator, action_time, counts). Pass --no-reactions to skip.")
}
if runtime.Bool("download-resources") {
d = d.Desc(downloadResourcesDryRunDesc)
}
return d
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
ids := common.SplitCSV(runtime.Str("message-ids"))
if len(ids) == 0 {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--message-ids is required (comma-separated om_xxx)").WithParam("--message-ids")
}
if len(ids) > maxMGetMessageIDs {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--message-ids supports at most %d IDs per request (got %d)", maxMGetMessageIDs, len(ids)).WithParam("--message-ids")
}
for _, id := range ids {
if _, err := validateMessageID(id); err != nil {
return err
}
}
return nil
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
ids := common.SplitCSV(runtime.Str("message-ids"))
mgetURL := buildMGetURL(ids)
data, err := runtime.DoAPIJSONTyped(http.MethodGet, mgetURL, nil, nil)
if err != nil {
return err
}
rawItems, _ := data["items"].([]interface{})
nameCache := make(map[string]string)
// Pre-fetch merge_forward sub-messages concurrently before the per-item
// conversion loop, so N merge_forwards in the input don't serialize
// into N × ~1s of stall inside FormatMessageItem. Passing nameCache
// also pre-resolves every sub-item's sender open_id in one batched
// contact API call.
mergePrefetch := convertlib.PrefetchMergeForwardSubItems(runtime, rawItems, nameCache)
downloadResources := runtime.Bool("download-resources")
messages := make([]map[string]interface{}, 0, len(rawItems))
for _, item := range rawItems {
m, _ := item.(map[string]interface{})
messages = append(messages, convertlib.FormatMessageItemWithMergePrefetchOpts(m, runtime, nameCache, mergePrefetch, downloadResources))
}
convertlib.ResolveSenderNames(runtime, messages, nameCache)
convertlib.AttachSenderNames(messages, nameCache)
convertlib.ExpandThreadRepliesWithResources(runtime, messages, nameCache, convertlib.ThreadRepliesPerThread, convertlib.ThreadRepliesTotalLimit, downloadResources)
if !runtime.Bool("no-reactions") {
convertlib.EnrichReactions(runtime, messages)
}
if downloadResources {
enrichMessageResourceDownloads(runtime, messages)
}
outData := map[string]interface{}{
"messages": messages,
"total": len(messages),
}
runtime.OutFormat(outData, nil, func(w io.Writer) {
if len(messages) == 0 {
fmt.Fprintln(w, "No messages found.")
return
}
var rows []map[string]interface{}
for _, msg := range messages {
row := map[string]interface{}{
"message_id": msg["message_id"],
"time": msg["create_time"],
"type": msg["msg_type"],
}
if sender, ok := msg["sender"].(map[string]interface{}); ok {
if disp := senderDisplay(sender); disp != "" {
row["sender"] = disp
}
}
if content, _ := msg["content"].(string); content != "" {
row["content"] = convertlib.TruncateContent(content, 40)
}
rows = append(rows, row)
}
output.PrintTable(w, rows)
fmt.Fprintf(w, "\n%d message(s)\ntip: use --format json to view full message content\n", len(messages))
})
return nil
},
}