162 lines
5.1 KiB
Go
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 }
|