chatmsg

package
v1.0.63 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

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

View Source
const MessageListContractVersion = "im.message-list.v1"

MessageListContractVersion identifies the additive, compatibility-preserving public envelope shared by message list/search/mget/thread projections.

View Source
const MessageSendReceiptContractVersion = "im.message-send-receipt.v1"

MessageSendReceiptContractVersion identifies the additive receipt attached to high-level current-user send results.

View Source
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.

View Source
const StreamingCardContractVersion = "im.streaming-card.v1"

StreamingCardContractVersion identifies the additive card receipt emitted by high-level card shortcuts.

Variables

View Source
var (
	ErrCardUpdateNotApplied = errors.New("streaming card update was not applied")
	ErrCardUpdateUnverified = errors.New("streaming card update could not be verified")
	ErrCardUpdateBizIDDrift = errors.New("streaming card update returned a different bizId")
)

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

func ApplyPagination(payload, data map[string]any)

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

func ApplyTruncation(payload map[string]any)

ApplyTruncation publishes the stable aggregate bit while preserving the established reason-specific fields for compatibility and diagnosis.

func CleanText

func CleanText(s string) string

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

func ConversationID(m map[string]any) any

ConversationID preserves the stable conversation identity carried by list and search responses.

func CreateTime

func CreateTime(m map[string]any) any

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

func Forwarded(m map[string]any, project func(map[string]any) map[string]any) []map[string]any

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

func GroupSearchMessages(messages []map[string]any) []map[string]any

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

func IsEncrypted(s string) bool

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

func ListMessageItems(data map[string]any) []map[string]any

ListMessageItems returns message rows from the common list response envelopes.

func MergeDecryptLedger added in v1.0.63

func MergeDecryptLedger(payload map[string]any, ledger map[string]any)

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

func MessageAISendFlag(m map[string]any) any

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

func MessageID(m map[string]any) any

MessageID preserves the stable message identity needed by follow-up reply, reaction, resource and deduplication operations.

func MessageType

func MessageType(m map[string]any) any

MessageType preserves the lower message type when present.

func NewMessageListPayload added in v1.0.57

func NewMessageListPayload(messages []map[string]any) map[string]any

NewMessageListPayload initializes the common result ledger before a caller adds pagination or resource-download facts.

func NormalizeCardBizID added in v1.0.58

func NormalizeCardBizID(raw string) (string, error)

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

func Pagination(data map[string]any) map[string]any

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

func ProjectMessageSendReceipt(raw map[string]any) map[string]any

ProjectMessageSendReceipt connects a send result to its asynchronous status query without treating openTaskId as a message identifier.

func ProjectMessageSendStatus added in v1.0.58

func ProjectMessageSendStatus(raw map[string]any, requestedTaskID string) map[string]any

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

func ProjectMessageV1(m map[string]any, includeReactions bool) map[string]any

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

func ProjectStreamingCardReceipt(created map[string]any, bizID string) map[string]any

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

func QuotedMessage(m map[string]any) map[string]any

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

func Reactions(m map[string]any) map[string]any

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

func Resources(m map[string]any) []map[string]any

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

func ResourcesDeep(m map[string]any) []map[string]any

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

func SearchItems(data map[string]any) []map[string]any

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

func SearchMessageItems(data map[string]any) []map[string]any

SearchMessageItems flattens grouped search results into stable message rows.

func Sender

func Sender(m map[string]any) any

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

func SenderID(m map[string]any) any

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

func SenderType(m map[string]any) any

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

func StableMessageID(message map[string]any) string

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

func Text(m map[string]any) any

Text reads a message's textual content (tolerating common text keys and one level of nesting) and runs it through CleanText.

func ThreadID

func ThreadID(m map[string]any) any

ThreadID preserves the stable topic/thread identity needed to continue from a message-list result into the thread-replies command.

func UpdateTime

func UpdateTime(m map[string]any) any

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

type CardUpdateVerification struct {
	Accepted bool
	Verified bool
	Evidence string
}

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

type MessageResultContract struct {
	Version        string
	MessageFields  []string
	EnvelopeFields []string
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL