Documentation
¶
Overview ¶
Package chatmsg holds the shared, read-only projection helpers for DingTalk message-list responses (list_individual_chat_message, list_conversation_message_v2, search_at_me_message, search_messages_by_keyword, list_topic_replies, …). Several shortcuts reshape those raw responses into a clean speaker/text/time list; centralising the fiddly bits here keeps them consistent and fixed in one place:
- Sender: the display name lives under the bare "sender" key, forwarded entries carry the literal string "null", and some responses nest the speaker in a {name:…} object — all handled here.
- Text: out-of-office auto-replies / cards arrive as raw rich-content JSON, and card/robot messages arrive as undecryptable ciphertext; CleanText renders the former to readable text and marks the latter, WITHOUT ever rewriting ordinary text that merely contains a JSON fragment.
- Forwarded: a forwarded chat record ("聊天记录") hides its real per-message bodies in forwardMessages while the top-level content is a lossy summary.
Index ¶
- Constants
- Variables
- func ApplyMessagePagination(payload, data map[string]any, messages []map[string]any, direction string)
- func ApplyPagination(payload, data map[string]any)
- func ApplyTruncation(payload map[string]any)
- func CleanText(s string) string
- func ConversationID(m map[string]any) any
- func CreateTime(m map[string]any) any
- func DecryptChatMessageItems(ctx context.Context, rt messagecrypto.Runtime, items []map[string]any) map[string]any
- func FilterConversationScope(messages []map[string]any, conversationIDs []string) (matched []map[string]any, unverifiableMessageIDs []string)
- func Forwarded(m map[string]any, project func(map[string]any) map[string]any) []map[string]any
- func GroupSearchMessages(messages []map[string]any) []map[string]any
- func IsEncrypted(s string) bool
- func ListMessageItems(data map[string]any) []map[string]any
- func MergeDecryptLedger(payload map[string]any, ledger map[string]any)
- func MessageAISendFlag(m map[string]any) any
- func MessageID(m map[string]any) any
- func MessageType(m map[string]any) any
- func NewMessageListPayload(messages []map[string]any) map[string]any
- func NormalizeCardBizID(raw string) (string, error)
- func Pagination(data map[string]any) map[string]any
- func ProjectMessageSendReceipt(raw map[string]any) map[string]any
- func ProjectMessageSendStatus(raw map[string]any, requestedTaskID string) map[string]any
- func ProjectMessageV1(m map[string]any, includeReactions bool) map[string]any
- func ProjectStreamingCardReceipt(created map[string]any, bizID string) map[string]any
- func ProjectStreamingCardUpdate(updated map[string]any, bizID string, verification CardUpdateVerification) map[string]any
- func QuotedMessage(m map[string]any) map[string]any
- func Reactions(m map[string]any) map[string]any
- func Resources(m map[string]any) []map[string]any
- func ResourcesDeep(m map[string]any) []map[string]any
- func SearchItems(data map[string]any) []map[string]any
- func SearchMessageItems(data map[string]any) []map[string]any
- func Sender(m map[string]any) any
- func SenderID(m map[string]any) any
- func SenderType(m map[string]any) any
- func SetMessageDecryptClient(client *messagecrypto.Client)
- func StableMessageID(message map[string]any) string
- func SwapMessageDecryptClientForTest(t *testing.T, client *messagecrypto.Client)
- func Text(m map[string]any) any
- func ThreadID(m map[string]any) any
- func UpdateTime(m map[string]any) any
- type CardUpdateVerification
- type MessageResultContract
Constants ¶
const MessageListContractVersion = "im.message-list.v1"
MessageListContractVersion identifies the additive, compatibility-preserving public envelope shared by message list/search/mget/thread projections.
const MessageSendReceiptContractVersion = "im.message-send-receipt.v1"
MessageSendReceiptContractVersion identifies the additive receipt attached to high-level current-user send results.
const MessageSendStatusContractVersion = "im.message-send-status.v1"
MessageSendStatusContractVersion identifies the additive workflow fields projected by the high-level send-status shortcut. The lower response fields remain at their original locations for compatibility.
const StreamingCardContractVersion = "im.streaming-card.v1"
StreamingCardContractVersion identifies the additive card receipt emitted by high-level card shortcuts.
Variables ¶
Functions ¶
func ApplyMessagePagination ¶
func ApplyMessagePagination(payload, data map[string]any, messages []map[string]any, direction string)
ApplyMessagePagination publishes message-list completeness and converts the authoritative millisecond nextCursor into the RFC3339Nano time boundary accepted by the executable message-list command. Projected createTime is deliberately not used because it is only second precision.
func ApplyPagination ¶
ApplyPagination carries lower-layer completeness facts into a projected Shortcut payload. It intentionally preserves cursor values only in the command output (where callers need them to continue); audit reports redact those values and retain only their presence.
func ApplyTruncation ¶ added in v1.0.58
ApplyTruncation publishes the stable aggregate bit while preserving the established reason-specific fields for compatibility and diagnosis.
func CleanText ¶
CleanText makes a message body human-readable WITHOUT ever rewriting ordinary text. It only transforms a body that is a genuine DingTalk structured message:
- Encrypted card/robot ciphertext (base64 + "||v||t||len" trailer) → a clear "[加密消息]" marker instead of the raw base64.
- A rich-content card (out-of-office auto-reply, link/preview card, …) whose lines include at least one recognised rich-content block → the readable text extracted from those blocks, with the card's decorative JSON lines and "empty" placeholders dropped.
Crucially, if NO line is a recognised rich-content block (e.g. ordinary text that merely embeds a `{"approved":false}` fragment), the original string is returned verbatim — a JSON line is never silently dropped.
func ConversationID ¶
ConversationID preserves the stable conversation identity carried by list and search responses.
func CreateTime ¶
CreateTime reads a message's create/send time under whichever candidate key is present, returning the raw value.
func DecryptChatMessageItems ¶ added in v1.0.63
func DecryptChatMessageItems(ctx context.Context, rt messagecrypto.Runtime, items []map[string]any) map[string]any
DecryptChatMessageItems decrypts encrypted message items in place before projection, mirroring the atomic helpers chat read path. It returns the decrypt ledger payload fields, or nil when decryption must not run (dry-run, missing runtime, or a stub build without the SafeChat backend). It never returns an error: per-message failures land in the ledger and the command exit code stays unchanged.
func FilterConversationScope ¶ added in v1.0.58
func FilterConversationScope(messages []map[string]any, conversationIDs []string) (matched []map[string]any, unverifiableMessageIDs []string)
FilterConversationScope keeps only messages belonging to the explicitly requested conversations. A message without a conversation identity is reported as unverifiable rather than treated as in-scope.
func Forwarded ¶
Forwarded projects the nested messages of a forwarded chat record. The caller supplies its own per-message projection so each command keeps its own row shape; project is applied recursively, so multi-level forwards expand too.
func GroupSearchMessages ¶ added in v1.0.58
GroupSearchMessages restores the established typed search envelope after a client-side scoped scan. Group order follows first occurrence in the search result, and each message retains its original fields.
func IsEncrypted ¶
IsEncrypted reports whether a message body is a raw DingTalk encrypted-message ciphertext: a base64 blob (DingTalk wraps it across several lines) followed by the "||v||t||len" trailer. It is intentionally strict — both the trailer and a pure-base64 body are required — so ordinary text (CJK, punctuation, …) never trips it.
func ListMessageItems ¶ added in v1.0.58
ListMessageItems returns message rows from the common list response envelopes.
func MergeDecryptLedger ¶ added in v1.0.63
MergeDecryptLedger adds the decrypt ledger fields into an output payload. Counters accumulate, decryptFailures append, and partial is kept as an or so an earlier aggregation failure is never overwritten. A nil ledger (decrypt did not run) is a no-op so payloads stay byte-identical to the pre-decrypt output.
func MessageAISendFlag ¶ added in v1.0.62
MessageAISendFlag preserves the lower IM marker that identifies a message sent through an AI client. The service currently publishes the exact messageAiSendFlag field (for example "DWS"); readers must not infer it from sender type, robot status, clawType request metadata, or message content.
func MessageID ¶
MessageID preserves the stable message identity needed by follow-up reply, reaction, resource and deduplication operations.
func MessageType ¶
MessageType preserves the lower message type when present.
func NewMessageListPayload ¶ added in v1.0.57
NewMessageListPayload initializes the common result ledger before a caller adds pagination or resource-download facts.
func NormalizeCardBizID ¶ added in v1.0.58
NormalizeCardBizID performs only format-independent checks. bizId is an opaque server-issued identifier; a stricter character or prefix contract must not be invented by the CLI without an authoritative API declaration.
func Pagination ¶
Pagination extracts hasMore/nextCursor from the response root or a common result/data envelope. When hasMore is present it also emits the explicit inverse "complete", making truncation hard for an Agent to overlook.
func ProjectMessageSendReceipt ¶ added in v1.0.58
ProjectMessageSendReceipt connects a send result to its asynchronous status query without treating openTaskId as a message identifier.
func ProjectMessageSendStatus ¶ added in v1.0.58
ProjectMessageSendStatus preserves the lower response and adds a stable receipt that connects openTaskId to the message identifiers required by edit, recall, and read-status. It never manufactures a message reference: downstream actions are marked ready only when both IDs are actually present.
func ProjectMessageV1 ¶ added in v1.0.57
ProjectMessageV1 is the single compatibility-preserving core projection for list, search, mget, @me, and thread readers. Public wrappers may retain legacy aliases such as time or msgType, but the underlying identity, context, reaction, quote, forward, and resource semantics come from here.
func ProjectStreamingCardReceipt ¶ added in v1.0.58
ProjectStreamingCardReceipt publishes every server-returned identifier in a single cardRef. referencePairAvailable means this response contained both the update identifier and the visible message identifiers; it does not claim that older messages can be resolved without server-side mapping support.
func ProjectStreamingCardUpdate ¶ added in v1.0.58
func ProjectStreamingCardUpdate(updated map[string]any, bizID string, verification CardUpdateVerification) map[string]any
ProjectStreamingCardUpdate preserves the lower response while making the verified target explicit for downstream consumers.
func QuotedMessage ¶
QuotedMessage projects one level of quoted/replied-to context. It is deliberately non-recursive: a reply chain may be arbitrarily deep or even cyclic after gateway reshaping, while an Agent primarily needs the quoted message's stable identity, speaker, readable body and time.
func Reactions ¶
Reactions normalises DingTalk's inline emotionReplyList into one compact, Agent-friendly block. Unlike Lark, DingTalk already returns these reactions with message-list responses, so this projection performs no extra network request.
Output shape:
"reactions": {
"counts": [{"emoji": "赞", "count": 3}],
"details": [{"emoji": "赞", "replyUsers": ["..."]}]
}
func Resources ¶
Resources extracts actionable media and drive-file references from both structured message fields and the textual mediaId/fileId notation returned by older DingTalk message APIs. Every reference publishes the exact Shortcut arguments already known from the message, plus ready=false and missing fields when a follow-up lookup is still required. This shared shape is used by list, search, mget, quoted messages and thread replies.
func ResourcesDeep ¶ added in v1.0.56
ResourcesDeep returns resources from a message and each nested quoted, replied-to or forwarded message. Every nested resource is projected from the child message that owns it, so its download arguments never reuse the parent message ID. A missing child conversation ID inherits the enclosing conversation because quoted and forwarded records often omit that duplicate field.
func SearchItems ¶ added in v1.0.58
SearchItems locates and flattens the message list returned by the two DingTalk message-search interfaces. Grouped search responses carry the conversation identity on the group rather than each message, so the flattener copies that identity onto every returned message before callers perform scope checks.
func SearchMessageItems ¶ added in v1.0.58
SearchMessageItems flattens grouped search results into stable message rows.
func Sender ¶
Sender reads a message's speaker display name, tolerating common sender-name keys. The message-list responses carry the display name under the bare "sender" key (verified live), so it is probed first; the remaining aliases and the *Id fallbacks keep the projection resilient to other shapes. The literal string "null" (forwarded entries) and the empty string are treated as absent, and a nested {name:…} sender object yields its display name rather than the raw object.
func SenderID ¶ added in v1.0.57
SenderID preserves the stable sender identity without replacing the legacy scalar sender display field. Nested sender records and both userId families are accepted because list/search/mget currently expose different shapes.
func SenderType ¶ added in v1.0.57
SenderType returns only an explicitly published lower sender type. It does not guess that every sender identity is a user because bot/system messages can share the same generic senderId key.
func SetMessageDecryptClient ¶ added in v1.0.63
func SetMessageDecryptClient(client *messagecrypto.Client)
SetMessageDecryptClient injects the app-owned SafeChat/Ding crypto client for the smart read shortcuts. nil falls back to the default (backend-less) client.
func StableMessageID ¶ added in v1.0.57
StableMessageID returns the normalized message identity used for cross-page deduplication. An empty value means the lower response did not publish a stable identity; callers must keep that row rather than guessing.
func SwapMessageDecryptClientForTest ¶ added in v1.0.63
func SwapMessageDecryptClientForTest(t *testing.T, client *messagecrypto.Client)
SwapMessageDecryptClientForTest replaces the injected decrypt client for the test duration and restores the previous value via testseam. Sequential tests only.
func Text ¶
Text reads a message's textual content (tolerating common text keys and one level of nesting) and runs it through CleanText.
func ThreadID ¶
ThreadID preserves the stable topic/thread identity needed to continue from a message-list result into the thread-replies command.
func UpdateTime ¶
UpdateTime reads an edited message's update time. Gateways sometimes echo createTime as updateTime even when the message was never edited; omit that duplicate so Agents do not infer a nonexistent edit.
Types ¶
type CardUpdateVerification ¶ added in v1.0.59
CardUpdateVerification distinguishes an accepted write from an independently verified write. Some server versions return success=true without an updated flag or affected count; that is sufficient to report acceptance, but not to claim that the card's visible content was observed after the write.
func VerifyStreamingCardUpdate ¶ added in v1.0.58
func VerifyStreamingCardUpdate(requestedBizID string, response map[string]any) (CardUpdateVerification, error)
VerifyStreamingCardUpdate separates server acceptance from affirmative evidence that the requested write took effect.
type MessageResultContract ¶ added in v1.0.57
MessageResultContract is the reviewed additive contract shared by message list, search, mget, @me and thread projections. Keep this descriptor small: Runtime owns the values, while Skill references and policy checks consume a copy of these field names so prose cannot silently invent another result shape.
func CurrentMessageResultContract ¶ added in v1.0.57
func CurrentMessageResultContract() MessageResultContract
CurrentMessageResultContract returns defensive copies so callers cannot mutate the process-wide reviewed descriptor.