Files
larksuite-cli/skill-template/domains/im.md
sammi-bytedance 30327abacb feat(im): enrich messages with reactions + output update_time (#1095)
- Pull messages now auto-call im.reactions.batch_query and attach a
  reactions block (counts + details) to each message. Stops AI from
  misjudging "user already reacted" as "no response yet" and
  re-sending duplicate reactions. Server caps queries[] at 20 per
  call, so messages are split into batches of size <= 20.
- Edited messages additionally surface update_time. The server echoes
  update_time == create_time for unedited messages too, so the field
  is only emitted when updated == true; otherwise every message
  output would look "edited". The value is read via an explicit
  string assertion + TrimSpace so empty strings are filtered properly
  (the previous `v != ""` was a no-op for non-string types).
- All four message-pulling shortcuts (+messages-mget,
  +chat-messages-list, +messages-search, +threads-messages-list) get
  a --no-reactions opt-out flag for callers that want to skip the
  extra round-trip.
- Each shortcut declares im:message.reactions:read on its
  UserScopes/BotScopes (or Scopes for the user-only search command) so
  the auth flow covers the new dependency.
- Each shortcut's --dry-run output now lists the
  reactions/batch_query call (or omits it when --no-reactions is set),
  so callers can audit the full set of API calls before execution.
- Warnings go through runtime.IO().ErrOut (forbidigo lint requires
  IOStreams over os.Stderr in shortcut code).
- Duplicate message_id inputs (e.g. mget --message-ids om_a,om_a)
  attach the reactions block to every entry while still querying the
  API only once per distinct id.
- EnrichReactions walks msg["thread_replies"] recursively, and mget/
  chat-messages-list call it after ExpandThreadReplies, so replies
  receive reactions in the same batched call as their parent message.
- When the batch_query call fails or returns per-message failures,
  the affected messages get reactions_error=true (mirroring the
  thread_replies_error flag from thread.go) so consumers can
  distinguish "fetch failed" from "no reactions exist" by reading
  stdout alone, without depending on the stderr warning channel.
- lark-im skill docs: the default-enrichment contract lives in a
  standalone references/lark-im-message-enrichment.md so the generated
  SKILL.md can't strand it on regeneration. The four read references
  and the raw reactions API reference link to it, and the template
  source skill-template/domains/im.md carries a durable pointer.

Change-Id: Ia9ea74b11945644262bb25c6503fb9b2003c6c98
2026-05-27 18:06:36 +08:00

3.3 KiB

Core Concepts

  • Message: A single message in a chat, identified by message_id (om_xxx). Supports types: text, post, image, file, audio, video, sticker, interactive (card), share_chat, share_user, merge_forward, etc.
  • Chat: A group chat or P2P conversation, identified by chat_id (oc_xxx).
  • Thread: A reply thread under a message, identified by thread_id (om_xxx or omt_xxx).
  • Reaction: An emoji reaction on a message.
  • Flag: A bookmark on a message or thread.

Resource Relationships

Chat (oc_xxx)
├── Message (om_xxx)
│   ├── Thread (reply thread)
│   ├── Reaction (emoji)
│   └── Resource (image / file / video / audio)
└── Member (user / bot)

Important Notes

Identity and Token Mapping

  • --as user means user identity and uses user_access_token. Calls run as the authorized end user, so permissions depend on both the app scopes and that user's own access to the target chat/message/resource.
  • --as bot means bot identity and uses tenant_access_token. Calls run as the app bot, so behavior depends on the bot's membership, app visibility, availability range, and bot-specific scopes.
  • If an IM API says it supports both user and bot, the token type changes who the operator is. The same API can succeed with one identity and fail with the other because owner/admin status, chat membership, tenant boundary, or app availability are checked against the current caller.

Sender Name Resolution with Bot Identity

When using bot identity (--as bot) to fetch messages (e.g. +chat-messages-list, +threads-messages-list, +messages-mget), sender names may not be resolved (shown as open_id instead of display name). This happens when the bot cannot access the user's contact info.

Root cause: The bot's app visibility settings do not include the message sender, so the contact API returns no name.

Solution: Check the app's visibility settings in the Lark Developer Console — ensure the app's visible range covers the users whose names need to be resolved. Alternatively, use --as user to fetch messages with user identity, which typically has broader contact access.

Default message enrichment (reactions / update_time)

The four message-pulling shortcuts (+messages-mget, +chat-messages-list, +messages-search, +threads-messages-list) automatically attach a reactions block and (for edited messages) update_time to each returned message — no separate im.reactions.batch_query call is needed. Pass --no-reactions to opt out. For the full contract (output shape, the im:message.reactions:read scope requirement, and the "missing field ≠ fetch failure" data rules), read references/lark-im-message-enrichment.md.

Card Messages (Interactive)

Card messages (interactive type) are not yet supported for compact conversion in event subscriptions. The raw event data will be returned instead, with a hint printed to stderr.

Flag Types

Flags support two layers:

  • Message-layer flag: (ItemTypeDefault, FlagTypeMessage) — regular message bookmark
  • Feed-layer flag: (ItemTypeThread/ItemTypeMsgThread, FlagTypeFeed) — thread as feed-layer bookmark

Item types for feed-layer flags:

  • ItemTypeThread (4) = thread in a topic-style chat
  • ItemTypeMsgThread (11) = thread in a regular chat