239 lines
8.3 KiB
Markdown
239 lines
8.3 KiB
Markdown
# 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/duoduo/napcat
|
||
```
|
||
|
||
使用指定版本:
|
||
|
||
```
|
||
go get gitea.doicat.com/duoduo/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"
|
||
|
||
"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 封顶指数退避)。
|