# snowluma 对接 [SnowLuma](https://snowluma.github.io/)(OneBot v11 协议)的 Go 软件包,零第三方依赖(仅 gorilla/websocket)。 本包与 [gitea.doicat.com/bot/napcat](https://gitea.doicat.com/bot/napcat) 的公开 API **保持一致**:方法名、消息段构造函数、事件结构、Client/Context 用法完全相同。现有的 napcat 机器人项目,只需改一行导入即可切到 SnowLuma。 ```go bot := snowluma.New().SetToken("xxx").SetForwardWS("ws://127.0.0.1:3001") bot.OnGroupMessage(func(c *snowluma.Context) { if c.HasAtMe() { c.ReplyText("hi") } }) bot.Run(ctx) ``` ## 从 napcat 迁移(drop-in) napcat 用法: ```go import "gitea.doicat.com/bot/napcat" // ... napcat.New() / napcat.Text(...) / *napcat.Context ... ``` 改成: ```go import napcat "gitea.doicat.com/bot/snowluma" // ... 其余代码一字不改,napcat.New() / napcat.Text(...) / *napcat.Context 全部照常编译 ``` 导入别名 `napcat` 覆盖了包的声明名,因此所有 `napcat.XXX` 引用保持不变。差异只在协议/能力覆盖上(见下文「与 napcat 的差异」)。 ## 特性 - **一个对象搞定**:`New()` 创建与 SnowLuma 交互的客户端对象,配置、事件、API 全在对象上 - **四种通信方式**,链式配置、可任意叠加: - 正向 WebSocket(自动重连 + 指数退避) - 反向 WebSocket(支持多 bot 实例同时连入) - HTTP API - HTTP POST 上报(自动支持 OneBot 快速操作) - **事件模型**:message / message_sent / notice / request / meta_event 全量结构化,notice 未知字段不丢失(收进 `Extra`) - **消息段**:全类型构造函数;收到的 CQ 码字符串与段数组统一解码 - **API 覆盖**:OneBot 11 标准 + go-cqhttp/NapCat 兼容 + SnowLuma 扩展(闪传、Ark 卡片、群待办、群相册、QQ 空间、系统表情、流式接口等);未收录的扩展可用 `Call` 裸调 - **echo 路由**:WS 上的 action 请求/响应按 echo 精确配对,支持并发调用 - **连接级路由**:事件处理函数里的 API 调用自动走产生该事件的连接,多 bot 互不串线 - 内置文本命令路由、请求 Approve/Reject、心跳记录、panic 隔离 ## 目录结构 ```text snowluma/ ├── doc.go 包文档(pkg.go.dev 首页) ├── client.go Client:配置方法、连接汇聚、事件分发、Handler 注册 ├── context.go Context:处理函数上下文(Reply/Approve/快速操作) ├── api.go API:OneBot 11 / go-cqhttp / NapCat 兼容的类型化方法 ├── api_ext.go API:SnowLuma 扩展动作(闪传/Ark/待办/相册/空间/表情/流式) ├── caller.go Caller 接口 + WS echo 路由 + CallAPI 泛型 ├── connect_ws.go 正向/反向 WebSocket 连接器 + Session ├── connect_http.go HTTP 上报(快速操作)+ HTTP API 连接器 ├── event.go 事件结构(message/notice/request/meta) ├── segment.go 消息段构造与解码 ├── cqcode.go CQ 码解析/编码 ├── types.go 响应信封与实体类型 ├── example_test.go 用法示例(godoc Example) └── snowluma_test.go 单元测试(CQ 码回环、事件解码、消息段解码) ``` ## 安装 ```text go get gitea.doicat.com/bot/snowluma ``` 使用指定版本: ```text go get gitea.doicat.com/bot/snowluma@v1.0.0 ``` **私有仓库使用者**:如果仓库设为私有,除了给 Git 配置 Gitea 凭据外,还需设置: ```text set GOPRIVATE=gitea.doicat.com ``` ## 快速开始 ### 1. SnowLuma 侧配置 在 SnowLuma 的 OneBot 网络配置里按需启用一项(或多项): | 配置方法 | SnowLuma 侧配置 | | --- | --- | | `SetForwardWS("ws://127.0.0.1:3001")` | 启用「正向 WebSocket」,端口 3001 | | `SetReverseWS(":8080")` | 启用「反向 WebSocket」,URL 填 `ws://<本机IP>:8080/` | | `SetHTTPAPI("http://127.0.0.1:3000")` | 启用「HTTP」,端口 3000 | | `SetHTTPReport(":8081")` | 启用「HTTP POST 上报」,地址填 `http://<本机IP>:8081/` | 若配置了 token,`SetToken` 填同一个值即可,框架会在 WS 握手(query + Authorization 头)和 HTTP 调用(Authorization 头)中自动携带;HTTP 上报场景若配了 secret,`SetSecret` 会自动校验 `X-Signature`(HMAC-SHA1)。 ### 2. 完整示例 见 [example_test.go](./example_test.go) 的 `Example` 函数。 ## 配置方法一览 | 方法 | 说明 | | --- | --- | | `SetToken(t)` | OneBot access_token,所有连接自动携带 | | `SetSecret(s)` | HTTP 上报 X-Signature 校验密钥 | | `SetLogger(l)` | 自定义 \*slog.Logger | | `SetForwardWS(urls...)` | 正向 WS 目标,可多个,重复调用替换 | | `SetReverseWS(addrs...)` | 反向 WS 监听地址,可多个,重复调用替换 | | `SetHTTPAPI(baseURL)` | HTTP API 地址 | | `SetHTTPReport(addrs...)` | HTTP 上报监听地址,可多个,重复调用替换 | | `Use(conn...)` | 注册自定义 Connector(高级扩展点) | 以上方法均返回 `*Client`,支持链式调用。请勿在 `Run` 之后修改配置。 ## 事件处理 ```go bot.OnEvent(fn) // 所有事件 bot.OnMessage(fn) // 私聊 + 群聊消息(含 message_sent) bot.OnPrivateMessage(fn) // 私聊消息 bot.OnGroupMessage(fn) // 群聊消息 bot.OnNotice(fn) // 全部通知 bot.OnNoticeType(t, fn) // 指定 notice_type bot.OnRequest(fn) // 好友/群请求 bot.OnMeta(fn) // 心跳/生命周期 bot.OnCommand(name, fn) // 文本命令,args 为空白切分的参数 ``` 同一会话的事件**串行分发**(保序);handler 里的 panic 会被隔离并记录日志。 ## Context 常用方法 | 方法 | 说明 | | --- | --- | | `Reply(msg...)` / `ReplyText(s)` | 回复当前消息(群聊/私聊自动判断) | | `Recall()` | 撤回当前消息 | | `Approve()` / `Reject(reason)` | 同意/拒绝当前的请求事件 | | `HasAtMe()` / `PlainText()` / `Segments()` | 消息内容判断 | | `IsGroup()` / `IsPrivate()` / `IsSelfMessage()` | 事件类型判断 | | `SendGroupMsg(nil, gid, ...)` 等 | 直接调用任意 API(ctx 传 nil 用默认 30s 超时) | | `Call(nil, action, params, &out)` | 裸调未收录的扩展 API | ## 消息段 ```go snowluma.Text("hello") snowluma.At(12345) // snowluma.AtAll() snowluma.Image("C:/a.jpg") // 支持本地路径 / http(s) URL / base64:// snowluma.Reply(678).With("text", "引用") snowluma.CustomNode(10001, "某人", []snowluma.Segment{snowluma.Text("...")}) ``` ## 测试 ```text go test ./... ``` ## 与 napcat 的差异 两包面向不同运行时(SnowLuma vs NapCatQQ),但都是 OneBot v11 + NapCat 兼容协议。协议层完全一致,方法名一一对应;能力覆盖上的差异体现在: - **SnowLuma 独有**:闪传(flash)、Ark 卡片分享、群待办、群分类标签、群相册、QQ 空间、系统表情、流式文件上传/下载等扩展动作;本包在 `api_ext.go` 中提供类型化封装。 - **napcat 有而 SnowLuma 可能未实现/改名**:个别 NapCat 私有动作(如 `ArkShareGroup` 这类大小写混合名)在 SnowLuma 中改用了标准名(`send_group_ark_share`),本包对这些动作做了别名适配,保证同名方法仍可用。 未收录到类型化方法里的动作,一律可通过 `Client.Call` / `Context.Call`(或 `CallAPI[T]` 泛型)裸调,因此能力差异不会卡住迁移。