AniaBot
基于 Go 语言的 QQ 机器人框架,插件化架构,目前已支持 NapCat 协议,轻松构建属于你的 QQ Bot。
✨ 核心特性
基于接口的插件系统,完善的生命周期管理,Order 排序控制执行顺序。
目前同时支持 HTTP 和 WebSocket 两种 NapCat 接入方式,无缝切换,后续可补充其他协议。
内置 AI 对话插件,支持工具调用、联网搜索、图片 OCR 等多模态能力。
WebSocket 适配器内置并发工作池,高并发场景下保持稳定性。
流畅的 Builder API,支持文本、图片、视频、文件、合并转发等多种消息类型。
内置 Redis 存储适配器,支持 TTL、分布式锁,插件存储自动前缀隔离。
📦 群聊部署分支新增插件
本分支在主分支基础上新增了以下自定义插件,可直接开箱使用:
分享抖音内容,自动解析视频直链,获取作者信息和标题。
随机获取高质量二次元壁纸,支持群聊和私聊。
提供 30+ 分类的二次元图片和表情包,waifu、neko、kiss 等。
AI 驱动的 GitHub 项目质量分析,生成专业 Markdown 报告。
黑白名单消息拦截,支持群组和用户粒度,优先级可配置。
自动收集群消息,由 AI 生成有趣的群刊文档,定时归档。
多平台音乐搜索、翻页、下载发送,支持网易云、酷我等平台。
自动解析群聊中的 URL 并提取相关信息,AI 驱动的内容分析。
🚀 快速开始
本指南将帮助你在 5 分钟内完成 AniaBot 的部署和配置。
前置要求
| 依赖项 | 版本要求 | 说明 |
|---|---|---|
Go |
1.26.0 | 编译运行环境 |
Redis |
7.0+ | 插件持久化存储 |
NapCat |
最新版 | QQ 协议实现 |
安装步骤
克隆仓库
# 克隆群聊部署分支
git clone https://github.com/jeanhua/AniaBot.git
cd AniaBot
配置 config.yaml
复制并编辑配置文件,填入你的 Bot 信息:
bot:
admin_id: 123456789 # 管理员 QQ 号
adapter:
ws:
address: "ws://localhost:4455"
store:
redis:
address: "localhost:6379"
password: ""
db: 0
配置 NapCat
在 NapCat 管理界面中启用 WebSocket Server,默认端口 4455。
若使用 HTTP 适配器,将 cmd/main.go 中的 NewNapcatWebSocketAdapter() 改为
NewNapcatHttpAdapter(),并配置对应的 http.listen_port 和
http.target_url。
编译运行
# 直接运行(开发模式)
go run cmd/main.go
# 编译为 Linux 二进制
scripts/build_linux.bat # Windows 下运行
# 或手动编译
GOOS=linux GOARCH=amd64 go build -o build/AniaBot ./cmd/
AniaBot 支持 config.dev.yaml 作为开发环境配置,会优先于 config.yaml 加载,方便区分开发和生产环境。
验证安装
启动成功后,在 QQ 群中发送 @Bot /help(PluginSys 系统插件),如果 Bot 回复了插件列表,说明安装成功。
🏗️ 架构概览
AniaBot 采用分层架构设计,各层职责清晰,可灵活扩展。
整体架构
目录结构
AniaBot/
├── bot/
│ ├── adapter/napcat/ # NapCat 协议适配器实现
│ ├── component/ # AI 聊天组件、Function Tool
│ ├── core/ # Bot 核心,插件管理与事件分发
│ ├── plugins/ # 内置插件(log、repeat、AI等)
│ └── utils/ # 工具函数(命令解析、消息提取)
├── common/
│ ├── adapter/ # Adapter 接口定义
│ ├── bot/ # Bot 接口定义
│ ├── model/ # 消息模型(OB11 协议结构体)
│ ├── msgchain/ # 消息链构造器
│ ├── plugin/ # Plugin 接口定义
│ └── storage/ # Storage 接口定义
├── custom/
│ └── plugins/ # 自定义插件(群聊部署分支新增)
│ ├── douyinparser/
│ ├── acgwallpaper/
│ ├── waifupics/
│ ├── githubrepoer/
│ ├── interceptor/
│ ├── groupnewsletter/
│ └── gdmusicplugin/
└── cmd/main.go # 入口,插件注册
事件流程
当 QQ 消息到达时,框架按以下顺序处理:
消息接收
Adapter 接收来自 NapCat 的原始数据,反序列化为 message.Message 结构体。
命令解析
Core 调用 utils.ParseCommand() 解析消息中的 /命令 格式和 @Bot 提及。
插件遍历
按 Order 顺序依次调用每个插件的 OnGroupMsg / OnFriendMsg。
返回值控制
插件返回 false 即可中断后续插件执行,返回 true 则继续传递。
⚙️ 配置文件详解
AniaBot 使用 YAML 格式的配置文件,支持 config.yaml(生产)和 config.dev.yaml(开发)两套配置。
Bot 核心配置
bot:
admin_id: 123456789 # 管理员 QQ 号,部分插件仅管理员可用
adapter:
# WebSocket 适配器(推荐)
ws:
address: "ws://localhost:4455"
max_retries: 5 # 连接失败最大重连次数
worker_count: 0 # 0 = 自动(CPU核数×2)
worker_queue_size: 1024 # 消息队列大小,超出则丢弃
# HTTP 适配器(备选)
http:
listen_port: 6679 # 本地监听端口
target_url: "http://localhost:6680"
store:
redis:
address: "localhost:6379"
password: ""
db: 0
AI 对话插件配置
plugin:
ai_chat_bot:
base_url: "https://api.deepseek.com"
api_key: "sk-xxxxx"
model: "deepseek-chat"
temperature: 1.2
top_p: 0.9
top_k: 100
max_token: 8192
prompt: |-
你是一个 AI 对话机器人,在 QQ 上和别人聊天
search:
token: "jina_xxxx" # Jina AI 搜索 Token
ocr:
enable: false
base_url: "https://api.siliconflow.cn/v1"
api_key: "sk-xxxxx"
model: "Qwen/Qwen3-VL-8B-Instruct"
请勿将 config.yaml 提交到公开仓库。建议在 .gitignore 中排除配置文件,或使用环境变量替代。
拦截器配置
plugin:
interceptor:
blacklist: # 黑名单(拒绝)
users:
- 123456 # 屏蔽指定用户
groups:
- 654321 # 屏蔽指定群
whitelist: # 白名单(放行)
users:
- "all" # 放行所有用户
groups:
- "all" # 放行所有群
优先级:黑名单 > 白名单 > 默认拒绝
群刊插件配置
plugin:
group_newsletter:
model:
base_url: "https://api.openai.com/v1"
api_key: "sk-xxxxx"
model: "gpt-4"
prompt: "" # 留空使用内置 Prompt
max_token: 4000
msg_threshold: 100 # 达到此条数触发自动生成
max_messages: 500 # 最大缓存消息数
enabled_groups: # 留空 = 所有群
- 123456789
GD 音乐插件配置
plugin:
gdmusic:
base_url: "https://music-api.gdstudio.xyz/api.php" # 留空使用默认
URL 解析插件配置
plugin:
url_parser:
token: "your_token"
llm:
base_url: "https://api.openai.com/v1"
api_key: "your_api_key"
model: "gpt-4o"
💡 核心 Core
bot/core 包是整个框架的大脑,负责插件管理、事件分发和生命周期控制。
AniaBot 结构体
type AniaBot struct {
ctx context.Context
cancel context.CancelFunc
adapter adapter.Adapter
plugins []plugin.Plugin
cfg *viper.Viper
storage storage.Storage
restyClient *resty.Client
pluginSet map[string]struct{}
}
创建和运行
func main() {
// 1. 创建适配器
adapter := napcat.NewNapcatWebSocketAdapter()
// 2. 创建 Bot(可选传入配置、存储、HTTP Client)
bot := core.NewAniaBot(adapter,
core.WithConfig(cfg), // 可选:自定义配置
core.WithStorage(storage), // 可选:自定义存储
)
// 3. 注册插件(顺序不影响执行,由 Order 字段决定)
bot.AddPlugin(pluginlog.NewPlugin())
bot.AddPlugin(pluginaichat.NewAIChatPlugin())
bot.AddPlugin(douyinparser.Newplugin())
// 4. 运行(阻塞)
bot.Run()
}
插件执行顺序
框架通过 plugin.Meta.Order 字段控制执行顺序,内置常量如下:
| 常量 | 值 | 适用场景 |
|---|---|---|
LevelLog |
-1000 | 日志插件,最先执行 |
LevelNormal |
0 | 普通插件(默认) |
LevelPostHandle |
1000 | 后置处理(如 AI 对话) |
超时配置
| 事件类型 | 超时时间 |
|---|---|
插件启动 Start |
1 分钟 |
| Cron 初始化 | 1 分钟 |
| Awake 事件 | 1 分钟 |
| 消息处理事件 | 5 分钟 |
| 通知事件 | 5 分钟 |
🧩 插件开发指南
插件是 AniaBot 的核心扩展机制。只需实现 plugin.Plugin 接口即可创建自己的插件。
插件接口
所有插件需实现以下接口,通常直接嵌入 plugin.Meta 来获取默认实现:
type Plugin interface {
GetMeta() *Meta // 获取插件元数据
DI // 依赖注入:SetStorage、SetRestyClient
BasicEvent // OnGroupMsg、OnFriendMsg
StartupEvent // Start、StartCron、Awake
NoticeEvent // OnGroupRecall、OnPoke 等通知事件
}
最小插件示例
package myplugin
import (
"context"
"github.com/jeanhua/AniaBot/common/bot"
"github.com/jeanhua/AniaBot/common/model/command"
"github.com/jeanhua/AniaBot/common/model/message"
"github.com/jeanhua/AniaBot/common/msgchain"
"github.com/jeanhua/AniaBot/common/plugin"
"github.com/spf13/viper"
)
type MyPlugin struct {
plugin.Meta // 嵌入 Meta,自动实现所有默认接口
}
func NewMyPlugin() *MyPlugin {
return &MyPlugin{
Meta: plugin.Meta{
Name: "我的插件",
HelpWords: "@我 /hello 打招呼",
AdminOnly: false,
ShowFor: plugininfo.ShowForGroup | plugininfo.ShowForFriend,
Author: "jeanhua",
Version: "1.0.0",
Order: plugin.LevelNormal,
},
}
}
// Start 插件初始化,读取配置
func (p *MyPlugin) Start(ctx context.Context, cfg *viper.Viper) error {
return nil
}
// OnGroupMsg 处理群聊消息,返回 (是否继续, 错误)
func (p *MyPlugin) OnGroupMsg(
ctx context.Context, bot bot.Bot,
cmd command.Command, msg message.Message,
) (bool, error) {
// 判断:@Bot 且命令为 /hello
if cmd.Mention && cmd.Name == "hello" {
builder := msgchain.Builder().Group()
builder.Mention(msg.Sender.UserId)
builder.Text(" 你好!")
bot.SendGroupMsg(msg.GroupId, builder.Build())
return false, nil // 消费此消息,不传递
}
return true, nil // 继续传递给下一插件
}
插件生命周期
| 方法 | 调用时机 | 用途 |
|---|---|---|
Start(ctx, cfg) |
Bot 启动时 | 读取配置、初始化连接、清理旧数据 |
StartCron(ctx, bot, c) |
Start 之后 | 注册定时任务 |
Awake(ctx, bot) |
所有初始化完成后 1s | 启动后台 goroutine、发送启动通知 |
OnGroupMsg |
收到群消息 | 主要业务逻辑 |
OnFriendMsg |
收到私聊消息 | 主要业务逻辑 |
OnGroupRecall 等 |
各类通知事件 | 处理撤回、禁言等事件 |
Meta 字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
Name |
string |
插件名称,用于标识插件 |
HelpWords |
string |
插件帮助描述,显示在 /help 命令中 |
AdminOnly |
bool |
为 true 时仅管理员可见该插件信息 |
ShowFor |
ShowFor |
插件显示范围:ShowForGroup(群聊)、ShowForFriend(私聊)或组合 |
Author |
string |
插件作者名称 |
Version |
string |
插件版本号,如 "1.0.0" |
Order |
int |
插件执行顺序,值越小越先执行 |
ShowFor 取值
const (
ShowForNone ShowFor = 1 // 不显示
ShowForGroup ShowFor = 1 << 1 // 仅在群聊中显示
ShowForFriend ShowFor = 1 << 2 // 仅在私聊中显示
)
// 组合示例:群聊和私聊都显示
ShowFor: plugininfo.ShowForGroup | plugininfo.ShowForFriend
使用存储
通过注入的 p.Storage 进行持久化存储,存储已自动按插件名隔离前缀:
// 保存字符串
p.Storage.SetString(ctx, "key", "value")
// 读取字符串
val, ok := p.Storage.GetString(ctx, "key")
// 保存 JSON 对象
p.Storage.Set(ctx, "user:123", userStruct)
// 带 TTL 的键
p.Storage.SetString(ctx, "lock", "1",
storage.WithTTL(time.Minute * 10),
storage.WithCheckExist(), // 不存在时才写入(原子锁)
)
注册到 main.go
import myplugin "github.com/jeanhua/AniaBot/custom/plugins/myplugin"
func main() {
// ... 其他代码
bot.AddPlugin(myplugin.NewMyPlugin())
bot.Run()
}
💬 消息链构造器
msgchain 提供了流畅的链式 API,用于构造各种类型的 QQ 消息。
基础用法
builder := msgchain.Builder().Group()
builder.Mention(userId) // @某人
builder.Text(" 你好!") // 文本
builder.Face(14) // QQ 表情(微笑)
builder.ImageUrl("https://...") // 网络图片
bot.SendGroupMsg(groupId, builder.Build())
builder := msgchain.Builder().Friend()
builder.Text("私聊回复")
builder.FileUrl("report.md", "https://...")
bot.SendFriendMsg(userId, builder.Build())
支持的消息类型
| 方法 | 说明 | 参数 |
|---|---|---|
.Text(text) |
纯文本 | 文本内容 |
.Mention(userId) |
@某人 | 用户 QQ 号(仅群聊) |
.Face(id) |
QQ 表情 | 表情 ID |
.Reply(msgId) |
回复消息 | 消息 ID |
.ImageUrl(url) |
网络图片 | 图片 URL |
.ImageBase64(b64) |
Base64 图片 | Base64 字符串 |
.ImageLocal(path) |
本地图片 | 本地文件路径 |
.VideoUrl(url) |
网络视频 | 视频 URL |
.FileUrl(name, url) |
网络文件 | 文件名 + URL |
.FileBase64(name, b64) |
Base64 文件 | 文件名 + Base64 |
.RecordUrl(url) |
网络语音 | 音频 URL |
.Raw(segs...) |
原始消息段 | OB11Segment |
合并转发消息
fbuilder := msgchain.Builder().GroupForward()
// 每条消息单独构建
for _, m := range messages {
inner := msgchain.Builder().Group()
inner.Text(m.Content)
fbuilder.Message(m.UserId, m.Nickname, inner.Build())
}
bot.SendGroupForwardMsg(groupId, fbuilder.Build())
📦 插件一览
AniaBot 包含内置插件和群聊部署分支的自定义插件两类。
内置插件
在控制台实时打印群聊/私聊消息日志,记录上次启动时间。
Order: -1000 AdminOnly连续3人发送相同消息时自动复读,支持管理员开关。
Order: 0保存最近100条群消息,支持 /explore 命令回顾历史消息。
Order: 0多轮 AI 对话,支持工具调用(搜索/表情包/文件),OCR 识图。
Order: 1000每日定时在指定群发送新闻图片,支持 /news 命令立即获取。
Order: 0自定义插件(群聊部署分支)
自动解析分享的抖音链接,获取视频直链、作者和标题。
需配置API随机获取高清二次元壁纸,内置任务队列防止并发过载。
开箱即用30+ 分类的二次元图片,发送 /waifu help 查看全部分类。
开箱即用AI 分析 GitHub 项目质量,生成专业报告,支持多种过滤参数。
需配置LLM精细化的黑白名单管理,支持 "all" 通配符。
低优先级执行收集群消息,AI 生成有趣群刊,持久化保存,重启不丢失。
需配置LLM多平台音乐搜索、翻页浏览、发送音频文件,支持6大平台。
开箱即用自动解析群聊中的 URL 并提取相关信息,AI 驱动的内容分析。
需配置LLM📋 日志打印插件
LevelLog = -1000 AdminOnly日志插件在所有插件中最先执行,将收到的群聊和私聊消息实时打印到控制台,同时记录上次启动时间。
功能说明
- 打印
[收<-群:xxx 昵称:xxx]: 消息内容格式的群聊日志 - 打印
[收<-好友:xxx 昵称:xxx]: 消息内容格式的私聊日志 - 启动时读取上次重启时间并写入新时间
消息内容提取
使用 utils.ExtraMessage() 函数,会将消息段转换为可读文本,包括 QQ 表情名、@某人、回复引用等。
🔁 复读机插件
LevelNormal = 0当同一群中连续3条相同消息时,Bot 自动复读。管理员可通过命令控制开关。
指令
实现原理
使用 sync.Map 为每个群维护一个 repeatCount 结构体,记录当前最新消息和连续次数。当连续次数达到 3
且未复读过时,触发复读并标记已复读。消息变化时重置计数器。
🛡️ 防撤回插件
LevelNormal = 0在内存中维护每个群最近 100 条消息的环形队列,支持查看历史消息和处理图片链接过期问题。
指令
图片链接处理
3分钟内的图片/文件直接转发;超过3分钟时,若能获取到 NCrkey(NapCat 鉴权 Key),则替换 rkey 参数刷新链接;否则提示已过期。
消息队列实现
使用泛型实现的 MessageQueue[T] 环形队列,容量100,Get(n) 方法取最近n条消息,线程安全。
🤖 AI 对话插件
LevelPostHandle = 1000基于 langchaingo 构建的多轮对话插件,支持 Function Calling、联网搜索、图片 OCR 等高级功能。
使用方式
内置 Function Tools
| 工具名 | 功能 | 配置要求 |
|---|---|---|
webSearch |
Jina AI 互联网搜索 | 需要 Jina Token |
webExplore |
Jina AI 网页内容抓取 | 需要 Jina Token |
time |
获取当前时间 | 无需配置 |
meme |
搜索并发送表情包 | 无需配置 |
file |
生成并发送文件 | 无需配置 |
并发锁机制
每个群/用户维护一个 Redis 分布式锁(10分钟TTL),防止同一对话同时触发多次 AI 请求。等待期间回复"正在等待响应中"。
OCR 功能
当消息中包含图片时,若启用 OCR,会调用专用视觉模型(如 Qwen-VL)描述图片内容,将结果注入到对话上下文中,实现"看图聊天"。
📰 每日新闻插件
LevelNormal = 0通过 Cron 表达式定时向指定群发送每日新闻图片,也支持手动触发。
指令
配置示例
plugin:
dailyNews:
api: "https://uapis.cn/api/v1/daily/news-image"
cron: "0 12 * * *" # 每天12点
groups:
- 123456789
- 987654321
⭐ 高级篇:自定义插件
本节介绍群聊部署分支中新增的全部自定义插件,这些插件可以作为高级插件开发的参考案例。
这些插件涵盖了异步任务队列、AI 集成、正则解析、状态管理、存储持久化等常见模式,是学习 AniaBot 插件开发的绝佳范例。
技术特性速览
| 插件 | 核心技术点 |
|---|---|
| 抖音解析 | 正则提取 URL、第三方 API 调用 |
| 二次元壁纸 / waifu | Channel 任务队列、Goroutine Worker |
| GithubRepoer | LLM 集成、Repomix API、文件发送 |
| 消息拦截器 | 优先级排序、黑白名单逻辑 |
| 群刊插件 | 存储持久化、并发防重入、LLM 生成 |
| GD 音乐 | 会话状态管理、参数解析、翻页机制 |
正则提取 → 第三方解析 API → 返回直链
Channel 任务队列、Worker Goroutine 模式
多分类验证、队列限流、Help 指令
LLM + Repomix + 文件生成完整流程
黑白名单优先级策略、低Order值执行
Redis 持久化 + 并发安全 + AI 生成
会话状态、参数解析、多平台支持
🎵 抖音解析插件
douyinparser 开箱即用(依赖公开API)通过正则表达式提取分享文本中的抖音短链,调用第三方解析 API 获取视频直链、作者信息。
指令
使用示例
用户: @Bot /douyin 7.38 分享内容... https://v.douyin.com/xxxxx/
Bot: @用户 🍔 解析成功
博主: 某某某
标题: 视频标题
视频直链: https://.....
实现原理
正则提取链接
插件在 Start 时编译正则 https://v\.douyin\.com/[a-zA-Z0-9\-_]+(?:/|\b),从分享文本中提取短链。
调用解析 API
向 https://api.xhus.cn/api/autopars 发送请求,传入短链,获取视频信息。
构造回复
解析 JSON 响应,格式化博主名、标题、视频直链,发送给用户。
本插件依赖第三方公开 API,服务稳定性由第三方保障,请勿在生产中过度依赖。
🖼️ 二次元壁纸插件
acgwallpaper 开箱即用获取随机二次元壁纸图片,内置任务队列防止并发过载,展示了 Worker Pool 模式的实现。
指令
架构模式:任务队列
这是一个经典的生产者-消费者模式:
type AcgWallpaperPlugin struct {
plugin.Meta
pendding chan work // 有界 Channel 作为任务队列
}
// Awake:启动后台 Worker Goroutine
func (p *AcgWallpaperPlugin) Awake(ctx context.Context, bot bot.Bot) error {
go p.workFunc(bot) // 永久运行的 Worker
return nil
}
// 消息处理:将任务推入队列
func (p *AcgWallpaperPlugin) OnGroupMsg(...) (bool, error) {
if cmd.Mention && cmd.Name == "acg" {
select {
case p.pendding <- work{...}: // 成功入队
default: // 队列满时告知用户
bot.SendGroupMsg(groupId, "任务队列已满...")
}
}
}
使用带缓冲的 Channel 作为任务队列(容量由构造函数 maxWork 参数控制),配合 select default 实现非阻塞的队列满降级处理。这是
Go 中处理并发任务的惯用模式。
🌸 waifu.pics 插件
waifupics 开箱即用接入 waifu.pics API,提供 30+ 分类的二次元图片和表情包。
指令
支持的分类
人像类:
waifunekoshinobumegumin
表情包类(部分):
bullycuddlecryhugawookisslickpatsmugbonkyeetdance...
共 31 个分类,发送 /waifu help 查看全部。
分类验证
插件内置分类白名单,用户输入不存在的分类时会提示错误,防止无效请求:
var categories = []string{
"waifu", "neko", "shinobu", "megumin",
"bully", "cuddle", "cry", "hug", "awoo",
// ... 31 个分类
}
func validateCate(category string) bool {
for _, c := range categories {
if category == c { return true }
}
return false
}
🔬 GithubRepoer 插件
githubrepoer 需配置 LLM API调用 Repomix API 打包 GitHub 项目代码,再由 LLM 生成专业的质量分析报告,以 Markdown 文件形式发送。
指令
高级参数
| 参数 | 说明 | 示例 |
|---|---|---|
--include=[pattern] |
只包含匹配文件 | --include=**/*.go,**/*.md |
--exclude=[pattern] |
排除匹配文件 | --exclude=**/*_test.go |
--compress |
压缩代码,只保留函数签名 | 大型项目推荐使用 |
--del-comment |
删除所有注释 | 减少 Token 消耗 |
--del-emptyline |
删除所有空行 | 减少 Token 消耗 |
工作流程
任务入队
用户发送命令后,任务推入有界 Channel,Bot 回复"正在生成中"。
Repomix 打包
向 https://api.repomix.com/api/pack 发送请求,将 GitHub 仓库代码打包为单一文本。
Token 检查
若项目代码超出配置的 max_token 限制,返回 OutOfContextError,提示用户使用参数减少代码。
LLM 分析
将代码发送给 LLM,使用系统提示词"你是 Github 项目专业分析师"生成报告。
发送报告
将 LLM 返回的 Markdown 内容 Base64 编码后,以文件形式发送给用户。
配置
plugin:
github_repoer:
max_token: 100000
model:
base_url: "https://api.deepseek.com"
api_key: "sk-xxxxx"
model: "deepseek-chat"
prompt: |-
你是Github项目专业分析师,负责分析用户提供的代码项目质量
🚧 消息拦截器插件
Order: LevelLog + 1 = -999 AdminOnly(帮助菜单隐藏)精细化的消息过滤器,支持群和用户维度的黑白名单管理。插件执行顺序仅次于日志,可在所有业务插件之前拦截消息。
优先级规则
未在白名单中的群/用户消息将被默认拒绝。使用 "all" 通配符放行所有。
配置示例
plugin:
interceptor:
blacklist:
groups: [123456] # 屏蔽这些群
users: [999999] # 屏蔽这些用户
whitelist:
groups: ["all"] # 放行所有群
users: ["all"] # 放行所有用户
实现逻辑
func (p *InterceptorPlugin) check(target int, msg message.Message) bool {
// 1. 黑名单检查(最高优先级)
for _, id := range p.interceptGroup {
if id == "all" || ieqs(msg.GroupId, id) {
return false // 拒绝
}
}
// 2. 白名单检查
for _, id := range p.permitGroup {
if id == "all" || ieqs(msg.GroupId, id) {
return true // 放行
}
}
return false // 3. 默认拒绝
}
如果白名单为空(未配置),所有消息都会被拒绝!请务必在 whitelist 中添加 "all" 或具体群号。
📜 群刊插件
groupnewsletter 需配置 LLM API自动收集群聊消息,当达到阈值时由 AI 生成有趣的群刊文档,以 Markdown 文件发送。内置持久化存储,重启后消息不丢失。
指令
自动触发流程
消息收集
每条群消息被转换为可读文本(含发送者昵称、时间)存入内存缓冲区,同时持久化到 Redis。
阈值通知
当缓冲区消息数达到 msg_threshold 且没有正在生成中的任务时,向 notifyChan 发送群号。
生成群刊
后台 processLoop 收到通知,标记生成状态(防重入),清空消息缓冲,调用 LLM 生成。
发送文件
将 Markdown 内容以 群刊_日期_uuid.md 格式发送到群内。
并发安全设计
插件使用了多层并发保护:
- 消息缓冲区:每个群独立的
sync.RWMutex - Map 访问:
sync.RWMutex msgsMutex保护groupMsgsMap - 防重入锁:
generating map+generateMu防止同一群并发生成 - 生命周期:使用独立的
pluginCtx(非框架传入的短生命周期 ctx),确保 goroutine 可正常退出
AI Prompt 设计
默认内置了详细的群刊 Prompt,包括:标题风格、内容要求、Markdown 格式要求、特色栏目(今日之星、神回复、迷惑行为大赏)等。也可在配置中自定义。
消息格式化
// 距上一条消息超过30分钟,插入时间分隔
if msg.Time - cTime > 60*30 {
fmtData.WriteString("\n" + time.Unix(msg.Time, 0).Format("2006-01-02 15:04:05") + "\n")
}
fmtData.WriteString(fmt.Sprintf(
"[UserID]:%d\n[Username]:%s\n[Content]:%s\n",
msg.UserId, msg.Nickname, msg.Content,
))
🎶 GD 音乐插件
gdmusicplugin 开箱即用基于 GD Music API 的多平台音乐搜索插件,支持搜索、翻页、发送音频文件。内置会话状态管理,用户可以浏览搜索结果并下载。
指令
支持平台
| 平台 | 参数值 | 稳定性 |
|---|---|---|
| 网易云音乐 | netease |
稳定(默认) |
| 酷我音乐 | kuwo |
稳定 |
| JOOX | joox |
稳定 |
| QQ 音乐 | tencent |
一般 |
| 酷狗音乐 | kugou |
一般 |
| 咪咕音乐 | migu |
一般 |
会话状态设计
插件为每个群和每个私聊用户维护独立的 searchSession,记录上次搜索的关键词、平台、当前页和结果:
type searchSession struct {
keyword string
source gdmusic.Source
count int // 每页条数
page int // 当前页码(从 1 开始)
results []gdmusic.SearchResult
}
type MusicPlugin struct {
plugin.Meta
client *gdmusic.Client
groupCache sync.Map // map[uint]*searchSession
friendCache sync.Map // map[uint]*searchSession
}
这种"会话状态"模式(每个用户/群维护独立状态)在需要多步交互的场景(如翻页、向导流程)中非常常见,是插件开发的重要设计模式。
参数解析
支持 -n、-s、-p 标志参数,未被标志消费的部分自动拼接为搜索关键词:
// 输入:周杰伦 晴天 -n 3 -s kuwo -p 2
keyword = "周杰伦 晴天"
count = 3
source = "kuwo"
page = 2
🔗 URL 解析插件
urlparser 需配置LLM自动解析群聊中的 URL 并提取相关信息,基于 AI 的内容分析插件。使用 LLM 分析 URL 对应页面的内容,提取关键信息并返回。
工作原理
URL 检测
监听群聊消息,使用正则表达式检测消息中的 URL 链接。
异步任务队列
将待解析的 URL 加入异步工作队列 workLoop,避免阻塞消息处理。
LLM 内容分析
调用 LLM 分析 URL 对应页面的内容,提取标题、摘要、关键信息等。
返回结果
将分析结果格式化后发送给群聊。
配置
plugin:
# URL 解析插件配置
url_parser:
token: "your_token"
llm:
base_url: "https://api.openai.com/v1"
api_key: "your_api_key"
model: "gpt-4o"
实现要点
- 异步处理:使用 channel 作为任务队列,
workLoop协程异步处理 - 并发控制:channel 带缓冲大小(100),防止任务堆积
- LLM 集成:基于 langchaingo 的 OpenAI LLM,灵活切换模型和 API
📖 Bot API 参考
bot.Bot 接口提供了插件与 QQ 交互的所有方法。
发送消息
向指定群发送消息。chain 通过 msgchain.Builder().Group() 构造。
向指定用户发送私聊消息。
发送群聊 AI 语音消息。character 为语音角色名。
发送戳一戳。groupId 为 nil 时为私聊戳一戳。
发送群聊合并转发消息(聊天记录)。通过 msgchain.Builder().GroupForward() 构造。
获取信息
获取指定消息的详情,常用于处理 Reply 消息段。
获取群成员信息,包括昵称、群名片、角色、等级等。
获取合并转发消息的详情,返回消息列表。
获取 NapCat rkey,用于刷新过期的图片/文件链接。
在指定群发送群打卡消息。
为指定消息设置表情点赞。emojiId 为表情 ID,like 为 true 表示点赞,false 表示取消点赞。
获取信息
获取 Bot 的好友列表,返回所有好友信息。
获取指定群聊的详细信息,包括群名称、成员数、群公告等。
获取群聊消息历史记录。count 指定获取的消息数量,最多 20 条。
获取与指定好友的私聊消息历史记录。count 指定获取的消息数量,最多 20 条。
获取可用的 AI 语音角色列表,包含角色名称、ID 等信息。
🗄️ 存储 API 参考
每个插件通过 p.Storage 访问 Redis 存储,存储键名自动加上插件名的 Base64 前缀,实现天然隔离。
接口定义
type Storage interface {
GetString(ctx context.Context, key string) (string, bool)
SetString(ctx context.Context, key, val string, option ...Option) bool
Get(ctx context.Context, key string, out any) bool
Set(ctx context.Context, key string, val any, option ...Option) bool
ScanKeys(ctx context.Context, pattern string, count int64) ([]string, error)
Del(ctx context.Context, key string) bool
Clear(ctx context.Context) bool
Clone(prefix string) Storage
LPush(ctx context.Context, key string, values ...any) int64
RPush(ctx context.Context, key string, values ...any) int64
LPop(ctx context.Context, key string) (any, bool)
RPop(ctx context.Context, key string) (any, bool)
LRange(ctx context.Context, key string, start, stop int64) ([]any, bool)
LLen(ctx context.Context, key string) int64
LRem(ctx context.Context, key string, count int64, value any) int64
LSet(ctx context.Context, key string, index int64, value any) bool
LIndex(ctx context.Context, key string, index int64) (any, bool)
LTrim(ctx context.Context, key string, start, stop int64) bool
}
配置选项
| 选项 | 说明 | 用途 |
|---|---|---|
storage.WithTTL(d) |
设置键过期时间 | 临时数据、锁 |
storage.WithCheckExist() |
键不存在时才写入(NX) | 分布式锁 |
List 操作
| 方法 | 说明 |
|---|---|
LPush / RPush |
向列表左侧 / 右侧批量追加元素,返回列表长度 |
LPop / RPop |
从左侧 / 右侧弹出一个元素,返回 (any, bool) |
LRange |
按区间读取列表元素,返回 ([]any, bool) |
LLen |
获取列表长度 |
LRem |
按值删除元素,返回删除数量 |
LSet / LIndex |
按索引更新 / 读取元素 |
LTrim |
裁剪列表,仅保留指定区间 |
使用示例
// 简单读写
p.Storage.SetString(ctx, "key", "value")
val, ok := p.Storage.GetString(ctx, "key")
// JSON 对象存储
type UserData struct { Score int; Level string }
p.Storage.Set(ctx, "user:123", UserData{Score: 100})
var data UserData
p.Storage.Get(ctx, "user:123", &data)
// 带 TTL 的分布式锁(NX操作)
locked := p.Storage.SetString(ctx, "lock:group:123", "1",
storage.WithCheckExist(),
storage.WithTTL(time.Minute * 10),
)
if !locked {
// 锁已被占用
}
// 子存储(多级隔离)
subStore := p.Storage.Clone("cache")
subStore.Set(ctx, "key", data)
// 模糊扫描 key
keys, err := p.Storage.ScanKeys(ctx, "group_*", 100)
// List:队列操作
p.Storage.RPush(ctx, "job_queue", "task-1", "task-2")
item, ok := p.Storage.LPop(ctx, "job_queue")
size := p.Storage.LLen(ctx, "job_queue")
items, ok := p.Storage.LRange(ctx, "job_queue", 0, -1)
// 清空当前前缀下所有键
p.Storage.Clear(ctx)
框架在初始化插件时,会自动调用 Clone(base64(pluginName)),确保不同插件的键名不会冲突,即使使用相同的 key 字符串也安全。
🔌 适配器
AniaBot 提供两种 NapCat 适配器,均实现了相同的 adapter.Adapter 接口。
WebSocket 适配器(推荐)
WebSocket 适配器提供实时双向通信,内置 Worker Pool 和自动重连机制:
- 自动重连:指数退避策略,最大重试次数可配置
- Worker Pool:可配置 worker 数量和队列大小,防止消息堆积
- ACK 机制:每个请求带 echo ID,通过 Channel 异步等待响应,超时 10s
- 并发安全:写操作通过
sync.Mutex保护
bot:
adapter:
ws:
address: "ws://localhost:4455"
max_retries: 5
worker_count: 8 # 0 = CPU * 2
worker_queue_size: 512
HTTP 适配器
HTTP 适配器通过本地监听接收 NapCat 事件推送,并通过 HTTP 请求发送消息:
- 适合无法使用 WebSocket 的场景
- 每个请求独立超时(5s)
// cmd/main.go
// adapter := napcat.NewNapcatWebSocketAdapter()
adapter := napcat.NewNapcatHttpAdapter() // 取消注释
AniaBot