platform

package
v1.71.1 Latest Latest
Warning

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

Go to latest
Published: Oct 6, 2026 License: MIT Imports: 12 Imported by: 0

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

View Source
const (
	// SegmentExtraForwardNodes 直发合并转发消息的结构化记录(SegmentForward 段)。
	SegmentExtraForwardNodes = "forward_nodes"
	// SegmentExtraQuotedForward 被引用合并转发记录(reply 段,被引用消息
	// 本身是合并转发时,如 QQ 103 引用 102)。
	SegmentExtraQuotedForward = "quote_forward"
)

Segment.Extra 中转发记录载荷的键(见 SegmentExtraKey 通用键说明)。

View Source
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 的通用键。

View Source
const ButtonRowAuto = 0

ButtonRowAuto 是 Button.Row 的零值,表示交由平台自动排列布局。

Row=ButtonRowAuto 时,每个按钮各自独占一行(安全默认值)。 需要将多个按钮排在同一行时,为它们指定相同的 Row 值(1 ~ 5)。

Variables

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

func Content(e Event) string

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

func GetBotID(a Adapter) string

GetBotID 安全获取适配器的机器人唯一 ID。

若适配器未实现 BotIdentity 或平台尚未返回 ID,返回空字符串。

使用示例:

if platform.GetBotID(adapter) == event.Sender().ID {
    return // 忽略自身发出的消息
}

func GetBotName

func GetBotName(a Adapter) string

GetBotName 安全获取适配器的机器人显示名称。

若适配器未实现 BotIdentity,返回空字符串。

func GetPlatformAPI added in v1.34.0

func GetPlatformAPI(sender Sender) any

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

func GetPlatformAPIAs[T any](sender Sender) (T, bool)

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

func GetReplyToID(e Event) string

GetReplyToID 安全获取被回复消息的 ID。

派生顺序(reply 段为单一真相源):段内首个 SegmentReply → 接口断言兜底。 平台实现其 ReplyToID() 时应直接委托段查找,杜绝双写。 若事件无 reply 段且未实现 ReplyEvent,返回空字符串。

func IsEdited

func IsEdited(e Event) bool

IsEdited 安全判断消息是否为编辑版本。

若事件未实现 EditableEvent,返回 false。

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

func MentionedBot(e Event) bool

MentionedBot 判断消息是否 @ 了机器人自身。

派生顺序(跨平台统一口径):

  1. GetMentions 中 IsSelf=true 的条目——各平台的结构化 @ 列表;
  2. 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

func QuotedImage(segs []Segment) (url, mimeType string)

QuotedImage 从引用消息段中提取被引用图片,返回 (URL, MimeType)。

提取顺序(对每个 reply 段):

  1. 归一化附件 Extra[SegmentExtraQuoteAtts](跨平台统一路径)
  2. 结构化被引用合并转发记录 Extra[SegmentExtraQuotedForward] (*ForwardRecord,QQ 103 引用 102 时由适配器解析填充;图片按记录 节点顺序提取,递归嵌套关联子条目)
  3. QQ 兼容兜底 Extra["raw_quote"](msg_elements 原始 JSON,图片位于 elements[].attachments[])
  4. QQ 兼容兜底 Extra["parallel_message"](并行视图 msg_nodes[].attachments)

类型判定:优先 Kind 或 content_type 显式标注 image/* 的项;显式标注其他 类型(video/audio/file)的项跳过,不作为兜底;所有项均未标注类型时回退 首个带 URL 的项(真实类型由下载后的内容嗅探二次校验)。 无引用段或无可用的图片附件时返回空。

func RawPayload

func RawPayload(e Event) any

RawPayload 安全获取平台原始 payload。

若事件未实现 RawEvent,返回 nil。

func RawType

func RawType(e Event) string

RawType 安全获取平台原始事件类型字符串。

若事件未实现 RawEvent,返回空字符串。

func SafeDispatch

func SafeDispatch(handler func(Event), event Event)

SafeDispatch 安全调用事件 handler,捕获并记录 panic 以防止崩溃。

所有平台适配器的 Start() 方法应使用此函数包装对 handler 的调用, 而非在每个适配器中重复实现相同的 defer-recover 逻辑。

使用示例:

platform.SafeDispatch(handler, event)

func SegmentsContent added in v1.34.0

func SegmentsContent(segs []Segment) string

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

func SegmentsReplyToID(segs []Segment) string

SegmentsReplyToID 返回段中首个 reply 段的目标消息 ID(无 reply 段时返回空字符串)。

func TruncateText

func TruncateText(text string, maxRunes int) string

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

type DegradePolicy func(s Segment, samePlatform bool) []Segment

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 页脚文本
	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 EmojiKind

type EmojiKind string

EmojiKind 平台无关的 emoji 种类枚举。

const (
	// EmojiKindUnicode 标准 Unicode 表情(如 "👍"),Value 字段填 Unicode 字符
	EmojiKindUnicode EmojiKind = "unicode"
	// EmojiKindCustom 平台自定义表情(如 Discord 的 name:id 格式),需 ID + Value
	EmojiKindCustom EmojiKind = "custom"
	// EmojiKindSystem 平台内置系统表情(如 QQ 内置表情,需 ID 字段)
	EmojiKindSystem EmojiKind = "system"
)

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 调用)。

const (
	// GroupRoleUnknown 未知角色(平台未提供信息,或私聊场景)
	GroupRoleUnknown GroupRole = 0
	// GroupRoleMember 普通成员
	GroupRoleMember GroupRole = 1
	// GroupRoleAdmin 群管理员(具有管理群/频道权限的角色)
	GroupRoleAdmin GroupRole = 2
	// GroupRoleOwner 群主/服务器拥有者,或具有最高管理权限(Administrator)的成员
	GroupRoleOwner GroupRole = 3
)

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

type HealthDetailer interface {
	HealthDetail() map[string]any
}

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

func MessageFromEvent(e Event) Message

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

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 将此结构体转换为平台特定的发送格式。 未设置的字段会被忽略;平台不支持的字段会优雅降级。

字段优先级(平台支持时):

  1. Embeds(最丰富)
  2. Attachments(富媒体)
  3. Markdown(格式文本)
  4. Text(纯文本,最广泛兼容)

func AudioMessage

func AudioMessage(url string) OutboundMessage

AudioMessage 快速创建音频消息(远程 URL)

func FileDataMessage

func FileDataMessage(data []byte, name, mimeType string) OutboundMessage

FileDataMessage 快速创建文件消息(本地二进制直传)

func FileMessage

func FileMessage(url, name string) OutboundMessage

FileMessage 快速创建文件消息(远程 URL)

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 ImageMessage

func ImageMessage(url string) OutboundMessage

ImageMessage 快速创建图片消息(远程 URL)

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 TextMessage

func TextMessage(text string) OutboundMessage

TextMessage 快速创建纯文本消息

func VideoMessage

func VideoMessage(url string) OutboundMessage

VideoMessage 快速创建视频消息(远程 URL)

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 NewRegistry

func NewRegistry() *Registry

NewRegistry 创建空的适配器注册表

func (*Registry) All

func (r *Registry) All() []Adapter

All 返回所有已注册适配器的快照(切片顺序不保证)

无论注册表是否为空,始终返回非 nil 切片,保持与 Go 惯例的一致性。

func (*Registry) CapabilitiesFor

func (r *Registry) CapabilitiesFor(platform string) (Capabilities, bool)

CapabilitiesFor 返回指定平台的能力声明。

若平台未注册,返回零值 Capabilities 和 false。

func (*Registry) FatalErrors added in v1.22.0

func (r *Registry) FatalErrors() <-chan error

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) Get

func (r *Registry) Get(platform string) (Adapter, bool)

Get 获取指定平台的适配器

func (*Registry) Len

func (r *Registry) Len() int

Len 返回已注册适配器数量,无需分配切片。

func (*Registry) Register

func (r *Registry) Register(adapter Adapter)

Register 注册一个平台适配器

若同一平台已注册,会覆盖旧适配器。

func (*Registry) Remove

func (r *Registry) Remove(platform string) bool

Remove 注销指定平台的适配器,返回 true 表示成功移除,false 表示不存在。

注意:仅从注册表中移除,不调用 Stop();若适配器正在运行, 调用方应先调用 adapter.Stop() 再调用 Remove。

func (*Registry) Replace

func (r *Registry) Replace(adapter Adapter) (old Adapter, replaced bool)

Replace 原子替换指定平台的适配器,返回被替换的旧适配器。

无论原有适配器是否存在,新适配器都会被注册。 若该平台此前无适配器,returned old 为 nil,replaced 为 false。

典型用法(热替换运行中的适配器):

old, ok := registry.Replace(newAdapter)
if ok {
    _ = old.Stop(ctx)
}

func (*Registry) SenderFor

func (r *Registry) SenderFor(platform string) (Sender, bool)

SenderFor 返回指定平台的消息发送器。

若平台未注册,返回 (nil, false)。

func (*Registry) StartAll

func (r *Registry) StartAll(ctx stdctx.Context, handler func(Event)) error

StartAll 并发启动所有已注册平台适配器

每个适配器在独立 goroutine 中运行,ctx 取消时所有适配器退出。 handler 会收到来自所有平台的事件。

错误语义

单个适配器致命失败**不会**中止其余平台——这是刻意的:一个平台配置有误 不应让整个 Bot 起不来。但这也意味着返回值来得很晚:StartAll 只在所有 适配器都退出后才返回,健康的平台会一直阻塞到 ctx 取消,实际可能是几天。

因此致命错误在**发生的当下**就通过两条途径立即上报,不必等到返回:

  • AdapterObserver.OnAdapterError(通过 WithObserver 注册)
  • FatalErrors() 返回的错误 channel

返回值仍是所有致命错误的合并结果,供关心最终状态的调用方使用。

func (*Registry) StopAll

func (r *Registry) StopAll(ctx stdctx.Context) error

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:

使用 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

func AsSendError(err error) (*SendError, bool)

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)

func (*SendError) Error

func (e *SendError) Error() string

Error 实现 error 接口。

func (*SendError) Unwrap

func (e *SendError) Unwrap() error

Unwrap 支持 errors.Is / errors.As 的错误链穿透。

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)
}

func (SendErrorCode) String

func (c SendErrorCode) String() string

String 返回错误码的可读名称,便于日志输出。

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) Chat

func (e *SyntheticEvent) Chat() ChatInfo

Chat 返回会话信息。

func (*SyntheticEvent) ID

func (e *SyntheticEvent) ID() string

ID 返回事件唯一标识。

func (*SyntheticEvent) Kind

func (e *SyntheticEvent) Kind() EventKind

Kind 返回事件类别。

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) Sender

func (e *SyntheticEvent) Sender() UserInfo

Sender 返回发送者信息。

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

func GetMentions(e Event) []UserInfo

GetMentions 安全获取消息中 @ 的用户列表。

派生顺序:接口断言优先(平台可实现更丰富的 UserInfo,如 qq 的 IsBot)、 段兜底(SegmentsMentions 派生,botID 缺失时以段内 Extra[SegmentExtraIsSelf] 覆盖为准)。 若事件无 @ 用户,返回 nil。

func SegmentsMentions added in v1.34.0

func SegmentsMentions(segs []Segment, botID string) []UserInfo

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。

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 协议适配器。
qq
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.

Jump to

Keyboard shortcuts

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