Files
napcat/README.md

239 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 封顶指数退避)。