Skip to content

feat: support message receive event card format#1480

Merged
YangJunzhou-01 merged 1 commit into
larksuite:mainfrom
91-enjoy:feat/event_card_format
Jun 18, 2026
Merged

feat: support message receive event card format#1480
YangJunzhou-01 merged 1 commit into
larksuite:mainfrom
91-enjoy:feat/event_card_format

Conversation

@91-enjoy

@91-enjoy 91-enjoy commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Previously, im.message.receive events with message_type: interactive surfaced the raw JSON
payload as content, requiring callers to manually parse the card schema. This PR introduces a
user_dsl renderer (ConvertInteractiveEventContent) that converts interactive card content into
structured human-readable text — consistent with how text, post, image, and other message
types are already handled.

The output format is <card title="..." subtitle="...">...</card>, with each card element type
serialised to a readable representation (markdown body, button links, table rows, chart summaries,
etc.).

Changes

events/im/message_receive.go

  • Route message_type == "interactive" through ConvertInteractiveEventContent instead of
    returning raw JSON
  • Simplify Content field description now that the interactive special-case is handled upstream

shortcuts/im/convert_lib/card_userdsl.go (new)

  • ConvertInteractiveEventContent(rawContent, mentions) — entry point; extracts user_dsl string
    from the event content envelope and delegates to the converter; falls back to
    "[interactive card]" on malformed input

  • userDslConverter — supports both card schema versions:

    • user-1.ts (no schema field): reads i18n_header.zh_cn or direct header, elements at
      root
    • user-2.ts (schema: "2.0"): reads header at root, body.elements
  • Handles 30+ element tags:

    Category Tags
    Text plain_text, lark_md, markdown
    Layout div (text + fields + extra), column_set, column, hr, br
    Media img / image, img_combination, audio, video
    Actions button (URL / disabled / confirm), actions / action, overflow
    Inputs select_static, multi_select_static, select_person, multi_select_person, input, date_picker, picker_date, picker_time, picker_datetime, checker
    Rich table, chart, form, collapsible_panel, interactive_container
    People person, person_list, avatar
    Other note, text_tag, select_img, repeat, fallback_text
  • resolveAtMentions — replaces <at ...> tags in markdown content with @name(open_id);
    supports both quoted (mention_key="k") and unquoted (mention_key=k) attribute formats to
    match real Lark event payloads, plus multi-mention (ids=id1,id2,id3 mentions_key=k1,,k3);
    degrades gracefully to @id when a mention key is absent from the mentions list

shortcuts/im/convert_lib/card_userdsl_test.go (new)

  • TestConvertInteractiveEventContent — invalid JSON, missing/empty/wrong-type user_dsl all
    fall back to "[interactive card]"; valid user-2 card renders <card title="...">body</card>
  • TestConvertInteractiveEventContentMentions — quoted & unquoted mention_key / mentions_key,
  • TestUserDslConverterSchema — user-1 (i18n_header), user-1 (direct header), user-2 schemas;
    empty card → fallback; title-only card → renders without body
  • TestUserDslConverterDispatch — table-driven, 30+ cases covering every element type routed
    through convertElement

Test Plan

  • make unit-test passed (go test -race ./...)
  • Added TestConvertInteractiveEventContent, TestConvertInteractiveEventContentMentions,
    TestUserDslConverterSchema, TestUserDslConverterDispatch with 30+ sub-cases
  • Manual verification: trigger a real interactive card message and confirm lark im receive
    outputs <card title="..."> text instead of raw JSON

Related Issues

Summary by CodeRabbit

  • Improvements
    • Interactive (card) message content is now converted automatically into a human-readable text representation rather than showing raw JSON.
    • Interactive card rendering supports mention placeholders, Markdown/Lark content, and a wide range of card elements (titles, media, buttons, tables, inputs, charts, and more).
  • Tests
    • Added comprehensive test coverage for interactive card conversion, including fallbacks and mention-resolution scenarios.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds ConvertInteractiveEventContent in shortcuts/im/convert_lib/card_userdsl.go to convert Lark interactive-card user DSL JSON into human-readable text, including mention resolution. processImMessageReceive is updated to invoke this function for interactive message types instead of passing raw JSON. A comprehensive test suite is added.

Changes

Interactive Card Conversion

Layer / File(s) Summary
Event handler wiring and schema update
events/im/message_receive.go
ImMessageReceiveOutput.Content desc updated to remove raw-JSON note; processImMessageReceive branches on msg.MessageType == "interactive" to call ConvertInteractiveEventContent rather than passing raw content through.
Mention map construction and at-tag resolver
shortcuts/im/convert_lib/card_userdsl.go
Adds package/imports, compiled regexes, and attrVal utility; builds mention map from mentions array; replaces <at ...> tags supporting both single-mention and multi-mention syntaxes with ordered display strings or @id fallbacks.
ConvertInteractiveEventContent entrypoint and userDslConverter
shortcuts/im/convert_lib/card_userdsl.go
Exported entrypoint handles JSON unmarshal/missing-field fallback; userDslConverter.convert detects schema2 vs schema1 DSL shapes, extracts header title/subtitle with i18n fallback, assembles body elements, and applies empty-result fallback.
Element dispatcher and structural container rendering
shortcuts/im/convert_lib/card_userdsl.go
convertElements recurses through element lists; convertElement dispatches across all tag variants; div, note, image, column_set/column converters assemble multi-line text with icons, fields, and nested recursion.
Interactive controls and form element rendering
shortcuts/im/convert_lib/card_userdsl.go
Renders buttons (URL extraction, confirm/disabled), actions, overflow, select/multi-select/person-select, inputs, date/time/datetime pickers with normalization, checkers, form wrappers, collapsible panels, and interactive containers with URL extraction.
Table, chart, person, tag, and media rendering
shortcuts/im/convert_lib/card_userdsl.go
Table rendering derives column headers/keys and normalizes cell values; chart interprets chart_spec; person/person_list, text-tag, avatar, select_img, repeat blocks, and audio/video placeholders complete element coverage.
Full test suite
shortcuts/im/convert_lib/card_userdsl_test.go
Tests cover all fallback paths, mention resolution for multiple at-tag syntaxes, schema/header variants, full element dispatch (table-driven), button URL priority, table-cell value extraction, table generation, lark_md mention resolution, and end-to-end schema2/schema1 conversion.

