# napcat 对接 [NapCatQQ](https://napneko.github.io/)(OneBot 11 协议)的 Go 软件包,零第三方依赖(仅 gorilla/websocket)。 ```go bot := napcat.New().SetToken("xxx").SetForwardWS("ws://127.0.0.1:3001") bot.OnGroupMessage(func(c *napcat.Context) { if c.HasAtMe() { c.ReplyText("hi") } }) bot.Run(ctx) ``` ## 特性 - **一个对象搞定**:`New()` 创建与 NapCat 交互的客户端对象,配置、事件、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 扩展共 80+ 个类型化方法, 未收录的扩展可用 `Call` 裸调 - **echo 路由**:WS 上的 action 请求/响应按 echo 精确配对,支持并发调用 - **连接级路由**:事件处理函数里的 API 调用自动走产生该事件的连接,多 bot 互不串线 - 内置文本命令路由、请求 Approve/Reject、心跳记录、panic 隔离 ## 目录结构 ``` napcat/ ├── doc.go 包文档(pkg.go.dev 首页) ├── client.go Client:配置方法、连接汇聚、事件分发、Handler 注册 ├── context.go Context:处理函数上下文(Reply/Approve/快速操作) ├── api.go API:80+ 类型化 API 方法 ├── 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 Examples) └── *_test.go 单元测试(内置 mock NapCat) ``` ## 安装 ``` go get gitea.doicat.com/bot/napcat ``` 使用指定版本: ``` go get gitea.doicat.com/bot/napcat@v1.0.0 ``` **私有仓库使用者**:如果仓库设为私有,除了给 Git 配置 Gitea 凭据外,还需设置: ``` set GOPRIVATE=gitea.doicat.com ``` 这样 go 命令才会走 Git 凭据拉取而不是公共校验和数据库。 ## 快速开始 ### 1. NapCat 侧配置 在 NapCat WebUI 的「网络配置」里按需启用一项(或多项): | 配置方法 | NapCat 侧配置 | |---|---| | `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/` | 若配置了 access_token,`SetToken` 填同一个值即可,框架会在 WS 握手 (query + Authorization 头)和 HTTP 调用(Authorization 头)中自动携带; HTTP 上报场景若配了 secret,`SetSecret` 会自动校验 `X-Signature`(HMAC-SHA1)。 ### 2. 完整示例 ```go package main import ( "context" "os" "os/signal" "gitea.doicat.com/bot/napcat" ) func main() { bot := napcat.New(). SetToken("your-token"). SetForwardWS("ws://127.0.0.1:3001") // 可叠加其他通信方式: // bot.SetReverseWS(":8080") // bot.SetHTTPAPI("http://127.0.0.1:3000") // bot.SetHTTPReport(":8081") // 文本命令 bot.OnCommand("/ping", func(c *napcat.Context, args []string) { c.ReplyText("pong!") }) // 群里 @机器人 就复读 bot.OnGroupMessage(func(c *napcat.Context) { if c.HasAtMe() && !c.IsSelfMessage() { c.ReplyText("收到: " + c.PlainText()) } }) // 进群欢迎 bot.OnNoticeType("group_increase", func(c *napcat.Context) { c.SendGroupMsg(nil, c.Event.Notice.GroupID, napcat.Text("欢迎新人~")) }) // 被戳就戳回去 bot.OnNoticeType("notify", func(c *napcat.Context) { n := c.Event.Notice if n.SubType == "poke" && n.TargetID == c.SelfID() { c.SendPoke(nil, n.GroupID, n.UserID) } }) // 好友/群请求自动同意 bot.OnRequest(func(c *napcat.Context) { c.Approve() }) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() bot.Run(ctx) } ``` ## 配置方法一览 | 方法 | 说明 | |---|---| | `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,如 "group_increase" "notify" "group_recall" bot.OnRequest(fn) // 好友/群请求 bot.OnMeta(fn) // 心跳/生命周期 bot.OnCommand(name, fn) // 文本命令,args 为空白切分的参数 ``` 同一会话的事件**串行分发**(保序);handler 里的 panic 会被隔离并记录日志。 事件队列上限 256 条,溢出时丢弃并告警。 ## 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 | HTTP 上报模式下,`Reply` / `Approve` / `Reject` 会随 HTTP 响应以 OneBot「快速操作」返回,不产生额外请求。 ## 消息段 ```go napcat.Text("hello") napcat.At(12345) // napcat.AtAll() napcat.Image("C:/a.jpg") // 支持本地路径 / http(s) URL / base64:// napcat.Reply(678).With("text", "引用") // With 追加任意字段 napcat.CustomNode(10001, "某人", []napcat.Segment{napcat.Text("...")}) ``` 收到的消息统一用 `c.Segments()` 获取,CQ 码字符串会被自动解析; `napcat.ParseCQ` / `napcat.EncodeCQ` 可单独使用。 ## API 调用 全部 API 挂在 `Client`(默认通道)与 `Context`(事件来源通道)上, 命名与 OneBot action 一一对应(驼峰化),例如: ```go login, _ := bot.GetLoginInfo(nil) // get_login_info mid, err := bot.SendGroupMsg(nil, 123, napcat.Text("hi")) bot.SetGroupBan(nil, 123, 456, 600) // set_group_ban info, _ := bot.GetGroupMemberInfo(nil, 123, 456, false) bot.UploadGroupFile(nil, 123, `C:\file.zip`, "file.zip", "") hist, _ := bot.GetGroupMsgHistory(nil, 123, 0, 20, false) ``` 返回未结构化的 NapCat 扩展 API(如 `GetProfileLike`)时得到 `jsonRaw`(map), 需要更细的结构时用 `Call` 裸调自行解码。 ## 测试 ``` go test ./... ``` 测试内置 mock NapCat(正向 WS、反向 WS、HTTP 上报签名校验、HTTP API), 覆盖 echo 往返、错误响应、快速操作、命令分发、CQ 码转义回环等场景。 ## 注意事项 - `message_sent` 是 bot 自己发出的消息事件,同样进入 message 处理链, 用 `c.IsSelfMessage()` 区分,避免 bot 复读自己。 - 多个 bot 实例同时连入反向 WS 时,`Client.Call`(默认通道)只作用于 最近连入的会话;事件处理内请一律用 `Context` 的方法,自动路由正确。 - 正向 WS 断开后自动重连(1s 起步、30s 封顶指数退避)。