Files
Cyrene_v2/internal/channel/adapter/message.go
T

162 lines
5.1 KiB
Go

// Package adapter defines the cross-platform IM channel adaptation contract for
// Cyrene v2. It declares the normalized message model, the per-platform adapter
// interface, the capability declaration, and the strongly typed identity (actor)
// metadata that the channel layer uses for owner detection and group-chat
// isolation.
//
// The contract is intentionally platform-neutral: concrete adapters (qq-obv11,
// weixin-ilink, telegram, webhook) are implemented behind this interface in
// milestone M6, not here. The design authority is DevDocs/design/08-渠道接入.md
// (sections 0-2) and DevDocs/design/02-会话与上下文模型.md (section 3).
package adapter
import (
"errors"
"fmt"
"time"
)
// ChannelType classifies where a message originated or is destined.
type ChannelType string
const (
// ChannelDirect is a one-to-one private conversation.
ChannelDirect ChannelType = "direct"
// ChannelGroup is a group / room conversation.
ChannelGroup ChannelType = "group"
)
// Valid reports whether ct is one of the known channel kinds.
func (ct ChannelType) Valid() bool {
switch ct {
case ChannelDirect, ChannelGroup:
return true
default:
return false
}
}
// ContentType classifies the primary payload of a message.
type ContentType string
const (
// ContentText is a plain-text chat message.
ContentText ContentType = "text"
// ContentImage is an image attachment.
ContentImage ContentType = "image"
// ContentVoice is a voice/audio clip.
ContentVoice ContentType = "voice"
// ContentFile is an arbitrary file attachment.
ContentFile ContentType = "file"
// ContentAction is a non-chat action signal (typing, join/leave, recall).
ContentAction ContentType = "action"
)
// Valid reports whether ct is one of the known content kinds.
func (ct ContentType) Valid() bool {
switch ct {
case ContentText, ContentImage, ContentVoice, ContentFile, ContentAction:
return true
default:
return false
}
}
// MediaKind classifies an individual media attachment. It is broader than
// ContentType so attachments can also carry video or other payloads.
type MediaKind string
const (
// MediaImage is an image file.
MediaImage MediaKind = "image"
// MediaVoice is an audio/voice file.
MediaVoice MediaKind = "voice"
// MediaVideo is a video file.
MediaVideo MediaKind = "video"
// MediaFile is an arbitrary file.
MediaFile MediaKind = "file"
)
// Media is a single attachment carried by a UnifiedMessage. URL and Data are
// mutually exclusive representations: prefer URL for remote media to
// keep payloads out of memory.
type Media struct {
// Kind classifies the attachment.
Kind MediaKind
// URL is the remote location of the media when not inlined.
URL string
// Name is the original file name, when known.
Name string
// Size is the media size in bytes.
Size int64
// MIMEType is the media content type, when known.
MIMEType string
// Data carries inlined bytes for small, locally produced payloads.
Data []byte
}
// Mention is a reference to a user inside a message body.
type Mention struct {
UserID string
UserName string
}
// UnifiedMessage is the normalized, platform-neutral representation of an IM
// event or inbound message. Every channel adapter converts its native payload
// into UnifiedMessage so the runtime can reason about them uniformly.
//
// SenderID/SenderName are kept per design/08 section 0; Sender carries the
// strongly typed identity metadata (Role, group flags) used for owner detection
// and group isolation. The strategy engine (M6) decides access from Sender, not
// from the free-text SenderName.
type UnifiedMessage struct {
Platform string
ChannelID string
ChannelType ChannelType
SenderID string
SenderName string
Sender Actor
Content string
ContentType ContentType
Media []Media
Mentions []Mention
ReplyToID string
ReplyToText string
GroupID string
GroupName string
ThreadID string
MessageID string
Timestamp time.Time
// RawData is the opaque native payload the message was converted from.
RawData []byte
}
// UnifiedResponse is the normalized outbound payload for SendMessage. It may
// carry several messages so an adapter can burst a reply.
type UnifiedResponse struct {
// Messages are the reply messages to deliver.
Messages []UnifiedMessage
// ReplyTo is an optional context identifier the messages reply to.
ReplyTo string
// Hint is an optional human hint surfaced by the runtime.
Hint string
}
// Validate reports whether the message is well-formed for downstream handling.
// It checks the enum fields and requires a group id on group messages.
func (m UnifiedMessage) Validate() error {
if !m.ChannelType.Valid() {
return fmt.Errorf("adapter: invalid channel type %q", m.ChannelType)
}
if !m.ContentType.Valid() {
return fmt.Errorf("adapter: invalid content type %q", m.ContentType)
}
if m.ChannelType == ChannelGroup && m.GroupID == "" {
return errors.New("adapter: group message missing group id")
}
return nil
}
// IsGroup reports whether the message belongs to a group conversation.
func (m UnifiedMessage) IsGroup() bool { return m.ChannelType == ChannelGroup }