Documentation
¶
Overview ¶
Package platform 定义平台无关的消息与事件抽象层。
设计目标:
- 将框架核心(engine、context)与具体平台(QQ官方、Discord、Telegram 等)解耦
- 现有 QQ 适配器通过 platform/qq 包实现本接口,向后兼容
- 新平台只需实现 Adapter + Event 接口,无需改动核心引擎
层次结构:
┌──────────────┐ │ Bot/Engine │ 使用 platform.Event / platform.Adapter ├──────────────┤ │ platform/ │ 接口定义(本包) ├──────────────┤ │ platform/qq │ QQ 官方实现 │ platform/.. │ 其他平台实现 └──────────────┘
forward.go — 合并转发记录的统一结构与文本渲染。
SegmentForward 段 / reply 段的结构化转发载荷(*ForwardRecord)由各平台 适配器归一化产出(QQ 官方 API 的扁平渲染文本、milky/onebot 的转发节点等), 存储键见 SegmentExtraForwardNodes / SegmentExtraQuotedForward。下游(AI 提示词、消息记录、跨平台降级摘要)通过本文件的类型与 ForwardRecordText 消费,无需依赖具体平台包。
Index ¶
- Constants
- Variables
- func AttachmentTranscript(att Attachment) string
- func Content(e Event) string
- func ForwardRecordText(rec *ForwardRecord) string
- func GetBotID(a Adapter) string
- func GetBotName(a Adapter) string
- func GetPlatformAPI(sender Sender) any
- func GetPlatformAPIAs[T any](sender Sender) (T, bool)
- func GetReplyToID(e Event) string
- func IsEdited(e Event) bool
- func IsSendErrorCode(err error, code SendErrorCode) bool
- func MentionedBot(e Event) bool
- func PickQuotedImage(atts []Attachment) (string, string)
- func QuotedImage(segs []Segment) (url, mimeType string)
- func RawPayload(e Event) any
- func RawType(e Event) string
- func SafeDispatch(handler func(Event), event Event)
- func SegmentsContent(segs []Segment) string
- func SegmentsReplyToID(segs []Segment) string
- func TruncateText(text string, maxRunes int) string
- type APIProvider
- type Adapter
- type AdapterObserver
- type Announcement
- type AnnouncementManager
- type Attachment
- type AttachmentKind
- type AutoModerator
- type AvatarProvider
- type BotIdentity
- type Button
- type ButtonStyle
- type Capabilities
- type CapabilityFlag
- type ChatInfo
- type DegradePolicy
- type DirectedAtBotEvent
- type DisconnectNotifier
- type EditableEvent
- type Embed
- type EmbedField
- type Emoji
- type EmojiKind
- type Event
- type EventIdentity
- type EventKind
- type ForwardNode
- type ForwardNodeKind
- type ForwardOption
- type ForwardRecord
- type GroupInfo
- type GroupInfoProvider
- type GroupManager
- type GroupMemberInfo
- type GroupRole
- type GroupSettings
- type HealthDetailer
- type InvitationHandler
- type MentionsEvent
- type Message
- type MessageDeleter
- type MessageEditor
- type MessageHistoryProvider
- type NoopSender
- type OutboundMessage
- func AudioMessage(url string) OutboundMessage
- func FileDataMessage(data []byte, name, mimeType string) OutboundMessage
- func FileMessage(url, name string) OutboundMessage
- func ImageDataMessage(data []byte, name, mimeType string) OutboundMessage
- func ImageMessage(url string) OutboundMessage
- func MarkdownMessage(md string) OutboundMessage
- func MessageToOutbound(m Message, opts ...ForwardOption) OutboundMessage
- func SegmentsToOutbound(segs []Segment) OutboundMessage
- func TextMessage(text string) OutboundMessage
- func VideoMessage(url string) OutboundMessage
- func (m OutboundMessage) IsEmpty() bool
- func (m OutboundMessage) WithAttachments(attachments ...Attachment) OutboundMessage
- func (m OutboundMessage) WithButtons(buttons ...Button) OutboundMessage
- func (m OutboundMessage) WithEmbeds(embeds ...Embed) OutboundMessage
- func (m OutboundMessage) WithExtra(key string, value any) OutboundMessage
- func (m OutboundMessage) WithMentions(userIDs ...string) OutboundMessage
- func (m OutboundMessage) WithQuoteTrigger() OutboundMessage
- func (m OutboundMessage) WithReply(messageID string) OutboundMessage
- type RawEvent
- type ReactionSender
- type RecoverableAdapter
- type Registry
- func (r *Registry) All() []Adapter
- func (r *Registry) CapabilitiesFor(platform string) (Capabilities, bool)
- func (r *Registry) FatalErrors() <-chan error
- func (r *Registry) Get(platform string) (Adapter, bool)
- func (r *Registry) Len() int
- func (r *Registry) Register(adapter Adapter)
- func (r *Registry) Remove(platform string) bool
- func (r *Registry) Replace(adapter Adapter) (old Adapter, replaced bool)
- func (r *Registry) SenderFor(platform string) (Sender, bool)
- func (r *Registry) StartAll(ctx stdctx.Context, handler func(Event)) error
- func (r *Registry) StopAll(ctx stdctx.Context) error
- func (r *Registry) WithObserver(o AdapterObserver) *Registry
- type ReplyEvent
- type Segment
- type SegmentType
- type SendError
- type SendErrorCode
- type SendRequest
- type SendResult
- type Sender
- type SessionNotifier
- type SyntheticEvent
- type SyntheticOption
- func WithSyntheticAttachments(a ...Attachment) SyntheticOption
- func WithSyntheticChat(c ChatInfo) SyntheticOption
- func WithSyntheticID(id string) SyntheticOption
- func WithSyntheticPlatform(p string) SyntheticOption
- func WithSyntheticSender(u UserInfo) SyntheticOption
- func WithSyntheticTimestamp(t time.Time) SyntheticOption
- type TypingNotifier
- type UserInfo
- type VoiceTranscript
Constants ¶
const ( // SegmentExtraForwardNodes 直发合并转发消息的结构化记录(SegmentForward 段)。 SegmentExtraForwardNodes = "forward_nodes" // SegmentExtraQuotedForward 被引用合并转发记录(reply 段,被引用消息 // 本身是合并转发时,如 QQ 103 引用 102)。 SegmentExtraQuotedForward = "quote_forward" )
Segment.Extra 中转发记录载荷的键(见 SegmentExtraKey 通用键说明)。
const ( // SegmentExtraIsSelf 平台 payload 自带自我标记时(qq is_you、satori is_self) // 解析器在 at 段上标注 true,作为 botID 判定的覆盖路径。 SegmentExtraIsSelf = "is_self" // SegmentExtraFromPlatform 段来源平台标记(防御性,防跨平台误透传)。 SegmentExtraFromPlatform = "from_platform" // SegmentExtraTitle forward 段摘要标题(跨平台降级用)。 SegmentExtraTitle = "title" // SegmentExtraSummary forward 段摘要文本(跨平台降级用)。 SegmentExtraSummary = "summary" // SegmentExtraQuoteAtts reply 段携带的被引用消息附件列表([]Attachment)。 // // 引用图片等富媒体时,被引用内容通常不在本条消息的段列表中;适配器在 // 解析引用消息时把被引用附件归一化后存入本键,供下游(AI 视觉、识图 // 插件等)直接消费。取值约定: // - 类型恒为 []Attachment(平台归一化结构,非平台原始 JSON) // - URL 为事件时刻获取的直链(Telegram 经 getFile 换链、Discord 取自 // referenced_message、OneBot 经 get_msg 回查);多数平台直链会过期, // 下游应及时使用,不应持久化 URL // - 填充是尽力而为:payload 未内嵌且回查失败时键缺省,调用方须容忍 SegmentExtraQuoteAtts = "quote_attachments" )
SegmentExtraKey 是 Segment.Extra 的通用键。
const ButtonRowAuto = 0
ButtonRowAuto 是 Button.Row 的零值,表示交由平台自动排列布局。
Row=ButtonRowAuto 时,每个按钮各自独占一行(安全默认值)。 需要将多个按钮排在同一行时,为它们指定相同的 Row 值(1 ~ 5)。
Variables ¶
var ErrNotSupported = errors.New("operation not supported by this platform")
ErrNotSupported 当平台不支持某个可选管理操作时,实现方应返回此错误。
示例(WeChat Sender 实现 GroupManager 接口但不支持 SetAdmin):
func (s *wechatSender) SetAdmin(_ stdctx.Context, _, _ string, _ bool) error {
return platform.ErrNotSupported
}
Functions ¶
func AttachmentTranscript ¶ added in v1.38.0
func AttachmentTranscript(att Attachment) string
AttachmentTranscript 从附件的 Extra 元数据中提取语音转写文本。
遍历 Extra 中所有值,返回第一个实现 VoiceTranscript 且转写文本非空的; 无则返回空字符串。
func Content ¶ added in v1.35.0
Content 返回事件的正文文本(段拼接 + 首尾空白裁剪)。
裁剪语义与 QQ/Satori 既有行为一致:QQ 群消息在 @ 占位符前自动插入分隔 空格(实测格式 " <@id> 正文"),占位符剥离后正文首部会残留空白。 段模型保留原文(Segments() 可恢复完整原文),本便捷视图统一裁剪。
func ForwardRecordText ¶ added in v1.54.0
func ForwardRecordText(rec *ForwardRecord) string
ForwardRecordText 将合并转发记录渲染为可读文本(供 AI 提示词、消息记录 等文本型下游消费)。媒体段渲染为占位标记([图片]/[语音]/[视频]/[文件]), 不产出 URL——转发内媒体直链通常为时效性链接,不应进入持久文本。
内置防膨胀上限(面向提示词场景的保守预算):
- 每条子消息文本截断至 forwardNodeTextMaxRunes rune;
- 每层最多渲染 forwardMaxNodesPerLevel 条,超出部分以省略行标注;
- 嵌套层级最多 forwardMaxDepth 层,更深的关联内容以占位行标注。
空记录返回 ""。
func GetBotID ¶
GetBotID 安全获取适配器的机器人唯一 ID。
若适配器未实现 BotIdentity 或平台尚未返回 ID,返回空字符串。
使用示例:
if platform.GetBotID(adapter) == event.Sender().ID {
return // 忽略自身发出的消息
}
func GetPlatformAPI ¶ added in v1.34.0
GetPlatformAPI 从 platform.Sender 提取平台特有 API 句柄。
返回 any,调用方需自行断言目标类型;推荐优先使用泛型版本 GetPlatformAPIAs,类型安全且无需断言:
api := platform.GetPlatformAPI(ctx.GetPlatformSender())
if one, ok := api.(*onebot.Sender); ok {
_ = one.SendGroupSign(ctx, 123456)
}
func GetPlatformAPIAs ¶ added in v1.34.0
GetPlatformAPIAs 泛型获取平台特有 API 句柄。
从 Sender 提取 APIProvider 句柄并断言为目标类型 T(具体类型或接口均可), 避免 GetPlatformAPI 返回 any 后的手工断言。**仅用于平台特有 API**; 平台无关操作请直接使用 Sender 接口本身:
api, ok := platform.GetPlatformAPIAs[*onebot.Sender](ctx.GetPlatformSender())
if ok {
_, _ = api.SendGroupForwardMsg(ctx, 123456, nodes) // 平台特有
}
// 平台无关操作仍走 Sender(二者互补,互不影响)
if gm, ok := platform.GetGroupManager(adapter); ok {
_ = gm.BanMember(ctx, groupID, userID, 60)
}
// T 也可以是接口(如 QQ 的 OpenAPI 接口)
api, ok := platform.GetPlatformAPIAs[openapi.OpenAPI](sender)
若 Sender 未实现 APIProvider 或句柄类型不是 T,返回 (零值, false)。
func GetReplyToID ¶
GetReplyToID 安全获取被回复消息的 ID。
派生顺序(reply 段为单一真相源):段内首个 SegmentReply → 接口断言兜底。 平台实现其 ReplyToID() 时应直接委托段查找,杜绝双写。 若事件无 reply 段且未实现 ReplyEvent,返回空字符串。
func IsSendErrorCode ¶
func IsSendErrorCode(err error, code SendErrorCode) bool
IsSendErrorCode 判断 err 链中是否存在指定 SendErrorCode。
等价于 AsSendError(err) && se.Code == code,但更简洁。
使用示例:
if platform.IsSendErrorCode(err, platform.SendErrPermDenied) {
notifyAdmin("bot lacks permission")
}
func MentionedBot ¶ added in v1.60.0
MentionedBot 判断消息是否 @ 了机器人自身。
派生顺序(跨平台统一口径):
- GetMentions 中 IsSelf=true 的条目——各平台的结构化 @ 列表;
- DirectedAtBotEvent 标记——事件类型本身即"@机器人"、载荷无法表达 @ 的平台(如 QQ 群 @机器人 消息)。
两者都没有时返回 false。"平台无法感知 @ 列表即放行"这一宽松语义只保留在 [OnMentionedBot] 中,不在本函数内表达:插件内的 @ 判定(如群策略要求 @ 时 的过滤)需要严格结果,不能被无条件放宽。
若事件为 nil,返回 false。
func PickQuotedImage ¶ added in v1.41.2
func PickQuotedImage(atts []Attachment) (string, string)
PickQuotedImage 从归一化附件列表中取首个图片附件,返回 (URL, MimeType)。
优先 Kind 或 MimeType 显式标注 image 的项;显式标注其他类型的项跳过; 全部未标注类型时回退首个带 URL 的项(真实类型由下载后的内容嗅探二次校验)。
func QuotedImage ¶ added in v1.41.2
QuotedImage 从引用消息段中提取被引用图片,返回 (URL, MimeType)。
提取顺序(对每个 reply 段):
- 归一化附件 Extra[SegmentExtraQuoteAtts](跨平台统一路径)
- 结构化被引用合并转发记录 Extra[SegmentExtraQuotedForward] (*ForwardRecord,QQ 103 引用 102 时由适配器解析填充;图片按记录 节点顺序提取,递归嵌套关联子条目)
- QQ 兼容兜底 Extra["raw_quote"](msg_elements 原始 JSON,图片位于 elements[].attachments[])
- QQ 兼容兜底 Extra["parallel_message"](并行视图 msg_nodes[].attachments)
类型判定:优先 Kind 或 content_type 显式标注 image/* 的项;显式标注其他 类型(video/audio/file)的项跳过,不作为兜底;所有项均未标注类型时回退 首个带 URL 的项(真实类型由下载后的内容嗅探二次校验)。 无引用段或无可用的图片附件时返回空。
func SafeDispatch ¶
SafeDispatch 安全调用事件 handler,捕获并记录 panic 以防止崩溃。
所有平台适配器的 Start() 方法应使用此函数包装对 handler 的调用, 而非在每个适配器中重复实现相同的 defer-recover 逻辑。
使用示例:
platform.SafeDispatch(handler, event)
func SegmentsContent ¶ added in v1.34.0
SegmentsContent 将有序消息段拼接为纯文本。
规则:
- text 段:直接拼接 Text
- at / mention_all:**剥离**(不进入 Content)——统一后各平台 Content 命令友好(OnCommand 可匹配 "@机器人 /ping"),修复平台间行为分裂
- face / reply / forward / button / unknown:跳过
- image/audio/video/file:跳过(无文本)
本函数对段不做任何过滤(含 @ 机器人自身的段也在段列表中)。
func SegmentsReplyToID ¶ added in v1.34.0
SegmentsReplyToID 返回段中首个 reply 段的目标消息 ID(无 reply 段时返回空字符串)。
func TruncateText ¶
TruncateText 截断文本至指定字符数(按 Unicode rune 计算,非字节)。
若文本长度不超过 maxRunes,直接返回原字符串(无内存分配)。 超出时返回截断后的文本加 "…" 后缀,**返回值总长度不超过 maxRunes** (省略号计入预算内)。
该函数的典型用法是把文本压到平台长度上限以内,例如 Capabilities.MaxTextLength 或 Telegram 的 200 字符 callback 提示; 若省略号不计入预算,结果会恰好超出上限一个字符,请求仍被平台拒绝。
修复框架问题 #27:xhstext 发现生成文本可能超过平台消息长度限制, 统一提供此工具函数避免各插件自行实现截断逻辑。
使用示例:
output := platform.TruncateText(longText, 500)
Types ¶
type APIProvider ¶ added in v1.34.0
type APIProvider interface {
// PlatformAPI 返回平台特有的 API 句柄。
PlatformAPI() any
}
APIProvider 由平台 Sender 实现,向调用方暴露平台特有的 API 句柄。
返回的句柄是**操作面**:请仅调用其方法,勿保存句柄跨请求复用, 也勿尝试构造零值句柄(内部依赖未初始化时会 panic)。
PlatformAPI 的返回值类型与字段可见性(各平台包文档说明):
platform/onebot → *onebot.Sender 字段私有,仅可调用方法
platform/qq → openapi.OpenAPI 接口,无字段
platform/milky → *milky.Adapter 字段私有,仅可调用方法
platform/satori → *satori.Client 字段私有,仅可调用方法
platform/discord → *discordgo.Session 第三方 SDK,**字段公开**,
只读使用其方法,勿修改内部状态
platform/telegram → *telegram.Client 字段私有,仅可调用方法
实现方为各平台的 sender 类型(onebot.Sender 为公开类型,其余为包内 私有类型);调用方无需关心实现类型,通过 GetPlatformAPIAs 断言 上表列出的公开句柄类型即可。
使用 GetPlatformAPIAs(推荐)或 GetPlatformAPI 直接断言。
type Adapter ¶
type Adapter interface {
// Platform 返回平台标识符(小写,如 "qq"、"discord"、"telegram")
Platform() string
// Start 启动适配器事件循环(阻塞,直到 ctx 取消或出错)
//
// 每收到一个事件,调用 handler(event)。
// handler 应快速返回(框架内部会在 goroutine 中处理)。
Start(ctx stdctx.Context, handler func(Event)) error
// Stop 优雅停止适配器
Stop(ctx stdctx.Context) error
// Sender 返回该平台的消息发送接口
Sender() Sender
// Capabilities 返回该平台支持的特性集合。
// 用于 Handler 做跨平台特性检测,实现渐进增强策略。
Capabilities() Capabilities
// IsRunning 返回适配器当前是否处于运行状态。
//
// 在 Start() 成功启动后返回 true,Stop() 完成后返回 false。
// 用于健康检查和监控,实现应保证并发安全。
IsRunning() bool
}
Adapter 是平台适配器的核心接口。
每个平台(QQ、Discord、Telegram 等)实现此接口, 框架核心通过此接口接收事件和发送消息,不依赖任何平台 SDK。
生命周期:
Start() ──→ [事件循环,持续调用 handler] ──→ Stop()
type AdapterObserver ¶
type AdapterObserver interface {
// OnAdapterStarted 适配器 goroutine 启动时调用(Start 开始阻塞前)。
OnAdapterStarted(platform string)
// OnAdapterStopped 适配器 goroutine 退出时调用(无论是否有错误)。
OnAdapterStopped(platform string)
// OnAdapterError 适配器以非 context 取消/超时错误退出时调用。
// errMsg 为 error.Error() 文本,避免直接传递 error 接口引起 alloc。
OnAdapterError(platform, errMsg string)
// OnAdapterDisconnect RecoverableAdapter 意外断连时调用。
OnAdapterDisconnect(platform string, err error)
}
AdapterObserver 接收 Registry 适配器生命周期事件,用于可观测性集成。
所有方法均在调用方 goroutine 中**同步**执行,实现应保证非阻塞(如仅递增计数器)。 如需进行耗时操作(日志写入、网络调用),请在实现内部使用异步队列。
使用示例(注册 metrics 观察者):
reg := platform.NewRegistry().WithObserver(mc.PlatformObserver()) reg.StartAll(ctx, handler)
type Announcement ¶ added in v1.34.0
type Announcement struct {
// ID 公告 ID(用于删除)。
ID string
// Content 公告内容。
Content string
// PublisherID 发布者用户 ID。
PublisherID string
// ImageURL 公告配图 URL(平台不提供时为空)。
ImageURL string
// Timestamp Unix 时间戳(秒)。
Timestamp int64
// Extra 平台特有扩展字段(key-value 形式)。
Extra map[string]any
}
Announcement 是群公告的跨平台摘要。
平台特有字段(如置顶、生效时间)通过 Extra 携带(key-value 形式)。
type AnnouncementManager ¶ added in v1.34.0
type AnnouncementManager interface {
// SendAnnouncement 发布群公告。
// imageURL 为可选配图 URL,空字符串表示纯文字公告。
// 平台不支持时返回 [ErrNotSupported]。
SendAnnouncement(ctx stdctx.Context, groupID, content, imageURL string) error
// GetAnnouncements 获取群公告列表。
// 平台不支持时返回 [ErrNotSupported]。
GetAnnouncements(ctx stdctx.Context, groupID string) ([]Announcement, error)
}
AnnouncementManager 可选接口:支持群公告的平台适配器 Sender 实现此接口。
使用前用 GetAnnouncementManager 检查支持:
if am, ok := platform.GetAnnouncementManager(adapter); ok {
_ = am.SendAnnouncement(ctx, groupID, "公告内容")
}
func GetAnnouncementManager ¶ added in v1.34.0
func GetAnnouncementManager(a Adapter) (AnnouncementManager, bool)
GetAnnouncementManager 安全获取适配器 Sender 的群公告接口。
若 Sender 未实现 AnnouncementManager,返回 (nil, false)。
type Attachment ¶
type Attachment struct {
// Kind 附件媒体类型(出站必填;入站平台可推断时填充)
Kind AttachmentKind
// URL 附件远程 URL(出站发送 / 入站接收)
URL string
// Data 本地二进制数据(出站直传,URL 为空时使用;入站为空)
Data []byte
// MimeType MIME 类型,如 "image/png"(可选,辅助平台正确处理)
MimeType string
// Name 文件名(与 Data 或 URL 配合;平台不支持时可忽略)
Name string
// Size 文件大小(字节),入站平台不提供时为 0
Size int
// Width 图片/视频宽度(像素),非媒体类型或平台不提供时为 0
Width int
// Height 图片/视频高度(像素),非媒体类型或平台不提供时为 0
Height int
// Extra 平台专属扩展元数据(key-value 形式),无扩展数据时为 nil。
//
// 已知键(见各平台 extra.go 中的常量定义):
// - "voice": QQ 语音附件的 WAV 链接与 ASR 文本(*qq.VoiceAttachmentMeta)
// - "button": QQ 按钮权限控制(*qq.ButtonExtra)
Extra map[string]any
}
Attachment 单个附件(出站发送与入站接收共用)。
出站语义(Sender 使用):
- URL 与 Data 互斥:URL 非空 → 远程 URL 发送;Data 非空 → 二进制直传
- 两者均为空时,Sender 应将其忽略
入站语义(Event.Attachments 返回):
- 平台填充能力不同,无法提供的字段返回零值
- Size/Width/Height 为接收时元信息,出站时忽略(零值无害)
平台专属扩展元数据通过 Extra 携带(key-value 形式):
att.Extra = map[string]any{"wav_url": ..., "asr_text": ...} // QQ 语音
att.Extra["resource_id"] = "r1" // 平台资源 ID
func AttachmentFromData ¶
func AttachmentFromData(kind AttachmentKind, data []byte) Attachment
AttachmentFromData 用本地二进制数据构造附件。
URL 与 Data 互斥;需要远程 URL 时请使用 AttachmentFromURL。
使用示例:
att := platform.AttachmentFromData(platform.AttachmentKindFile, pdfBytes) att.Name = "report.pdf" att.MimeType = "application/pdf"
func AttachmentFromURL ¶
func AttachmentFromURL(kind AttachmentKind, url string) Attachment
AttachmentFromURL 用远程 URL 构造附件。
URL 与 Data 互斥;需要二进制直传时请使用 AttachmentFromData。
使用示例:
att := platform.AttachmentFromURL(platform.AttachmentKindImage, "https://example.com/img.png")
func Attachments ¶ added in v1.35.0
func Attachments(e Event) []Attachment
Attachments 返回事件携带的附件列表(段派生,仅 image/audio/video/file)。
平台不支持附件或消息无附件时返回 nil。
func QuoteAttachments ¶ added in v1.41.2
func QuoteAttachments(segs []Segment) []Attachment
QuoteAttachments 返回首个 reply 段携带的归一化被引用附件列表 (Extra[SegmentExtraQuoteAtts],类型恒为 []Attachment)。 无 reply 段或未填充时返回 nil。
func SegmentsAttachments ¶ added in v1.34.0
func SegmentsAttachments(segs []Segment) []Attachment
SegmentsAttachments 从有序消息段中提取附件列表(仅 image/audio/video/file,按出现顺序)。
type AttachmentKind ¶
type AttachmentKind string
AttachmentKind 附件媒体类型枚举。
const ( // AttachmentKindImage 图片 AttachmentKindImage AttachmentKind = "image" // AttachmentKindAudio 音频/语音 AttachmentKindAudio AttachmentKind = "audio" // AttachmentKindVideo 视频 AttachmentKindVideo AttachmentKind = "video" // AttachmentKindFile 通用文件 AttachmentKindFile AttachmentKind = "file" )
type AutoModerator ¶
type AutoModerator interface {
// DeleteMemberMessage 删除群成员发送的消息(机器人需有管理员权限)。
DeleteMemberMessage(ctx stdctx.Context, groupID, messageID string) error
// MuteAll 开启/关闭全体禁言(mute=true 开启,false 解除)。
// 平台不支持时返回 [ErrNotSupported]。
MuteAll(ctx stdctx.Context, groupID string, mute bool) error
}
AutoModerator 可选接口:支持自动化内容审核/撤回的平台适配器 Sender 实现此接口。
与 MessageDeleter 的区别:
- MessageDeleter:撤回**机器人自己**发送的消息
- AutoModerator:撤回/屏蔽**他人**发送的消息(需管理员权限)
使用前用 GetAutoModerator 检查支持:
if am, ok := platform.GetAutoModerator(adapter); ok {
_ = am.DeleteMemberMessage(ctx, groupID, messageID)
}
func GetAutoModerator ¶
func GetAutoModerator(a Adapter) (AutoModerator, bool)
GetAutoModerator 安全获取适配器 Sender 的自动审核接口。
若 Sender 未实现 AutoModerator,返回 (nil, false)。
type AvatarProvider ¶
type AvatarProvider interface {
// GetUserAvatarURL 获取指定用户的头像 URL。
// userID 为平台用户唯一 ID。
// 平台不支持时返回 [ErrNotSupported]。
GetUserAvatarURL(ctx stdctx.Context, userID string) (string, error)
}
AvatarProvider 可选接口:支持获取用户/群组头像 URL 的平台适配器 Sender 实现此接口。
适用于需要在消息中展示用户头像的场景(如 /头像 命令、个人资料卡片)。
使用前用 GetAvatarProvider 检查支持:
if ap, ok := platform.GetAvatarProvider(adapter); ok {
url, err := ap.GetUserAvatarURL(ctx, userID)
}
func GetAvatarProvider ¶
func GetAvatarProvider(a Adapter) (AvatarProvider, bool)
GetAvatarProvider 安全获取适配器 Sender 的头像查询接口。
若 Sender 未实现 AvatarProvider,返回 (nil, false)。
type BotIdentity ¶
type BotIdentity interface {
// BotID 返回机器人在当前平台的唯一标识符。
//
// 与 event.Sender().ID 对比可判断事件是否由机器人自身触发。
// 平台未提供或尚未连接时返回空字符串。
BotID() string
// BotName 返回机器人的显示名称(昵称/用户名)。
//
// 平台未提供时返回空字符串。
BotName() string
}
BotIdentity 是机器人自身身份信息的可选接口。
支持获取机器人自身 ID/名称的平台适配器应实现此接口, 便于 Handler 做"防止自回复"判断、日志标注等操作。
使用示例:
// 防止自回复
if botID := platform.GetBotID(adapter); botID != "" {
if event.Sender().ID == botID {
return // 忽略自身发出的消息
}
}
// 直接类型断言(需要同时访问多个字段时更高效)
if bi, ok := adapter.(platform.BotIdentity); ok {
log.Printf("bot %s (%s) online", bi.BotName(), bi.BotID())
}
type Button ¶
type Button struct {
// ID 按钮回调标识符(如 Discord 的 custom_id、Telegram 的 callback_data)
ID string
// Label 按钮显示文字
Label string
// URL 链接目标(Style 为 ButtonStyleLink 时有效)
URL string
// Command 指令按钮文本(如 "/help")。
//
// 非空时按钮为"指令按钮":点击后把命令插入输入框(如 QQ 的
// action.type=2,自动插入 "@bot <Command>"),不产生交互回调事件,
// 由用户自行发送。平台不支持指令按钮时忽略此字段。
Command string
// Style 按钮样式
Style ButtonStyle
// Disabled 按钮是否置灰不可点击(Discord/QQ 均支持)
Disabled bool
// Row 按钮所在行。
//
// - ButtonRowAuto(0,零值默认):由平台自动排列,每个此值的按钮独占一行
// - 1 ~ 5:显式行号,相同 Row 值的按钮排列在同一行(1 = 第一行)
//
// Discord 最多 5 行,每行最多 5 个按钮;超出部分截断。
Row int
// Emoji 按钮前展示的 emoji(Discord 原生支持,其他平台忽略)
Emoji string
// Extra 平台专属按钮扩展字段(key-value 形式)。
//
// 用于携带通用字段无法表达的平台特定配置,各平台 Sender 读取。
// 不支持此字段的平台可安全忽略。
//
// 已知键(见各平台 extra.go 中的常量定义):
// - "button": QQ 按钮权限控制(*qq.ButtonExtra)
// - "inline": Telegram switch_inline_query 等扩展字段(*telegram.InlineButtonExtra)
Extra map[string]any
}
Button 代表一个平台无关的交互按钮。
适用于 Discord 消息组件、Telegram 内联键盘、QQ 机器人键盘等。 各平台 Sender 负责将此结构映射到平台特定格式; 不支持按钮的平台可忽略此字段。
type ButtonStyle ¶
type ButtonStyle string
ButtonStyle 按钮样式枚举(各平台尽力映射到最接近的原生样式)
const ( // ButtonStylePrimary 主要操作按钮(蓝色/强调色) ButtonStylePrimary ButtonStyle = "primary" // ButtonStyleSecondary 次要操作按钮(灰色/默认色) ButtonStyleSecondary ButtonStyle = "secondary" // ButtonStyleDanger 危险操作按钮(红色/警告色) ButtonStyleDanger ButtonStyle = "danger" // ButtonStyleLink 链接按钮(点击后跳转 URL) ButtonStyleLink ButtonStyle = "link" )
type Capabilities ¶
type Capabilities struct {
// Markdown 是否支持 Markdown 格式消息
Markdown bool
// Buttons 是否支持交互按钮(内联键盘等)
Buttons bool
// MultiAttachment 是否支持在一条消息中发送多个附件
MultiAttachment bool
// MessageEdit 是否支持编辑已发送消息(实现 MessageEditor)
MessageEdit bool
// MessageDelete 是否支持删除/撤回消息(实现 MessageDeleter)
MessageDelete bool
// Embeds 是否支持富文本嵌入卡片
Embeds bool
// FileUpload 是否支持二进制文件直传(非 URL,Attachment.Data)
FileUpload bool
// GuildSupport 是否有服务器/频道层级(ChatInfo.ParentID 有效)
GuildSupport bool
// Reactions 是否支持表情回应(Discord/Telegram/QQ 均支持)
Reactions bool
// ThreadReply 是否支持消息回复链/引用回复
ThreadReply bool
// TypingIndicator 是否支持"正在输入"状态
TypingIndicator bool
// MentionAll 是否支持 @全体成员
MentionAll bool
// VoiceChannel 是否支持语音频道(Discord Stage/VC)
VoiceChannel bool
// Caption 是否支持在同一条消息内同时携带文本与附件(图文同发)。
// Telegram(媒体 caption)、Discord(content+附件)、OneBot(CQ 码混排)、
// Satori(元素列表)、QQ(msg_type=7 携带 media+content,单媒体)支持。
Caption bool
// Forward 是否支持合并转发(发送与接收)。
// 例:OneBot/QQ 原生支持;Discord/Telegram 不支持(出站转发时降级)。
Forward bool
// MaxTextLength 单条文本消息最大字符数。
// 例:Discord=2000,Telegram=4096,QQ=0(未公开)。
MaxTextLength int
// MaxAttachmentMB 单个附件最大大小(MB)。
// 例:Discord=8,Telegram=50,QQ=0(未公开)。
MaxAttachmentMB int
// MaxButtonsPerRow 每行最多按钮数(0=无已知限制)。
// 例:Discord/QQ=5。
MaxButtonsPerRow int
// MaxButtonRows 最多按钮行数(0=无已知限制)。
// 例:Discord/QQ=5。
MaxButtonRows int
// MaxEmbedFields 单个 Embed 最多字段数(0=无已知限制)。
// 例:Discord=25。
MaxEmbedFields int
}
Capabilities 声明平台支持的特性集合。
平台适配器通过 Capabilities() 返回此结构,允许 Handler 在运行时 做跨平台特性检测,实现"渐进增强"策略(优先使用丰富特性,降级到纯文本)。
示例(字段访问方式):
caps := ctx.GetPlatformCapabilities()
if caps.Embeds {
msg = platform.TextMessage("").WithEmbeds(myEmbed)
} else {
msg = platform.MarkdownMessage(myEmbed.Title + "\n" + myEmbed.Description)
}
示例(Has() 方式,推荐用于条件判断):
if caps.Has(platform.CapEmbeds) {
msg = platform.TextMessage("").WithEmbeds(myEmbed)
}
func (Capabilities) Has ¶
func (c Capabilities) Has(flags CapabilityFlag) bool
Has 报告平台是否具备指定的能力标志。
推荐在条件判断中使用此方法代替直接访问布尔字段, 以便将来通过追加 CapabilityFlag 常量引入新能力而不修改 Capabilities 布局:
if caps.Has(platform.CapMarkdown | platform.CapEmbeds) {
// 同时支持 Markdown 和 Embeds
}
type CapabilityFlag ¶
type CapabilityFlag uint64
CapabilityFlag 是平台布尔能力的位掩码类型。
新增能力时只需追加新常量,无需修改 Capabilities 结构体布局。 建议在 Handler 中优先使用 Capabilities.Has 进行能力检查。
const ( CapMarkdown CapabilityFlag = 1 << iota // 支持 Markdown 格式消息 CapButtons // 支持交互按钮(内联键盘等) CapMultiAttachment // 支持在一条消息中发送多个附件 CapMessageEdit // 支持编辑已发送消息 CapMessageDelete // 支持删除/撤回消息 CapEmbeds // 支持富文本嵌入卡片 CapFileUpload // 支持二进制文件直传(非 URL) CapGuildSupport // 有服务器/频道层级(ChatInfo.ParentID 有效) CapReactions // 支持表情回应 CapThreadReply // 支持消息回复链/引用回复 CapTypingIndicator // 支持"正在输入"状态 CapMentionAll // 支持 @全体成员 CapVoiceChannel // 支持语音频道 CapCaption // 支持在同一条消息内同时携带文本与附件(图文同发) CapForward // 支持合并转发(发送与接收) )
type ChatInfo ¶
type ChatInfo struct {
// ID 会话/群组/频道唯一标识
// 私聊:用户 ID;群组:群 ID;频道/话题:channel_id
ID string
// ParentID 父容器唯一标识(服务器/频道层级时使用)。
//
// 频道消息:guild_id / 服务器 ID
// Discord: guild_id;QQ 频道: guild_id
// 私聊和普通群组为空字符串。
ParentID string
// Name 会话名称(可选,部分平台不提供)
Name string
// IsGroup 是否为群组/频道消息(false = 私聊)
IsGroup bool
// IsDM 是否为私信(Direct Message)会话。
//
// 与 IsGroup=true、ParentID 非空同时成立时,
// 表示这是一条频道私信(如 QQ DIRECT_MESSAGE_CREATE),
// 发送回复时应使用 DM 专属接口而非普通频道消息接口。
IsDM bool
// Tokens 平台专属授权令牌,用于平台内部路由或被动回复授权。
//
// 各平台适配器在解析事件时写入,平台 Sender 在发送时读取。
// 框架层 handler 通常无需直接访问此字段。
//
// 已知 token 键(见各平台 extra.go 中的常量定义):
// - QQ: TokenMsgID ("msg_id")、TokenEventID ("event_id")
//
// 读取 nil map 是安全的(返回空字符串),写入前须先初始化。
Tokens map[string]string
}
ChatInfo 代表消息所在会话的基本信息。
type DegradePolicy ¶ added in v1.34.0
DegradePolicy 是跨平台转发时对段的处置策略。
type DirectedAtBotEvent ¶ added in v1.60.0
type DirectedAtBotEvent interface {
// DirectedAtBot 返回本条消息在平台语义上是否直接发给机器人。
DirectedAtBot() bool
}
DirectedAtBotEvent 是"消息本身即指向机器人"感知的可选接口。
部分平台的事件类型已经隐含"这条消息 @ 了机器人",但载荷无法表达 @: QQ 群 @机器人(GROUP_AT_MESSAGE_CREATE)与频道 @机器人(AT_MESSAGE_CREATE) 的事件类型即代表 @ 机器人,payload 不含 mentions 数组(该字段仅 GROUP_MESSAGE_CREATE 提供),正文里的 <@id> 占位符也被服务端替换为空格。
这类事件若不声明本接口,GetMentions 恒为空,MentionedBot 与 [OnMentionedBot] 会判定"没有 @ 机器人"——表现为需 @ 触发的插件在 QQ 群 @机器人 时静默无响应、群策略要求 @ 时消息被丢弃。
框架通过 MentionedBot 帮助函数安全访问,无需直接断言。
type DisconnectNotifier ¶
type DisconnectNotifier struct {
// contains filtered or unexported fields
}
DisconnectNotifier 是 RecoverableAdapter.OnDisconnect 的共享实现。
平台适配器嵌入此类型即可获得断连回调的注册与通知能力:
type MyAdapter struct {
DisconnectNotifier
// ...
}
嵌入后 adapter.OnDisconnect(fn) 和 adapter.NotifyDisconnect(err) 自动可用, 无需在每个适配器中重复实现。
func (*DisconnectNotifier) NotifyDisconnect ¶
func (n *DisconnectNotifier) NotifyDisconnect(err error)
NotifyDisconnect 通知所有已注册的断连回调。
适配器在意外断连时调用此方法;若无已注册回调则跳过(零分配)。
func (*DisconnectNotifier) OnDisconnect ¶
func (n *DisconnectNotifier) OnDisconnect(fn func(error)) (unregister func())
OnDisconnect 注册断连回调,返回注销函数。参见 RecoverableAdapter.OnDisconnect。
type EditableEvent ¶
type EditableEvent interface {
// IsEdited 返回此事件是否为消息编辑事件
IsEdited() bool
// OriginalTimestamp 返回原始消息的发送时间戳(零值表示不可用)
OriginalTimestamp() time.Time
}
EditableEvent 是消息编辑感知的可选接口(D3)。
支持消息编辑事件的平台(Discord、Telegram)实现此接口。 使用示例:
if ee, ok := event.(platform.EditableEvent); ok && ee.IsEdited() {
log.Printf("消息已编辑,原始时间戳: %v", ee.OriginalTimestamp())
}
type Embed ¶
type Embed struct {
// Title 标题
Title string
// Description 正文描述(支持 Markdown,平台不支持时降级为纯文本)
Description string
// URL 标题跳转链接(可选)
URL string
// Color 边框/主题颜色,RGB 十六进制无符号整数,如 0x5865F2(Discord 蓝)
Color uint32
// Fields 字段列表
Fields []EmbedField
// ImageURL 正文大图 URL
ImageURL string
// ThumbnailURL 右上角缩略图 URL
ThumbnailURL string
FooterText string
// Timestamp 时间戳(显示在页脚,零值表示不展示)
Timestamp time.Time
}
Embed 富文本嵌入卡片(Discord 风格,其他平台尽力映射)。
各平台支持程度:
- Discord: 原生支持全部字段
- Telegram: 映射为格式化文本消息,图片单独发送
- QQ: 映射为 Markdown 或纯文本(仅 Title/Description/Fields)
- 不支持的平台可安全忽略此字段
type EmbedField ¶
type EmbedField struct {
// Name 字段标题
Name string
// Value 字段内容
Value string
// Inline 是否与相邻字段同行展示(Discord 支持,其他平台忽略)
Inline bool
}
EmbedField Embed 内的单个字段行。
type Emoji ¶
type Emoji struct {
// Kind 表情种类
Kind EmojiKind
// ID 平台内部 emoji ID。
// 标准 Unicode 表情此字段为空,直接使用 Value。
ID string
// Value emoji 字面量或名称。
// Unicode 表情填字符本身(如 "👍");自定义表情填显示名称(如 "myEmoji")。
Value string
}
Emoji 平台无关的表情标识,用于 ReactionSender 接口。
各平台 Sender 根据 Kind 将其映射到平台特定格式:
- Discord: unicode → Value 直接传入;custom → "Value:ID"
- Telegram: unicode → Value;custom → 自定义 emoji ID
- QQ: system → (emojiType=1, emojiID=ID);unicode → (emojiType=2, emojiID=Value)
使用示例:
// Unicode 点赞
platform.Emoji{Kind: platform.EmojiKindUnicode, Value: "👍"}
// Discord 自定义 emoji
platform.Emoji{Kind: platform.EmojiKindCustom, ID: "123456789", Value: "myEmoji"}
// QQ 内置系统表情(表情 ID=405)
platform.Emoji{Kind: platform.EmojiKindSystem, ID: "405"}
type Event ¶
type Event interface {
EventIdentity
// Segments 返回有序消息段(唯一真相源)。
//
// 正文/附件便捷视图由帮助函数派生:
// - Content(e) = TrimSpace(SegmentsContent(e.Segments()))
// - Attachments(e) = SegmentsAttachments(e.Segments())
Segments() []Segment
// Sender 返回消息发送者信息
Sender() UserInfo
// Chat 返回消息所在会话信息
Chat() ChatInfo
// Timestamp 返回事件时间戳(尽力而为,平台不提供时返回零值)
Timestamp() time.Time
}
Event 是平台无关的事件抽象接口(最小必要集合)。
组合 EventIdentity(路由/去重),外加 Segments、Sender、Chat、Timestamp。 消息内容(正文/附件)不占用接口方法——由 Content / Attachments 帮助函数 从 [Segments] 统一派生,杜绝平台实现与段不一致。
各平台适配器将原始 payload 包装为 Event 实现, 框架核心只依赖此接口,不直接引用任何平台特定结构体。
调用方可选择性收窄依赖:
- 仅需去重追踪:依赖 EventIdentity(如 dedup 中间件)
- 仅需消息内容:依赖 Content / Attachments(从段派生)
- 完整事件处理:依赖 Event
平台特定或可选功能通过独立接口扩展:
- RawEvent:访问平台原始类型字符串和 payload
- EditableEvent:判断消息是否为编辑版本
- ReplyEvent:获取被回复消息的 ID(回复链/消息线程)
使用类型断言检测可选能力:
if re, ok := event.(platform.ReplyEvent); ok {
replyID := re.ReplyToID()
}
或使用包级帮助函数(优先推荐):
rawType := platform.RawType(event) // 若不支持则返回 "" replyID := platform.GetReplyToID(event) // 若不支持则返回 ""
type EventIdentity ¶
type EventIdentity interface {
// Platform 返回平台标识符(如 "qq"、"discord"、"telegram")
Platform() string
// Kind 返回平台无关的事件类别
Kind() EventKind
// ID 返回平台级别的唯一事件标识符。
//
// 用途:去重、追踪、死信队列等需要唯一标识的场景。
// 平台不提供时返回空字符串;调用方应对空字符串做兼容处理。
ID() string
}
EventIdentity 是事件路由和去重标识接口。
包含 Platform(路由到对应适配器)、Kind(事件分类路由)、 ID(去重/追踪)。中间件(dedup、retry、deadletter)通常只需此接口。
type EventKind ¶
type EventKind string
EventKind 平台无关的事件类别枚举。
每个平台的具体事件类型(如 dto.EventType)映射到此枚举, 供 Engine 的 Matcher 做通用路由(无需感知平台细节)。
const ( // EventKindUnknown 未知/未映射事件 EventKindUnknown EventKind = "UNKNOWN" // EventKindPrivateMessage 私聊消息(QQ C2C、Telegram 私聊、Discord DM 等) EventKindPrivateMessage EventKind = "PRIVATE_MESSAGE" // EventKindGroupMessage 群组消息(QQ 群、Discord 频道等) EventKindGroupMessage EventKind = "GROUP_MESSAGE" // EventKindGuildMessage 频道/服务器消息(QQ频道、Discord 服务器等) EventKindGuildMessage EventKind = "GUILD_MESSAGE" // EventKindNotice 通知类事件(通用兜底,平台无法精确归类时使用)。 // // 优先使用下方的细粒度 Kind(BotAdded / FriendAdded 等); // 仅当确实无法归类时才使用此值,配合 platform.RawType(event) 做进一步区分。 EventKindNotice EventKind = "NOTICE" // EventKindRequest 请求类事件(加好友请求、加群请求等) EventKindRequest EventKind = "REQUEST" // EventKindSystem 系统事件(Ready、Resumed 等) EventKindSystem EventKind = "SYSTEM" // EventKindInteraction 交互事件(按钮回调、斜杠命令、下拉菜单等) // // Discord Interaction、QQ 机器人 v2 按钮回调、Telegram 内联键盘回调。 EventKindInteraction EventKind = "INTERACTION" // EventKindReaction 消息表情回应(添加或移除) // // Discord 表情回应、Telegram 表情回应、QQ 表情回应。 EventKindReaction EventKind = "REACTION" // EventKindMemberJoin 普通成员加入群组/服务器事件(非机器人自身)。 // // 机器人自身被加入群组/频道请使用 [EventKindBotAdded]。 EventKindMemberJoin EventKind = "MEMBER_JOIN" // EventKindMemberLeave 普通成员离开/被踢出群组/服务器事件(非机器人自身)。 // // 机器人自身被移出群组/频道请使用 [EventKindBotRemoved]。 EventKindMemberLeave EventKind = "MEMBER_LEAVE" // EventKindMemberUpdate 成员信息变更(昵称、角色、权限等)。 // // QQ 频道 GuildMemberUpdate、Discord guild_member_update 等。 EventKindMemberUpdate EventKind = "MEMBER_UPDATE" // EventKindMessageUpdate 消息被编辑 // // Discord 消息编辑、Telegram 消息编辑。 EventKindMessageUpdate EventKind = "MESSAGE_UPDATE" // EventKindMessageDelete 消息被撤回/删除 EventKindMessageDelete EventKind = "MESSAGE_DELETE" // EventKindBotAdded 机器人自身被加入某个群组/频道/服务器。 // // QQ: GROUP_ADD_ROBOT(被加入群)、GUILD_CREATE(被加入频道) // Discord: guild_create(机器人加入新服务器) EventKindBotAdded EventKind = "BOT_ADDED" // EventKindBotRemoved 机器人自身被移出群组/频道/服务器。 // // QQ: GROUP_DEL_ROBOT(被移出群)、GUILD_DELETE(被移出频道) // Discord: guild_delete(机器人离开服务器) EventKindBotRemoved EventKind = "BOT_REMOVED" // EventKindFriendAdded 新好友/关注者。 // // QQ: FRIEND_ADD(C2C 场景用户添加机器人为好友/关注) EventKindFriendAdded EventKind = "FRIEND_ADDED" // EventKindFriendRemoved 好友/关注者移除。 // // QQ: FRIEND_DEL(C2C 场景用户删除机器人好友/取消关注) EventKindFriendRemoved EventKind = "FRIEND_REMOVED" // EventKindMsgPermissionChange 消息权限变更(消息下发开启/关闭)。 // // QQ: GROUP_MSG_REJECT(群关闭机器人消息)、GROUP_MSG_RECEIVE(群开启机器人消息)、 // C2C_MSG_REJECT(C2C 关闭机器人消息)、 C2C_MSG_RECEIVE(C2C 开启机器人消息) EventKindMsgPermissionChange EventKind = "MSG_PERMISSION_CHANGE" // EventKindChannelChange 子频道(channel)创建、更新或删除。 // // QQ: CHANNEL_CREATE / CHANNEL_UPDATE / CHANNEL_DELETE // Discord: channel_create / channel_update / channel_delete EventKindChannelChange EventKind = "CHANNEL_CHANGE" // EventKindGuildChange 服务器/频道(guild)信息更新(非加入/离开)。 // // QQ: GUILD_UPDATE;Discord: guild_update EventKindGuildChange EventKind = "GUILD_CHANGE" // EventKindMessageAudit 消息审核结果通知。 // // QQ: MESSAGE_AUDIT(主动消息推送后的审核结果回调) EventKindMessageAudit EventKind = "MESSAGE_AUDIT" )
type ForwardNode ¶ added in v1.54.0
type ForwardNode struct {
// Sender 子消息发送者(多数平台仅能提供昵称,ID 可能不可得)。
Sender UserInfo
// Segments 子消息内容段(text/image/audio/video/file,保序)。
Segments []Segment
// Kind 条目语义类型:""(普通消息)或 ForwardKindQuote / ForwardKindRecord。
Kind ForwardNodeKind
// Related 关联子条目:Kind=ForwardKindQuote 时为被引用消息的渲染
// (通常 1 条),Kind=ForwardKindRecord 时为嵌套转发记录的消息列表。
Related []ForwardNode
}
ForwardNode 合并转发记录中的单条子消息(或嵌套引用/嵌套记录条目)。
type ForwardNodeKind ¶ added in v1.54.0
type ForwardNodeKind string
ForwardNodeKind 合并转发条目的语义类型。
const ( // ForwardKindQuote 引用条目:Related 为被引用消息的渲染(通常 1 条)。 ForwardKindQuote ForwardNodeKind = "quote" // ForwardKindRecord 合并转发条目:Related 为嵌套转发记录的消息列表。 ForwardKindRecord ForwardNodeKind = "forward_record" )
type ForwardOption ¶ added in v1.34.0
type ForwardOption func(*forwardConfig)
ForwardOption 是 MessageToOutbound 的可选配置。
func WithDegrade ¶ added in v1.34.0
func WithDegrade(p DegradePolicy) ForwardOption
WithDegrade 覆盖默认降级策略(默认使用内置处置表)。
策略接收待处置段,返回降级产物(0 个 = 剥离,1+ 个 = 替换为该组段)。
func WithTargetPlatform ¶ added in v1.34.0
func WithTargetPlatform(platformID string) ForwardOption
WithTargetPlatform 声明转发目标平台 ID。
目标平台与消息来源平台(Message.Platform)一致时,reply/face/forward/button/unknown 等平台原生段按「同平台透传」处理(原始数据可还原); 缺省或跨平台时按内置处置表保守降级(reply/unknown 剥离、face 降 text、forward 摘要)。
type ForwardRecord ¶ added in v1.54.0
type ForwardRecord struct {
// Title 记录标题(如 "XX和YY的聊天记录"),可能为空。
Title string
// Nodes 按原始顺序排列的子消息。
Nodes []ForwardNode
}
ForwardRecord 一条合并转发记录。
type GroupInfo ¶
type GroupInfo struct {
// ID 群组唯一标识
ID string
// Name 群组名称
Name string
// MemberCount 成员数量(平台不提供时为 0)
MemberCount int
// Description 群组简介(平台不提供时为空字符串)
Description string
}
GroupInfo 群组基本信息。
由 GroupInfoProvider.GetGroupInfo 返回;各平台填充能力不同, 未知字段返回零值。
type GroupInfoProvider ¶
type GroupInfoProvider interface {
// GetGroupInfo 查询群组基本信息(名称、成员数等)。
//
// 平台不支持时返回 [ErrNotSupported]。
GetGroupInfo(ctx stdctx.Context, groupID string) (GroupInfo, error)
// GetGroupMemberList 获取群所有成员的基本信息列表。
//
// 大型群组下列表可能很长,各平台可能有分页限制。
// 平台不支持时返回 [ErrNotSupported]。
GetGroupMemberList(ctx stdctx.Context, groupID string) ([]GroupMemberInfo, error)
// GetGroupMember 查询指定群成员的详细信息。
//
// 成员不存在或被踢出时返回错误;平台不支持时返回 [ErrNotSupported]。
GetGroupMember(ctx stdctx.Context, groupID, userID string) (GroupMemberInfo, error)
// GetJoinedGroups 返回机器人当前已加入的群组 ID 列表。
//
// 用于定时推送(整点报时、每日新闻)等需要枚举目标群的场景。
// 平台不支持时返回 [ErrNotSupported]。
GetJoinedGroups(ctx stdctx.Context) ([]GroupInfo, error)
}
GroupInfoProvider 可选接口:支持查询群组信息的平台适配器 Sender 实现此接口。
与 GroupManager(成员管理)独立——GroupManager 用于"写"操作(踢人/禁言), GroupInfoProvider 用于"读"操作(查询成员列表、群名等)。
使用前用 GetGroupInfoProvider 检查支持:
if gip, ok := platform.GetGroupInfoProvider(adapter); ok {
members, err := gip.GetGroupMemberList(ctx, groupID)
}
func GetGroupInfoProvider ¶
func GetGroupInfoProvider(a Adapter) (GroupInfoProvider, bool)
GetGroupInfoProvider 安全获取适配器 Sender 的群组信息查询接口。
若 Sender 未实现 GroupInfoProvider,返回 (nil, false)。
使用示例:
if gip, ok := platform.GetGroupInfoProvider(adapter); ok {
members, err := gip.GetGroupMemberList(ctx, groupID)
for _, m := range members {
fmt.Println(m.DisplayName, m.UserID)
}
}
type GroupManager ¶
type GroupManager interface {
// KickMember 将指定用户踢出群组。
//
// permanent=true 时拉黑(禁止重新加入),false 时仅踢出。
// 平台不支持 permanent 时应忽略并返回 nil。
KickMember(ctx stdctx.Context, groupID, userID string, permanent bool) error
// BanMember 禁言/解禁指定用户。
//
// duration 为禁言时长;传入 0 表示解除禁言。
BanMember(ctx stdctx.Context, groupID, userID string, duration time.Duration) error
// SetAdmin 授予/撤销群内管理员身份。
//
// isAdmin=true 授予管理员;isAdmin=false 撤销。
// 平台不支持时返回 [ErrNotSupported]。
SetAdmin(ctx stdctx.Context, groupID, userID string, isAdmin bool) error
}
GroupManager 可选接口:支持群成员管理操作的平台适配器 Sender 实现此接口。
不同平台对群管理的支持程度不同:
- QQ:支持禁言与踢人(BanMember 走 2026-08 群禁言接口;KickMember 走 2026-09 群成员批量移除接口,permanent=true 同时加入群黑名单); 设置管理员暂不支持
- Discord:支持踢出/禁言(通过 Guild 管理 API)
- Telegram:支持踢出成员(ban/unban)
- WeChat:通常不支持(返回 ErrNotSupported)
使用前用 GetGroupManager 检查支持:
if gm, ok := platform.GetGroupManager(adapter); ok {
_ = gm.BanMember(ctx, groupID, userID, 60)
}
func GetGroupManager ¶
func GetGroupManager(a Adapter) (GroupManager, bool)
GetGroupManager 安全获取适配器 Sender 的群成员管理接口。
若 Sender 未实现 GroupManager,返回 (nil, false)。
type GroupMemberInfo ¶
type GroupMemberInfo struct {
// UserID 用户唯一标识
UserID string
// DisplayName 群内昵称(优先于用户昵称);若平台不提供则为用户全局昵称
DisplayName string
// GroupRole 成员角色(普通/管理/群主)
GroupRole GroupRole
// JoinedAt 加入群组的时间(平台不提供时为零值)
JoinedAt time.Time
// AvatarURL 用户头像 URL(平台不提供时为空字符串)
AvatarURL string
}
GroupMemberInfo 群成员信息。
由 GroupInfoProvider.GetGroupMemberList / GroupInfoProvider.GetGroupMember 返回。
type GroupRole ¶
type GroupRole int
GroupRole 发送者在当前群/频道中的角色等级。
仅在群组消息中有意义;私聊场景值为 GroupRoleUnknown。 各平台填充能力不同:Discord 通过 Member.Permissions 推断; QQ 群消息暂不在事件 payload 中提供(需额外 API 调用)。
type GroupSettings ¶ added in v1.34.0
type GroupSettings interface {
// SetGroupName 修改群名称。
// 平台不支持时返回 [ErrNotSupported]。
SetGroupName(ctx stdctx.Context, groupID, name string) error
// SetGroupCard 设置群成员名片(备注名)。
// card 为空字符串时清除名片。
// 平台不支持时返回 [ErrNotSupported]。
SetGroupCard(ctx stdctx.Context, groupID, userID, card string) error
// SetGroupSpecialTitle 设置群成员专属头衔。
// title 为空字符串时清除头衔。
// 平台不支持时返回 [ErrNotSupported]。
SetGroupSpecialTitle(ctx stdctx.Context, groupID, userID, title string) error
// LeaveGroup 退出群组;dismiss 为 true 时尝试解散群(仅群主)。
// 平台不支持时返回 [ErrNotSupported]。
LeaveGroup(ctx stdctx.Context, groupID string, dismiss bool) error
}
GroupSettings 可选接口:支持群资料管理的平台适配器 Sender 实现此接口。
覆盖群名称、群名片、专属头衔、退群等群资料操作, 与 GroupManager(成员管理)互补。
使用前用 GetGroupSettings 检查支持:
if gs, ok := platform.GetGroupSettings(adapter); ok {
_ = gs.SetGroupName(ctx, groupID, "新群名")
}
func GetGroupSettings ¶ added in v1.34.0
func GetGroupSettings(a Adapter) (GroupSettings, bool)
GetGroupSettings 安全获取适配器 Sender 的群资料管理接口。
若 Sender 未实现 GroupSettings,返回 (nil, false)。
type HealthDetailer ¶ added in v1.9.0
HealthDetailer 是可选的适配器健康详情接口。
实现此接口的 Adapter 可在健康检查中提供比 IsRunning() 更详细的状态信息, 如连接类型、事件流状态、API 客户端可用性、重连次数等。
AdapterHealthChecker 会在 Check 时自动检查此接口,将返回值合并到 metadata。
使用示例:
func (a *Adapter) HealthDetail() map[string]any {
return map[string]any{
"connection": "webhook",
"event_stream_active": a.isEventStreamOpen(),
}
}
type InvitationHandler ¶
type InvitationHandler interface {
// AcceptGroupInvite 接受群组邀请(inviteID 来自群邀请事件)。
AcceptGroupInvite(ctx stdctx.Context, inviteID string) error
// RejectGroupInvite 拒绝群组邀请(reason 不支持时忽略)。
RejectGroupInvite(ctx stdctx.Context, inviteID, reason string) error
// AcceptFriendRequest 接受好友申请(requestID 来自好友申请事件)。
AcceptFriendRequest(ctx stdctx.Context, requestID string) error
// RejectFriendRequest 拒绝好友申请。
RejectFriendRequest(ctx stdctx.Context, requestID, reason string) error
}
InvitationHandler 可选接口:支持处理好友/群邀请请求的平台适配器 Sender 实现此接口。
使用前用 GetInvitationHandler 检查支持:
if ih, ok := platform.GetInvitationHandler(adapter); ok {
_ = ih.AcceptGroupInvite(ctx, inviteID)
}
func GetInvitationHandler ¶
func GetInvitationHandler(a Adapter) (InvitationHandler, bool)
GetInvitationHandler 安全获取适配器 Sender 的邀请处理接口。
若 Sender 未实现 InvitationHandler,返回 (nil, false)。
type MentionsEvent ¶
type MentionsEvent interface {
// Mentions 返回消息中 @ 的用户列表。
// 包含被 @ 的机器人自身(IsSelf=true 标记,OnMentionedBot 依赖此语义);
// 无 @ 用户时返回 nil。
Mentions() []UserInfo
}
MentionsEvent 是 @ 用户列表感知的可选接口。
消息中携带 @ 用户列表(QQ group_at_message、Discord mentions、 Telegram entities 中的 mention)时,适配器实现此接口。 框架通过 GetMentions 帮助函数安全访问,无需直接断言。
使用示例:
if mentions := platform.GetMentions(event); len(mentions) > 0 {
for _, u := range mentions {
log.Printf("@ 了用户 %s (%s)", u.DisplayName, u.ID)
}
}
type Message ¶ added in v1.34.0
type Message struct {
// ID 平台消息 ID(可用于撤回/编辑)。
ID string
// Platform 消息来源平台 ID(= EventIdentity.Platform(),MessageFromEvent 填充;
// 转发判定用,见 MessageToOutbound)。
Platform string
// Sender 发送者信息。
Sender UserInfo
// Chat 消息所在会话。
Chat ChatInfo
// Segments 有序消息段(唯一真相源)。
Segments []Segment
// Content 消息文本内容(纯文本,不含平台特定格式;= SegmentsContent(Segments()))。
Content string
// Timestamp 消息发送时间(平台不提供时为零值)。
Timestamp time.Time
// Attachments 消息携带的附件列表(= SegmentsAttachments(Segments()))。
Attachments []Attachment
// Mentions 消息中 @ 的用户列表。
Mentions []UserInfo
// ReplyToID 被回复消息的平台原生 ID(非回复时为空)。
ReplyToID string
// Extra 平台特有扩展字段(key-value 形式)。
Extra map[string]any
}
Message 是入站消息的静态快照(平台无关)。
字段语义与 Event 接口一致,用于历史消息、消息检索等 需要"拿到一条完整消息数据"的场景(事件流使用动态 Event 接口)。
与 OutboundMessage 的区别:Message 是接收视角(含 Sender/Chat/ Timestamp/附件元信息),OutboundMessage 是发送视角(含 Markdown/ Buttons/二进制数据)。跨平台转发时用 MessageFromEvent 或 平台历史消息接口获得 Message,再构造 OutboundMessage。
func MessageFromEvent ¶ added in v1.34.0
MessageFromEvent 将动态事件转换为消息静态快照。
从 Event 接口的基础方法 + ReplyEvent/MentionsEvent 可选接口 提取全部字段;事件未实现可选接口时对应字段为空。
使用示例(把收到的消息转发为历史消息存档):
msg := platform.MessageFromEvent(ev) store.Append(ev.Chat().ID, msg)
type MessageDeleter ¶
type MessageDeleter interface {
// Delete 删除/撤回已发送的消息。
// chatID 为目标会话 ID,messageID 为平台原生消息 ID。
Delete(ctx stdctx.Context, chatID, messageID string) error
}
MessageDeleter 可选接口,支持消息删除的平台实现此接口。
使用前用类型断言检查支持:
if deleter, ok := sender.(platform.MessageDeleter); ok {
deleter.Delete(ctx, chatID, messageID)
}
func GetDeleter ¶
func GetDeleter(a Adapter) (MessageDeleter, bool)
GetDeleter 安全获取适配器 Sender 的消息删除接口。
若 Sender 未实现 MessageDeleter,返回 (nil, false)。 使用示例:
if deleter, ok := platform.GetDeleter(adapter); ok {
deleter.Delete(ctx, chatID, messageID)
}
type MessageEditor ¶
type MessageEditor interface {
// Edit 编辑已发送的消息。
// chatID 为目标会话 ID(频道/群/私聊),messageID 为平台原生消息 ID。
Edit(ctx stdctx.Context, chatID, messageID string, msg OutboundMessage) error
}
MessageEditor 可选接口,支持消息编辑的平台实现此接口。
使用前用类型断言检查支持:
if editor, ok := sender.(platform.MessageEditor); ok {
editor.Edit(ctx, chatID, messageID, newMsg)
}
func GetEditor ¶
func GetEditor(a Adapter) (MessageEditor, bool)
GetEditor 安全获取适配器 Sender 的消息编辑接口。
若 Sender 未实现 MessageEditor,返回 (nil, false)。 使用示例:
if editor, ok := platform.GetEditor(adapter); ok {
editor.Edit(ctx, chatID, messageID, newContent)
}
type MessageHistoryProvider ¶ added in v1.34.0
type MessageHistoryProvider interface {
// GetGroupHistory 获取群历史消息。
// chatID 为群会话 ID(取事件 Chat().ID 原样传入);limit 为最大返回条数(0 使用平台默认值)。
// 返回按时间从新到旧排列的消息快照列表。
// 平台不支持时返回 [ErrNotSupported]。
GetGroupHistory(ctx stdctx.Context, chatID string, limit int) ([]Message, error)
// GetFriendHistory 获取好友(私聊)历史消息。
// chatID 为用户会话 ID;limit 为最大返回条数(0 使用平台默认值)。
// 平台不支持时返回 [ErrNotSupported]。
GetFriendHistory(ctx stdctx.Context, chatID string, limit int) ([]Message, error)
}
MessageHistoryProvider 可选接口:支持查询历史消息的平台适配器 Sender 实现此接口。
使用前用 GetMessageHistoryProvider 检查支持:
if hp, ok := platform.GetMessageHistoryProvider(adapter); ok {
msgs, err := hp.GetGroupHistory(ctx, groupID, 20)
}
func GetMessageHistoryProvider ¶ added in v1.34.0
func GetMessageHistoryProvider(a Adapter) (MessageHistoryProvider, bool)
GetMessageHistoryProvider 安全获取适配器 Sender 的历史消息接口。
若 Sender 未实现 MessageHistoryProvider,返回 (nil, false)。
type NoopSender ¶
type NoopSender struct{}
NoopSender 空实现,用于测试或不需要发送能力的场景
func (*NoopSender) Send ¶
func (n *NoopSender) Send(_ stdctx.Context, _ SendRequest) (SendResult, error)
Send 什么也不做,始终返回零值 SendResult 和 nil
type OutboundMessage ¶
type OutboundMessage struct {
// Segments 有序消息段(主字段,新增)。
// 非空时 Sender 按段顺序发送,忽略下方扁平字段(唯一真相源)。
// 为空时走下方便捷字段路径(兼容旧调用方)。
Segments []Segment
// Text 纯文本内容
Text string
// Markdown Markdown 格式内容(平台不支持时降级为 Text)
Markdown string
// Attachments 附件列表(图片/音频/视频/文件,支持多附件)
//
// 不支持多附件的平台(如 QQ)只处理第一个匹配当前能力的附件。
Attachments []Attachment
// Embeds 富文本嵌入卡片列表(Discord 原生、其他平台降级处理)
Embeds []Embed
// Mentions 被 @ 用户的 ID 列表(平台内唯一标识符)
//
// QQ 平台:member_openid;Discord/Telegram:user_id。
// 各平台 Sender 负责将其转换为平台特定的 @ 格式。
Mentions []string
// Buttons 交互按钮列表(Discord 组件、Telegram 内联键盘等)
//
// 不支持按钮的平台可安全忽略此字段。
Buttons []Button
// ReplyToID 回复的目标消息 ID(平台原生消息 ID)
ReplyToID string
// QuoteTrigger 引用"触发本条会话的那条消息"(平台渲染引用气泡/回复)。
//
// 供插件在发送时按条选择:需要引用触发消息时设置,不需要则不设置
// (默认关闭,无任何全局/启动配置)。目标 ID 由 Sender 从事件上下文解析,
// 调用方无需知道平台原生 ID:
// - QQ C2C/群聊:message_reference.message_id = 事件 msg_idx(REFIDX)
// - QQ 频道:message_reference.message_id = 事件消息 ID
// - 其它平台:暂未实现,标记被安全忽略
// 与 ReplyToID 的关系:显式 ReplyToID 优先;两者都未提供时不产生引用。
// 事件无可引用消息(如主动 Notify)时该标记静默不生效。
QuoteTrigger bool
// Extra 平台特定扩展字段(key-value 形式)
//
// 示例(QQ 平台传递 msg_seq):
// msg.Extra = map[string]any{"msg_seq": 1}
Extra map[string]any
}
OutboundMessage 是平台无关的出站消息模型。
各平台 Sender 将此结构体转换为平台特定的发送格式。 未设置的字段会被忽略;平台不支持的字段会优雅降级。
字段优先级(平台支持时):
- Embeds(最丰富)
- Attachments(富媒体)
- Markdown(格式文本)
- Text(纯文本,最广泛兼容)
func FileDataMessage ¶
func FileDataMessage(data []byte, name, mimeType string) OutboundMessage
FileDataMessage 快速创建文件消息(本地二进制直传)
func ImageDataMessage ¶
func ImageDataMessage(data []byte, name, mimeType string) OutboundMessage
ImageDataMessage 快速创建图片消息(本地二进制直传)
适用于在内存中生成图片(如二维码、文字图片、验证码)后直接发送, 无需先上传到文件服务器。
mimeType 为图片 MIME 类型(如 "image/png"、"image/jpeg"), name 为可选文件名(某些平台要求)。
示例:
pngBytes, _ := qrcode.Encode(content, qrcode.Medium, 256) msg := platform.ImageDataMessage(pngBytes, "qrcode.png", "image/png")
func MarkdownMessage ¶
func MarkdownMessage(md string) OutboundMessage
MarkdownMessage 快速创建 Markdown 消息
func MessageToOutbound ¶ added in v1.34.0
func MessageToOutbound(m Message, opts ...ForwardOption) OutboundMessage
MessageToOutbound 将消息快照转换为出站消息(跨平台转发)。
直接按段 → 段映射,保留文本夹 at 的交错位置;不经扁平字段中转。 转发语义(转发语义决议):
- WithTargetPlatform(m.Platform) → 同平台透传(reply/face/forward/button/unknown 还原)
- 缺省/跨平台 → 保守降级(reply/button/unknown 剥离,face 降 text,forward 摘要)
func SegmentsToOutbound ¶ added in v1.34.0
func SegmentsToOutbound(segs []Segment) OutboundMessage
SegmentsToOutbound 将有序消息段转换为出站消息(段 → 便捷字段派生填充)。
Segments 原样保留为主字段(Sender 段路径优先);Text/Attachments/Mentions/ReplyToID 由派生函数填充,便于段路径之外的代码阅读。
func (OutboundMessage) IsEmpty ¶
func (m OutboundMessage) IsEmpty() bool
IsEmpty 报告消息是否没有任何可发送的内容。
当 Text、Markdown、Attachments、Embeds、Buttons、Mentions 均为空/nil 时返回 true。 Extra 等纯元数据字段不计入"内容"判断,因为单独存在时平台无法发出有意义的消息。
典型用法(Sender 实现中防止发送空消息):
if req.Message.IsEmpty() {
return errutil.ErrEmptyMessage
}
func (OutboundMessage) WithAttachments ¶
func (m OutboundMessage) WithAttachments(attachments ...Attachment) OutboundMessage
WithAttachments 追加附件
func (OutboundMessage) WithButtons ¶
func (m OutboundMessage) WithButtons(buttons ...Button) OutboundMessage
WithButtons 追加交互按钮
func (OutboundMessage) WithEmbeds ¶
func (m OutboundMessage) WithEmbeds(embeds ...Embed) OutboundMessage
WithEmbeds 追加富文本卡片
func (OutboundMessage) WithExtra ¶
func (m OutboundMessage) WithExtra(key string, value any) OutboundMessage
WithExtra 添加平台扩展字段(返回新消息,不修改原消息)
每次调用均创建独立的 Extra map,避免多个派生消息共享同一底层 map 导致的数据污染。 当原消息 Extra 为空时,直接创建单元素 map,跳过无用的 maps.Copy。
func (OutboundMessage) WithMentions ¶
func (m OutboundMessage) WithMentions(userIDs ...string) OutboundMessage
WithMentions 追加 @ 用户 ID 列表
func (OutboundMessage) WithQuoteTrigger ¶ added in v1.57.0
func (m OutboundMessage) WithQuoteTrigger() OutboundMessage
WithQuoteTrigger 标记本条消息引用触发它的那条消息(需要引用气泡时调用)。 见 OutboundMessage.QuoteTrigger 的语义说明;返回新消息,不修改原消息。
func (OutboundMessage) WithReply ¶
func (m OutboundMessage) WithReply(messageID string) OutboundMessage
WithReply 设置回复目标消息 ID
type RawEvent ¶
type RawEvent interface {
// RawType 返回平台原始事件类型字符串(如 QQ 的 "C2C_MESSAGE_CREATE")
RawType() string
// RawPayload 返回原始平台 payload(类型断言后可访问平台特定字段)
//
// 示例(QQ 平台):
// if payload, ok := e.RawPayload().(*dto.Payload); ok { ... }
RawPayload() any
}
RawEvent 是平台特定数据的可选访问接口。
适配器在需要暴露底层 payload 或平台原生类型字符串时实现此接口。 框架核心代码通过 RawType / RawPayload 帮助函数安全访问,无需直接断言。
type ReactionSender ¶
type ReactionSender interface {
// AddReaction 为指定消息添加表情回应。
// chatID 为目标会话 ID,messageID 为平台原生消息 ID,emoji 为平台无关表情标识。
AddReaction(ctx stdctx.Context, chatID, messageID string, emoji Emoji) error
// RemoveReaction 移除指定消息上的表情回应。
RemoveReaction(ctx stdctx.Context, chatID, messageID string, emoji Emoji) error
}
ReactionSender 可选接口,支持表情回应操作的平台实现此接口。
使用前用类型断言检查支持:
if rs, ok := platform.GetReactionSender(adapter); ok {
rs.AddReaction(ctx, chatID, messageID, platform.Emoji{Kind: platform.EmojiKindUnicode, Value: "👍"})
}
func GetReactionSender ¶
func GetReactionSender(a Adapter) (ReactionSender, bool)
GetReactionSender 安全获取适配器 Sender 的表情回应接口。
若 Sender 未实现 ReactionSender,返回 (nil, false)。 使用示例:
if rs, ok := platform.GetReactionSender(adapter); ok {
rs.AddReaction(ctx, chatID, messageID, platform.Emoji{Kind: platform.EmojiKindUnicode, Value: "👍"})
}
type RecoverableAdapter ¶
type RecoverableAdapter interface {
Adapter
// OnDisconnect 注册断连回调,返回注销函数。
//
// fn 在适配器每次意外断连时被调用,err 为断连原因。
// 多次调用将追加(而非覆盖)回调,互不影响。
// 调用返回的 unregister 函数可注销该特定回调;传入 nil 时为空操作。
OnDisconnect(fn func(err error)) (unregister func())
}
RecoverableAdapter 可选接口:支持感知断连事件的适配器实现此接口。
适配器在 Start() 内部自动重连时,每次意外断连应调用已注册的 fn, 允许框架或应用层触发告警、更新监控指标等副作用。
使用示例:
if ra, ok := adapter.(platform.RecoverableAdapter); ok {
unregister := ra.OnDisconnect(func(err error) {
metrics.RecordDisconnect(adapter.Platform())
logger.Warnf("adapter %s disconnected: %v", adapter.Platform(), err)
})
defer unregister() // 不再需要时注销回调
}
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry 多平台适配器注册表。
支持同时运行多个平台适配器,框架通过 Registry 管理它们的生命周期。
func (*Registry) CapabilitiesFor ¶
func (r *Registry) CapabilitiesFor(platform string) (Capabilities, bool)
CapabilitiesFor 返回指定平台的能力声明。
若平台未注册,返回零值 Capabilities 和 false。
func (*Registry) FatalErrors ¶ added in v1.22.0
FatalErrors 返回适配器致命错误的实时通知 channel。
StartAll 只在**所有**适配器退出后才返回,健康平台会一直阻塞到 ctx 取消, 因此单个平台的致命失败可能在数天内都无人知晓。订阅这个 channel 可以在 错误发生的当下就拿到它,用于告警、重启或降级:
go func() {
for err := range reg.FatalErrors() {
alert(err)
}
}()
_ = reg.StartAll(ctx, handler)
channel 有缓冲且写入是非阻塞的:没有消费者时错误会被直接丢弃, 不会拖慢适配器 goroutine。需要不丢事件的完整通知请改用 AdapterObserver。
channel 由 Registry 持有,不会被关闭。
func (*Registry) Remove ¶
Remove 注销指定平台的适配器,返回 true 表示成功移除,false 表示不存在。
注意:仅从注册表中移除,不调用 Stop();若适配器正在运行, 调用方应先调用 adapter.Stop() 再调用 Remove。
func (*Registry) Replace ¶
Replace 原子替换指定平台的适配器,返回被替换的旧适配器。
无论原有适配器是否存在,新适配器都会被注册。 若该平台此前无适配器,returned old 为 nil,replaced 为 false。
典型用法(热替换运行中的适配器):
old, ok := registry.Replace(newAdapter)
if ok {
_ = old.Stop(ctx)
}
func (*Registry) StartAll ¶
StartAll 并发启动所有已注册平台适配器
每个适配器在独立 goroutine 中运行,ctx 取消时所有适配器退出。 handler 会收到来自所有平台的事件。
错误语义 ¶
单个适配器致命失败**不会**中止其余平台——这是刻意的:一个平台配置有误 不应让整个 Bot 起不来。但这也意味着返回值来得很晚:StartAll 只在所有 适配器都退出后才返回,健康的平台会一直阻塞到 ctx 取消,实际可能是几天。
因此致命错误在**发生的当下**就通过两条途径立即上报,不必等到返回:
- AdapterObserver.OnAdapterError(通过 WithObserver 注册)
- FatalErrors() 返回的错误 channel
返回值仍是所有致命错误的合并结果,供关心最终状态的调用方使用。
func (*Registry) StopAll ¶
StopAll 并发停止所有已注册平台适配器,合并全部错误后返回。
所有适配器同时发起停止,总耗时取决于最慢的那一个(而非各平台停止时间之和)。 停止完成后统一清理断连回调注销函数,释放对 Registry 的内部引用,避免 GC 泄漏。
func (*Registry) WithObserver ¶
func (r *Registry) WithObserver(o AdapterObserver) *Registry
WithObserver 注册适配器生命周期观察者,返回 *Registry 支持链式调用。
必须在 StartAll 之前调用;并发调用是线程安全的(使用写锁)。 传入 nil 表示清除当前观察者。
type ReplyEvent ¶
type ReplyEvent interface {
// ReplyToID 返回被回复消息的平台原生 ID。
// 若此消息不是回复,或平台不提供此信息,返回空字符串。
ReplyToID() string
}
ReplyEvent 是消息回复链感知的可选接口(N7)。
支持消息回复(线程)的平台实现此接口。 框架通过 GetReplyToID 帮助函数安全访问,无需直接断言。
使用示例:
if id := platform.GetReplyToID(event); id != "" {
// 此消息是对 id 的回复
}
type Segment ¶ added in v1.34.0
type Segment struct {
// Type 段类型
Type SegmentType
// Text text 段内容 / at 段的显示文本(平台提供时)
Text string
// UserID at 段的目标用户 ID
UserID string
// ReplyToID reply 段的目标消息 ID
ReplyToID string
// Attachment image/audio/video/file 段载荷(复用统一附件类型)
Attachment Attachment
// FaceID face 段的表情 ID
FaceID string
// Extra 平台特有段数据(forward id、button 结构、ARK payload 等),
// 通用键见 SegmentExtraKey 常量。
Extra map[string]any
}
Segment 一条原子消息段,保留原文顺序。
入站:各平台解析器输出;出站:OutboundMessage.Segments 复用同一类型 (image/audio/video/file 段通过 Attachment.URL/Data 表达出站载荷)。
派生规则(唯一真相源 → 便捷视图):
- SegmentsContent 段 → 纯文本(at/mention_all/face/reply/forward/button/unknown 剥离)
- SegmentsAttachments 段 → 附件列表(仅 image/audio/video/file)
- SegmentsMentions 段 → 被 @ 用户聚合视图(保序去重,含自身 IsSelf=true)
func OutboundSegments ¶ added in v1.34.0
func OutboundSegments(m OutboundMessage) []Segment
OutboundSegments 将便捷字段逆向为有序段(尽力)。
已有 Segments 时直接返回原值;否则按 reply → at → 文本(Markdown 优先, 标注 Extra["markdown"]=true)→ 附件的顺序拼接。 注意:便捷字段无法表达交错位置(如「文本夹 at」),仅适用于旧路径消息。
type SegmentType ¶ added in v1.34.0
type SegmentType string
SegmentType 统一消息段类型。
const ( // SegmentText 纯文本段 SegmentText SegmentType = "text" // SegmentAt @ 单个用户段(一个 at 一个 Segment,保序) SegmentAt SegmentType = "at" // SegmentMentionAll @ 全体成员段 SegmentMentionAll SegmentType = "mention_all" // SegmentImage 图片段 SegmentImage SegmentType = "image" // SegmentAudio 音频/语音段 SegmentAudio SegmentType = "audio" // SegmentVideo 视频段 SegmentVideo SegmentType = "video" // SegmentFile 文件段 SegmentFile SegmentType = "file" // SegmentFace 表情段(贴图/内置表情,身份保留在 FaceID) SegmentFace SegmentType = "face" // SegmentReply 引用/回复段 SegmentReply SegmentType = "reply" // SegmentForward 合并转发段 SegmentForward SegmentType = "forward" // SegmentButton 交互按钮段 SegmentButton SegmentType = "button" // SegmentUnknown 平台特有/未识别段(Extra 保留原始数据) SegmentUnknown SegmentType = "unknown" )
type SendError ¶
type SendError struct {
// Code 标准错误码
Code SendErrorCode
// Platform 来源平台(如 "qq"、"discord")
Platform string
// ChatID 目标会话 ID(便于日志定位)
ChatID string
// Message 人可读的错误描述(可含平台原始错误信息)
Message string
// RetryAfter 建议重试等待时间(仅 SendErrRateLimit 有意义,0 表示未知)
RetryAfter int // 秒
// Cause 底层原始错误(平台 SDK 返回的 error)
Cause error
}
SendError 是 Sender.Send 返回的结构化错误。
平台适配器实现应在以下情况包装为 SendError:
- 触发平台频率限制时用 SendErrRateLimit
- 权限不足时用 SendErrPermDenied
- 消息过长时用 SendErrMsgTooLong
- 其他明确平台错误用 SendErrPlatform
使用 AsSendError 安全提取,无需直接类型断言:
if se, ok := platform.AsSendError(err); ok {
switch se.Code {
case platform.SendErrRateLimit:
time.Sleep(se.RetryAfter)
case platform.SendErrPermDenied:
log.Warn("bot lacks send permission in", se.ChatID)
}
}
func AsSendError ¶
AsSendError 从任意 error 链中提取 *SendError。
若 err 或其链上存在 *SendError,返回 (se, true);否则返回 (nil, false)。 推荐使用此函数代替直接类型断言,支持 %w 包装的多层错误链。
使用示例:
if se, ok := platform.AsSendError(err); ok && se.Code == platform.SendErrRateLimit {
time.Sleep(time.Duration(se.RetryAfter) * time.Second)
}
func NewSendError ¶
func NewSendError(code SendErrorCode, plt, chatID, msg string, retryAfter int, cause error) *SendError
NewSendError 构造一个 SendError(最常用的快速构造函数)。
平台适配器在 Sender.Send() 实现中使用:
return platform.NewSendError(platform.SendErrRateLimit, "qq", chatID,
"触发频率限制", 5, apiErr)
type SendErrorCode ¶
type SendErrorCode int
SendErrorCode 是 SendError 的标准错误码,标识消息发送失败的具体原因。
各平台 Sender 实现应将平台特定错误映射到此枚举, 让调用方无需解析错误字符串即可做针对性处理。
const ( // SendErrUnknown 未知/未分类错误(default,不应主动使用) SendErrUnknown SendErrorCode = iota // SendErrRateLimit 触发平台频率限制(429 / too many requests) // // 建议:退避重试,时间间隔由 SendError.RetryAfter 指示(0 表示未知)。 SendErrRateLimit // SendErrPermDenied 权限不足(机器人无发言权限、被屏蔽、未加入目标会话等) SendErrPermDenied // SendErrMsgTooLong 消息内容超出平台字符/字节长度限制 SendErrMsgTooLong // SendErrUnsupported 当前平台不支持该消息类型(如向不支持 Embed 的平台发送 Embed) SendErrUnsupported // SendErrInvalidTarget 目标会话无效(ID 不存在、已解散、机器人未在其中) SendErrInvalidTarget // SendErrNetworkError 网络/连接错误(超时、DNS 失败、连接被重置等) // // 此类错误通常可重试。 SendErrNetworkError // SendErrTokenExpired 平台授权令牌已过期(被动回复 token 超时、access_token 失效等) SendErrTokenExpired // SendErrDuplicate 消息重复(平台防重放拒绝,msg_seq 重复等) SendErrDuplicate // SendErrPlatform 平台返回的其他明确错误(已有 Code 和 Message,但不属于以上类别) SendErrPlatform )
func (SendErrorCode) Retryable ¶
func (c SendErrorCode) Retryable() bool
Retryable 返回此错误码是否通常可重试。
使用示例:
if se, ok := platform.AsSendError(err); ok && se.Code.Retryable() {
time.Sleep(se.RetryAfter)
return sender.Send(ctx, req)
}
type SendRequest ¶
type SendRequest struct {
// Target 目标会话信息(ID、IsGroup、Tokens 等路由与回复字段)。
// Target.ID 为空时,Sender 实现应返回 errutil.ErrNoChatInfo。
Target ChatInfo
// Message 要发送的消息内容。
Message OutboundMessage
}
SendRequest 发送请求信封,将路由信息与消息内容显式捆绑。
替代原先通过 context.WithValue 隐式注入 ChatInfo / EventID 的方式, 使 Sender 接口的契约完全可见,并在编译期保证类型安全。
被动回复授权 token(如 QQ 的 msg_id / event_id)由 Target(ChatInfo)的 Tokens 字段携带,平台事件解析时自动填充。
func (SendRequest) Validate ¶
func (r SendRequest) Validate() error
Validate 校验 SendRequest 是否合法,供 Sender 实现复用,避免重复检查。
当 Target.ID 为空时返回 errutil.ErrNoChatInfo; 当 Message 没有任何可发送内容时返回 errutil.ErrEmptyMessage; 当附件同时设置 URL 和 Data(互斥),或两者均为空时返回 errutil.ErrInvalidMessage。
使用示例(Sender 实现):
func (s *mySender) Send(ctx context.Context, req platform.SendRequest) error {
if err := req.Validate(); err != nil {
return err
}
// ... 平台特定发送逻辑
}
type SendResult ¶
type SendResult struct {
// MessageID 平台返回的已发送消息唯一 ID。
// 用于后续撤回/编辑等操作。富媒体上传且 srv_send_msg=false 时为空字符串。
MessageID string
// Timestamp 平台确认的消息发送时间(零值表示平台未返回)。
Timestamp time.Time
// Platform 来源平台标识符(如 "qq"、"discord"、"telegram")。
Platform string
// Raw 平台专属响应的完整原始数据,由各平台适配器负责填充。
// 调用方通过类型断言获取平台特定字段;不需要平台特定字段时可忽略。
Raw any
}
SendResult 消息发送成功后的响应摘要。
MessageID 与 Timestamp 是各平台响应中最常用的热点字段,直接暴露为强类型, 可以直接用于撤回(MessageDeleter.Delete)、编辑(MessageEditor.Edit)及日志追踪。
平台专属的额外字段(如 QQ 富媒体的 file_info / ttl)通过 Raw 携带, 调用方用类型断言按需访问:
if r, ok := result.Raw.(*qq.QQSendResult); ok {
fileInfo := r.FileInfo // 富媒体 file_info token
}
已知 Raw 类型:
- *qq.QQSendResult:QQ 平台响应(含 FileInfo、FileUUID、TTL 等富媒体字段)
type Sender ¶
type Sender interface {
// Send 发送消息,返回平台响应摘要与错误。
//
// 成功时 SendResult.MessageID 包含平台分配的消息 ID(可用于撤回/编辑);
// 平台未返回 ID 时 MessageID 为空字符串(不影响发送本身的成功状态)。
// 路由信息由 req 显式传入,ctx 仅用于取消/超时/tracing。
//
// 若 req.Target.ID 为空,实现者应返回 errutil.ErrNoChatInfo。
Send(ctx stdctx.Context, req SendRequest) (SendResult, error)
}
Sender 是平台无关的消息发送接口。
路由信息(目标会话 ChatInfo,含被动回复 token)由 SendRequest.Target 显式传入, ctx 仅用于超时控制、取消传播和 OpenTelemetry tracing。
使用示例(handler 内,框架自动构造 SendRequest):
ctx.Reply(platform.TextMessage("pong"))
直接调用(已知目标会话):
sender.Send(context.Background(), platform.SendRequest{
Target: platform.ChatInfo{ID: "group-001", IsGroup: true},
Message: platform.TextMessage("公告"),
})
type SessionNotifier ¶
type SessionNotifier interface {
// NotifyUser 向指定用户(私聊会话)主动推送一条消息。
// userID 为平台用户唯一 ID(如 QQ 号字符串)。
// 平台不支持主动私信时返回 [ErrNotSupported]。
NotifyUser(ctx stdctx.Context, userID string, msg OutboundMessage) error
// NotifyGroup 向指定群组主动推送一条消息。
// groupID 为平台群组唯一 ID。
// 机器人不在该群时应返回错误。
NotifyGroup(ctx stdctx.Context, groupID string, msg OutboundMessage) error
}
SessionNotifier 可选接口:支持主动向任意用户/群组推送消息的平台适配器 Sender 实现此接口。
与普通 Sender 的区别:
- Sender.Send 需要 ChatInfo(通常包含被动回复令牌),依赖事件上下文
- SessionNotifier 仅凭 userID/groupID 字符串即可主动发起推送,无需事件上下文
典型使用场景:
- 漂流瓶:捞起时跨群通知原投递者(无事件上下文)
- 提醒系统:定时向指定用户发送私信
- 跨群广播:向特定群主动推送信息
使用前用 GetSessionNotifier 检查支持:
if sn, ok := platform.GetSessionNotifier(adapter); ok {
_ = sn.NotifyUser(ctx, userID, platform.TextMessage("你的漂流瓶被捞起了!"))
}
func GetSessionNotifier ¶
func GetSessionNotifier(a Adapter) (SessionNotifier, bool)
GetSessionNotifier 安全获取适配器 Sender 的主动推送接口。
若 Sender 未实现 SessionNotifier,返回 (nil, false)。
使用示例:
if sn, ok := platform.GetSessionNotifier(adapter); ok {
_ = sn.NotifyUser(ctx, targetUserID, platform.TextMessage("消息来了!"))
}
type SyntheticEvent ¶
type SyntheticEvent struct {
// contains filtered or unexported fields
}
SyntheticEvent 是程序化构造的虚拟事件,实现 Event 接口。
主要用途:
- 单元/集成测试(无需真实平台连接)
- 插件内部向引擎注入合成事件(如定时推送、跨插件触发)
- 调试时模拟特定平台事件
推荐通过 NewSyntheticEvent + SyntheticOption 构造:
evt := platform.NewSyntheticEvent(
platform.EventKindGroupMessage,
"/ping",
platform.WithSyntheticChat(platform.ChatInfo{ID: "group-1", IsGroup: true}),
platform.WithSyntheticSender(platform.UserInfo{ID: "user-42"}),
)
func NewSyntheticEvent ¶
func NewSyntheticEvent(kind EventKind, content string, opts ...SyntheticOption) *SyntheticEvent
NewSyntheticEvent 创建一个合成事件。
kind 指定事件类型(如 EventKindGroupMessage); content 为消息文本;其余字段通过 opts 配置(未配置时使用合理默认值)。
func (*SyntheticEvent) Platform ¶
func (e *SyntheticEvent) Platform() string
Platform 返回平台标识(默认 "synthetic",可通过 WithSyntheticPlatform 覆盖)。
func (*SyntheticEvent) Segments ¶ added in v1.34.0
func (e *SyntheticEvent) Segments() []Segment
Segments 返回保序统一消息段(唯一真相源,text + 媒体段)。
func (*SyntheticEvent) Timestamp ¶
func (e *SyntheticEvent) Timestamp() time.Time
Timestamp 返回事件时间戳。
type SyntheticOption ¶
type SyntheticOption func(*SyntheticEvent)
SyntheticOption 用于配置 SyntheticEvent 的可选选项。
func WithSyntheticAttachments ¶
func WithSyntheticAttachments(a ...Attachment) SyntheticOption
WithSyntheticAttachments 追加附件列表。
func WithSyntheticChat ¶
func WithSyntheticChat(c ChatInfo) SyntheticOption
WithSyntheticChat 设置会话信息(群/私聊)。
func WithSyntheticID ¶
func WithSyntheticID(id string) SyntheticOption
WithSyntheticID 覆盖事件 ID(默认自动生成 UUID)。
func WithSyntheticPlatform ¶
func WithSyntheticPlatform(p string) SyntheticOption
WithSyntheticPlatform 覆盖 Platform() 返回的平台名称(默认 "synthetic")。
func WithSyntheticSender ¶
func WithSyntheticSender(u UserInfo) SyntheticOption
WithSyntheticSender 设置发送者信息。
func WithSyntheticTimestamp ¶
func WithSyntheticTimestamp(t time.Time) SyntheticOption
WithSyntheticTimestamp 覆盖时间戳(默认 time.Now())。
type TypingNotifier ¶
type TypingNotifier interface {
// SendTyping 向指定会话发送"正在输入"状态指示。
SendTyping(ctx stdctx.Context, chatID string) error
}
TypingNotifier 可选接口,支持"正在输入"状态的平台实现此接口。
使用前用类型断言检查支持:
if tn, ok := platform.GetTypingNotifier(adapter); ok {
tn.SendTyping(ctx, chatID)
}
func GetTypingNotifier ¶
func GetTypingNotifier(a Adapter) (TypingNotifier, bool)
GetTypingNotifier 安全获取适配器 Sender 的"正在输入"接口。
若 Sender 未实现 TypingNotifier,返回 (nil, false)。 使用示例:
if tn, ok := platform.GetTypingNotifier(adapter); ok {
tn.SendTyping(ctx, chatID)
}
type UserInfo ¶
type UserInfo struct {
// ID 平台内唯一用户标识(QQ openID、Telegram userID 等)
ID string
// DisplayName 用户显示名(昵称/用户名)
DisplayName string
// GroupRole 发送者在当前群/频道中的角色等级。
// 私聊场景或平台未提供时为 GroupRoleUnknown。
GroupRole GroupRole
// IsBot 是否为机器人账号
IsBot bool
// IsSelf 此用户信息是否指向机器人自身(如 @ 列表中的机器人自身)。
// 主要用于 MentionsEvent,方便插件快速判断机器人是否被 @。
IsSelf bool
}
UserInfo 代表消息发送者/用户的基本信息。
各平台填充能力不同,未知字段返回空字符串、false 或零值。
func GetMentions ¶
GetMentions 安全获取消息中 @ 的用户列表。
派生顺序:接口断言优先(平台可实现更丰富的 UserInfo,如 qq 的 IsBot)、 段兜底(SegmentsMentions 派生,botID 缺失时以段内 Extra[SegmentExtraIsSelf] 覆盖为准)。 若事件无 @ 用户,返回 nil。
func SegmentsMentions ¶ added in v1.34.0
SegmentsMentions 从有序消息段中提取被 @ 用户聚合视图(保序去重)。
botID 为机器人自身 ID(适配器 GetBotID),用于推导 IsSelf; 无法推导时(botID 为空)以 Segment.Extra[SegmentExtraIsSelf] 覆盖为准。
与现状 Mentions() 语义一致(satori/qq 均保留自身并标记 IsSelf=true):
- **包含**被 @ 的机器人自身(IsSelf=true)——OnMentionedBot 依赖此语义
- 排除 SegmentMentionAll(@ 全体成员不是具体用户)
- 排除无法解析 UserID 的 at 段
type VoiceTranscript ¶ added in v1.38.0
type VoiceTranscript interface {
// Transcript 返回语音转写文本;为空表示无转写结果。
Transcript() string
}
VoiceTranscript 语音转写文本提供者。
平台适配器可将携带语音转写结果的元数据放入 Attachment.Extra 中, 并让该元数据类型实现本接口;通用消费者(如 AI 插件)即可通过 AttachmentTranscript 读取平台侧免费的 ASR 转写文本, 无需自行下载音频再调用 STT。
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package deadletter 提供针对 platform.Event 的死信条目序列化与落盘/推送实现。
|
Package deadletter 提供针对 platform.Event 的死信条目序列化与落盘/推送实现。 |
|
Package discord is the Discord platform.Adapter implementation.
|
Package discord is the Discord platform.Adapter implementation. |
|
Package milky 实现了 remilia 机器人框架的 Milky QQ 协议适配器。
|
Package milky 实现了 remilia 机器人框架的 Milky QQ 协议适配器。 |
|
Package mock provides shared mock implementations of platform interfaces for testing.
|
Package mock provides shared mock implementations of platform interfaces for testing. |
|
Package onebot 实现了 remilia 框架的 OneBot V11 协议适配器。
|
Package onebot 实现了 remilia 框架的 OneBot V11 协议适配器。 |
|
Package qq 是 QQ 官方机器人平台的 platform.Adapter 实现。
|
Package qq 是 QQ 官方机器人平台的 platform.Adapter 实现。 |
|
miniapp
Package miniapp 提供 QQ 频道小程序(MiniApp)开放数据加解密与签名验证工具。
|
Package miniapp 提供 QQ 频道小程序(MiniApp)开放数据加解密与签名验证工具。 |
|
openapi
Package openapi 提供 QQ 机器人 OpenAPI 客户端。
|
Package openapi 提供 QQ 机器人 OpenAPI 客户端。 |
|
Package satori 实现了基于 Satori 协议的 platform.Adapter, 可连接任意兼容 Satori 协议的 SDK(如 Chronocat、Lagrange、Koishi 等)。
|
Package satori 实现了基于 Satori 协议的 platform.Adapter, 可连接任意兼容 Satori 协议的 SDK(如 Chronocat、Lagrange、Koishi 等)。 |
|
Package telegram implements the Telegram Bot API platform adapter for remilia.
|
Package telegram implements the Telegram Bot API platform adapter for remilia. |
|
Package terminal 提供基于终端/控制台的平台适配器,用于调试和分析。
|
Package terminal 提供基于终端/控制台的平台适配器,用于调试和分析。 |
|
Package wechat is the platform.Adapter skeleton for WeChat Work / WeChat Official Account.
|
Package wechat is the platform.Adapter skeleton for WeChat Work / WeChat Official Account. |