Sequence Diagram

sequenceDiagram
  participant ProcessImMessageReceive as processImMessageReceive
  participant ConvertInteractiveEventContent
  participant buildMentionMap
  participant userDslConverter
  participant convertElements
  ProcessImMessageReceive->>ConvertInteractiveEventContent: rawContent + mentions
  ConvertInteractiveEventContent->>buildMentionMap: mentions []interface{}
  buildMentionMap-->>ConvertInteractiveEventContent: mention map
  ConvertInteractiveEventContent->>userDslConverter: user_dsl + mentionMap
  userDslConverter->>convertElements: body.elements or root elements
  convertElements-->>userDslConverter: rendered text
  userDslConverter-->>ConvertInteractiveEventContent: <card title>rendered</card>
  ConvertInteractiveEventContent-->>ProcessImMessageReceive: human-readable text or [interactive card]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#1218: Both PRs integrate mentions into interactive-card @ rendering—the earlier PR extended card conversion to resolve mention_key/open_id via mentionsByKey, which this PR builds on by routing msg.Mentions through ConvertInteractiveEventContent.
  • larksuite/cli#1198: The main PR now precomputes and outputs interactive card message content using ConvertInteractiveEventContent, building on the card converter logic and expectations updated in the retrieved PR.

Suggested reviewers

  • YangJunzhou-01

Poem

🐰 Hop hop, the card JSON was wild,
Raw interactive blobs drove me mild.
Now user_dsl unwraps with care,
<at> tags resolved to names in the air.
Tables, buttons, charts—all neatly shown,
The rabbit tidied every unknown! 🃏

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding support for converting interactive card message format in the im.message.receive event handler.
Description check ✅ Passed The description follows the template with all required sections completed: Summary explains motivation, Changes lists implementation details across three files, Test Plan documents verification steps, and Related Issues section is included.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added domain/im PR touches the im domain size/L Large or sensitive change across domains or core paths labels Jun 16, 2026
@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@d77fde5842c551153f7977468f647820c70478fc

🧩 Skill update

npx skills add 91-enjoy/cli#feat/event_card_format -y -g

Change-Id: Id7ed87e907cc3159f479b217ce704c777c26e4e7
@91-enjoy
91-enjoy force-pushed the feat/event_card_format branch from 1f92f7c to d77fde5 Compare June 17, 2026 10:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/im/convert_lib/card_userdsl.go`:
- Around line 63-66: The resolveAtMentions function returns early when the
mentionAt map is empty, preventing the fallback parsing of <at id=...> and <at
ids=...> tags that should convert to `@id` format. Remove the early return
condition (lines 64-66) so the function continues to parse <at> tags and extract
the id or ids attributes as a fallback when no mention map is provided.
Additionally, add a unit test case to verify the fallback behavior works
correctly, such as testing that <at id=ou_x></at> converts to `@ou_x` when
mentionAt is nil or empty.
- Around line 117-126: The ConvertInteractiveEventContent function currently
returns the fallback "[interactive card]" when the JSON doesn't contain a
user_dsl field, even if rawContent itself is valid card DSL. When the unmarshal
succeeds but user_dsl is missing or empty in the content map, attempt to convert
rawContent directly as card DSL (using convertUserDslCard with the appropriate
mention map) before falling back to the "[interactive card]" message. This
ensures direct card JSON structures like {"header":{...}} are properly converted
to readable output. Additionally, add a test case to assert that direct
interactive card content is rendered correctly rather than replaced by the
fallback message, following the coding guideline that every behavior change
needs an accompanying test.
- Around line 158-160: The code currently only attempts to read the `zh_cn`
locale from the `i18n_header` object, which causes legacy cards that only have
`en_us` or `ja_jp` entries to lose their header content. Update the
`i18n_header` extraction logic to implement the same locale fallback mechanism
that `extractTextContent` already uses by trying to retrieve the header in order
from `zh_cn`, `en_us`, and `ja_jp`, returning the first one that exists instead
of only attempting `zh_cn`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 083dc339-1f10-4097-9ad3-3f3f75393320

📥 Commits

Reviewing files that changed from the base of the PR and between 1f92f7c and d77fde5.

📒 Files selected for processing (3)
  • events/im/message_receive.go
  • shortcuts/im/convert_lib/card_userdsl.go
  • shortcuts/im/convert_lib/card_userdsl_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • shortcuts/im/convert_lib/card_userdsl_test.go

Comment thread shortcuts/im/convert_lib/card_userdsl.go
Comment thread shortcuts/im/convert_lib/card_userdsl.go
Comment thread shortcuts/im/convert_lib/card_userdsl.go
@YangJunzhou-01
YangJunzhou-01 merged commit 96d7014 into larksuite:main Jun 18, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/im PR touches the im domain size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants