diff --git a/.github/workflows/nightly-docker.yml b/.github/workflows/nightly-docker.yml
index 970bd96ce4..88178fd194 100644
--- a/.github/workflows/nightly-docker.yml
+++ b/.github/workflows/nightly-docker.yml
@@ -4,7 +4,7 @@ on: [push]
jobs:
docker-builder:
name: build docker
- runs-on: ubuntu-latest
+ runs-on: ubuntu-23.04
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@master
diff --git a/.github/workflows/pull.yml b/.github/workflows/pull.yml
index 844d450d44..d1f958b802 100644
--- a/.github/workflows/pull.yml
+++ b/.github/workflows/pull.yml
@@ -33,6 +33,9 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}
+ - name: Tidy Modules
+ run: go mod tidy
+
- name: golangci-lint
uses: golangci/golangci-lint-action@master
with:
diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index 144cebf38e..cf3d4c79f3 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -13,6 +13,9 @@ jobs:
- name: Check out code into the Go module directory
uses: actions/checkout@master
+ - name: Tidy Modules
+ run: go mod tidy
+
- name: Run Lint
uses: golangci/golangci-lint-action@master
with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index d56b29854f..7610b89072 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -21,7 +21,7 @@ jobs:
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@master
with:
- version: latest
+ version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.golangci.yml b/.golangci.yml
index fd5c16ae86..5dbaff2ffb 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -20,7 +20,7 @@ linters:
#- depguard
- dogsled
- errcheck
- - exportloopref
+ #- exportloopref
- exhaustive
#- funlen
#- goconst
diff --git a/.goreleaser.yml b/.goreleaser.yml
index f55901e517..ecf18f77ed 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -62,7 +62,7 @@ archives:
name_template: "zbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format_overrides:
- goos: windows
- format: zip
+ formats: zip
nfpms:
- license: AGPL 3.0
diff --git a/README.md b/README.md
index 09168e3272..322fef207e 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
[](https://goreportcard.com/badge/github.com/FloatTech/ZeroBot-Plugin)
[](https://t.me/zerobotplugin)
- [](https://github.com/wdvxdr1123/ZeroBot)
+ [](https://github.com/wdvxdr1123/ZeroBot)
@@ -43,8 +43,6 @@
> 专为[后 go-cqhttp 时代](https://github.com/Mrs4s/go-cqhttp/issues/2471)开发迁移的`类zbp`新机器人现已出炉,基于官方api,稳定不风控: [NanoBot-Plugin](https://github.com/FloatTech/NanoBot-Plugin)
-> 如果您不知道什么是 [OneBot](https://github.com/howmanybots/onebot) 或不希望运行多个程序,还可以直接前往 [gocqzbp](https://github.com/FloatTech/gocqzbp) 的 [Release](https://github.com/FloatTech/gocqzbp/releases) 页面下载单一可执行文件或前往 [Packages](https://github.com/FloatTech/gocqzbp/pkgs/container/gocqzbp) 页面使用`docker`,运行后按提示登录即可。
-
> 如果您对开发插件感兴趣,欢迎加入[ZeroBot-Plugin-Playground](https://github.com/FloatTech/ZeroBot-Plugin-Playground)
> webui持续开发中, 欢迎加入[ZeroBot-Plugin-Webui](https://github.com/FloatTech/ZeroBot-Plugin-Webui)
@@ -176,6 +174,16 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
- [x] 设置温度[正整数]
+
+
+ 聊天时长统计
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chatcount"`
+
+ - [x] 查询水群@xxx
+
+ - [x] 查看水群排名
+
睡眠管理
@@ -184,6 +192,18 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
- [x] 早安 | 晚安
+
+
+ 违禁词检测
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/antiabuse"
+ `
+ - [x] 添加违禁词
+
+ - [x] 删除违禁词
+
+ - [x] 查看违禁词
+
ATRI
@@ -247,6 +267,8 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
- [x] 翻牌
- [x] 赞我
+
+ - [x] 群签到
- [x] [开启 | 关闭]入群验证
@@ -268,6 +290,20 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
- 设置欢迎语可选添加参数说明:{at}可在发送时艾特被欢迎者 {nickname}是被欢迎者名字 {avatar}是被欢迎者头像 {uid}是被欢迎者QQ号 {gid}是当前群群号 {groupname} 是当前群群名
+
+
+ 群应用:AI声聊
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/airecord"`
+
+ - [x] 设置AI语音群号1048452984(tips:机器人任意所在群聊即可)
+
+ - [x] 设置AI语音模型
+
+ - [x] 查看AI语音配置
+
+ - [x] 发送AI语音xxx
+
定时指令触发器
@@ -376,6 +412,18 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 设置默认限速为每 m [分钟 | 秒] n 次触发
+
+
+ aiimage
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiimage"`
+
+ - [x] 设置AI画图密钥xxx
+ - [x] 设置AI画图接口地址https://api.siliconflow.cn/v1/images/generations
+ - [x] 设置AI画图模型名Kwai-Kolors/Kolors
+ - [x] 查看AI画图配置
+ - [x] AI画图 [描述]
+
AIWife
@@ -392,6 +440,18 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 支付宝到账 1
+
+
+ AnimeTrace 动画/Galgame识别
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace"`
+
+ 基于[AnimeTrace](https://ai.animedb.cn/)API 的识图搜索插件
+
+ - [x] Gal识图 | Gal识图 [模型名]
+
+ - [x] 动漫识图 | 动漫识图 2 | 动漫识图 [模型名]
+
触发者撤回时也自动撤回
@@ -585,6 +645,17 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 磕cp大老师 雪乃
+
+
+ 奇怪语言加解密
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/crypter"`
+
+ - [x] 齁语加密 [文本] 或 h加密 [文本]
+ - [x] 齁语解密 [密文] 或 h解密 [密文]
+ - [x] fumo加密 [文本]
+ - [x] fumo解密 [文本]
+
今日早报
@@ -659,6 +730,16 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] [emoji][emoji]
+
+
+ 颜文字抽象转写
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/emozi"`
+
+ - [x] 抽象转写[文段]
+ - [x] 抽象还原[文段]
+ - [x] 抽象登录[用户名]
+
好友申请及群聊邀请事件处理
@@ -933,12 +1014,26 @@ print("run[CQ:image,file="+j["img"]+"]")
- 日韩 VITS 模型拟声
+ Minecraft服务器监控&订阅
+
+`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/minecraftobserver"`
- `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moegoe"`
+- [x] mc服务器状态 [服务器IP/URI]
+- [x] mc服务器添加订阅 [服务器IP/URI]
+- [x] mc服务器取消订阅 [服务器IP/URI]
+- [x] mc服务器订阅拉取 (需要插件定时任务配合使用,全局只需要设置一个)
+ - 使用job插件设置定时, 对话例子如下::
+ - 记录在"@every 1m"触发的指令
+ - (机器人回答:您的下一条指令将被记录,在@@every 1m时触发)
+ - mc服务器订阅拉取
+
+
+ Movies猫眼电影查询
- - [x] 让[派蒙|空|荧|阿贝多|枫原万叶|温迪|八重神子|纳西妲|钟离|诺艾尔|凝光|托马|北斗|莫娜|荒泷一斗|提纳里|芭芭拉|艾尔海森|雷电将军|赛诺|琴|班尼特|五郎|神里绫华|迪希雅|夜兰|辛焱|安柏|宵宫|云堇|妮露|烟绯|鹿野院平藏|凯亚|达达利亚|迪卢克|可莉|早柚|香菱|重云|刻晴|久岐忍|珊瑚宫心海|迪奥娜|戴因斯雷布|魈|神里绫人|丽莎|优菈|凯瑟琳|雷泽|菲谢尔|九条裟罗|甘雨|行秋|胡桃|迪娜泽黛|柯莱|申鹤|砂糖|萍姥姥|奥兹|罗莎莉亚|式大将|哲平|坎蒂丝|托克|留云借风真君|昆钧|塞琉斯|多莉|大肉丸|莱依拉|散兵|拉赫曼|杜拉夫|阿守|玛乔丽|纳比尔|海芭夏|九条镰治|阿娜耶|阿晃|阿扎尔|七七|博士|白术|埃洛伊|大慈树王|女士|丽塔|失落迷迭|缭乱星棘|伊甸|伏特加女孩|狂热蓝调|莉莉娅|萝莎莉娅|八重樱|八重霞|卡莲|第六夜想曲|卡萝尔|姬子|极地战刃|布洛妮娅|次生银翼|理之律者|迷城骇兔|希儿|魇夜星渊|黑希儿|帕朵菲莉丝|天元骑英|幽兰黛尔|德丽莎|月下初拥|朔夜观星|暮光骑士|明日香|李素裳|格蕾修|梅比乌斯|渡鸦|人之律者|爱莉希雅|爱衣|天穹游侠|琪亚娜|空之律者|薪炎之律者|云墨丹心|符华|识之律者|维尔薇|芽衣|雷之律者|阿波尼亚]说(中文)
+`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/movies"`
+- [x] 今日电影
+- [x] 预售电影
摸鱼
@@ -982,6 +1077,10 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 酷我点歌[xxx]
- [x] 酷狗点歌[xxx]
+
+ - [x] qq点歌[xxx]
+
+ - [x] 咪咕点歌[xxx]
@@ -1001,22 +1100,6 @@ print("run[CQ:image,file="+j["img"]+"]")
- 注:刷新文件夹较慢,请耐心等待刷新完成,会提示“成功”。
-
-
- 抽wife
-
- `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativewife"`
-
- - [x] 抽wife[@xxx]
-
- - [x] 添加wife[名字][图片]
-
- - [x] 删除wife[名字]
-
- - [x] [让 | 不让]所有人均可添加wife
-
- - 注:不同群添加后不会重叠
-
拼音首字母释义工具
@@ -1035,6 +1118,40 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 搜索日语语法 [xxx]
+
+
+ 牛牛大作战
+
+`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/niuniu" `
+
+- [x] 打胶
+
+- [x] 使用[道具名称]打胶
+
+- [x] jj[@xxx]
+
+- [x] 使用[道具名称]jj[@xxx]
+
+- [x] 赎牛牛
+
+- [x] 牛牛拍卖行
+
+- [x] 出售牛牛
+
+- [x] 牛牛商店
+
+- [x] 牛牛背包
+
+- [x] 注册牛牛
+
+- [x] 注销牛牛
+
+- [x] 牛子长度排行
+
+- [x] 牛子深度排行
+
+- [x] 查看我的牛牛
+
小说
@@ -1059,6 +1176,22 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 当图片属于非 neutral 类别时自动发送评价(默认禁用,启用输入 /启用 nsfwauto)
+
+
+ 抽wife
+
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nwife"`
+
+ - [x] 抽wife[@xxx]
+
+ - [x] 添加wife[名字][图片]
+
+ - [x] 删除wife[名字]
+
+ - [x] [让 | 不让]所有人均可添加wife
+
+ - 注:不同群添加后不会重叠
+
浅草寺求签
@@ -1151,6 +1284,17 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 打劫[对方Q号|@对方QQ]
+
+
+ RSSHub
+
+`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub"`
+
+- [x] 添加rsshub订阅-/bookfere/weekly
+- [x] 删除rsshub订阅-/bookfere/weekly
+- [x] 查看rsshub订阅列表
+- [x] rsshub同步 (使用job执行定时任务------记录在"@every 10m"触发的指令)
+
在线代码运行
@@ -1271,14 +1415,6 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] >TL 你好
-
-
- vits猫雷
-
- `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/vitsnyaru"`
-
- - [x] 让猫雷说[xxxx]
-
vtb语录
@@ -1297,10 +1433,18 @@ print("run[CQ:image,file="+j["img"]+"]")
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wallet"`
- - [x] 查看我的钱包
-
- [x] 查看钱包排名
+ - [x] 设置硬币名称[ATRI币]
+
+ - [x] 管理钱包余额[+金额|-金额][@xxx]
+
+ - [x] 查看我的钱包|查看钱包余额[@xxx]
+
+ - [x] 钱包转账[金额][@xxx]
+
+ - 注:仅超级用户能"管理钱包余额",
+
据意查句
@@ -1386,7 +1530,7 @@ print("run[CQ:image,file="+j["img"]+"]")
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/word_count"`
- - [x] 热词 [群号] [消息数目]|热词 123456 1000
+ - [x] 热词 [消息数目]|热词 1000
@@ -1494,29 +1638,42 @@ print("run[CQ:image,file="+j["img"]+"]")
### *低优先级*
- 骂人
+ OpenAI聊天
- `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse"`
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat"`
- - [x] 骂我
-
- - [x] 大力骂我
+ - [x] 设置AI聊天触发概率10
+ - [x] 设置AI聊天温度80
+ - [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]
+ - [x] 设置AI聊天(不)支持系统提示词
+ - [x] 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions
+ - [x] 设置AI聊天密钥xxx
+ - [x] 设置AI聊天模型名Qwen/Qwen3-8B
+ - [x] 查看AI聊天系统提示词
+ - [x] 重置AI聊天系统提示词
+ - [x] 设置AI聊天系统提示词xxx
+ - [x] 设置AI聊天分隔符``(留空则清除)
+ - [x] 设置AI聊天(不)响应AT
+ - [x] 设置AI聊天最大长度4096
+ - [x] 设置AI聊天TopP 0.9
+ - [x] 设置AI聊天(不)以AI语音输出
+ - [x] 查看AI聊天配置
+ - [x] 重置AI聊天
+ - [x] 群聊总结 [消息数目]|群聊总结 1000
- 人工智能回复
-
- `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aireply"`
+ 骂人
- - [x] @Bot 任意文本(任意一句话回复)
+ `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse"`
- - [x] 设置文字回复模式[婧枫|沫沫|青云客|小爱|ChatGPT]
+ - [x] 骂我
- - [x] 设置 ChatGPT api key xxx
+ - [x] 大力骂我
- 词典匹配回复
+ 词典匹配回复, 仅@触发
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/thesaurus"`
@@ -1537,8 +1694,7 @@ print("run[CQ:image,file="+j["img"]+"]")
### 1. 使用稳定版/测试版 (推荐)
-可以前往[Release](https://github.com/FloatTech/ZeroBot-Plugin/releases)页面下载对应系统版本可执行文件,编译时开启了全部插件。您还可以选择 [gocqzbp](https://github.com/FloatTech/gocqzbp) 的 [Release](https://github.com/FloatTech/gocqzbp/releases) 或 [Package](https://github.com/FloatTech/gocqzbp/pkgs/container/gocqzbp),它是 [Mrs4s/go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 与本插件的合体。
-
+可以前往[Release](https://github.com/FloatTech/ZeroBot-Plugin/releases)页面下载对应系统版本可执行文件,编译时开启了全部插件。
### 2. 本地直接运行
1. 下载安装最新 [Go](https://studygolang.com/dl) 环境
diff --git a/console/console_windows.go b/console/console_windows.go
index e4b5ed1b34..4b93161ec1 100644
--- a/console/console_windows.go
+++ b/console/console_windows.go
@@ -38,12 +38,18 @@ func setConsoleTitle(title string) (err error) {
}
func init() {
+ debugMode := os.Getenv("DEBUG_MODE") == "1"
stdin := windows.Handle(os.Stdin.Fd())
var mode uint32
err := windows.GetConsoleMode(stdin, &mode)
if err != nil {
- panic(err)
+ if debugMode {
+ logrus.Warnf("调试模式下忽略控制台模式获取失败: %v", err)
+ return // 调试模式下直接返回,跳过后续配置
+ } else {
+ panic(err) // 非调试模式下 panic
+ }
}
mode &^= windows.ENABLE_QUICK_EDIT_MODE // 禁用快速编辑模式
diff --git a/custom/.gitignore b/custom/.gitignore
new file mode 100644
index 0000000000..8229bdc7e4
--- /dev/null
+++ b/custom/.gitignore
@@ -0,0 +1,4 @@
+!.gitignore
+!doc.go
+!plugin
+*
diff --git a/custom/doc.go b/custom/doc.go
new file mode 100644
index 0000000000..4eaacf6d95
--- /dev/null
+++ b/custom/doc.go
@@ -0,0 +1,2 @@
+// Package custom 注册用户自定义插件于此
+package custom
diff --git a/custom/plugin/.gitignore b/custom/plugin/.gitignore
new file mode 100644
index 0000000000..593bcf0e80
--- /dev/null
+++ b/custom/plugin/.gitignore
@@ -0,0 +1,2 @@
+!.gitignore
+*
diff --git a/data b/data
index 69b0c8a9fc..328d7638e6 160000
--- a/data
+++ b/data
@@ -1 +1 @@
-Subproject commit 69b0c8a9fc24214db185aeccd6836ee9d38e7c3a
+Subproject commit 328d7638e6947e8ac1bc8d3f5ecd6a351a4a3b6f
diff --git a/default.nix b/default.nix
index ecde3a4a81..5d8ebfad95 100644
--- a/default.nix
+++ b/default.nix
@@ -11,6 +11,7 @@
}
),
buildGoApplication ? pkgs.buildGoApplication,
+ ...
}:
buildGoApplication {
pname = "ZeroBot-Plugin";
diff --git a/flake.lock b/flake.lock
index 4f1a905faa..36b302f6df 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
- "lastModified": 1705309234,
- "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -28,11 +28,11 @@
]
},
"locked": {
- "lastModified": 1705314449,
- "narHash": "sha256-yfQQ67dLejP0FLK76LKHbkzcQqNIrux6MFe32MMFGNQ=",
+ "lastModified": 1742209644,
+ "narHash": "sha256-jMy1XqXqD0/tJprEbUmKilTkvbDY/C0ZGSsJJH4TNCE=",
"owner": "nix-community",
"repo": "gomod2nix",
- "rev": "30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6",
+ "rev": "8f3534eb8f6c5c3fce799376dc3b91bae6b11884",
"type": "github"
},
"original": {
@@ -43,11 +43,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1705856552,
- "narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
+ "lastModified": 1745391562,
+ "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
+ "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7",
"type": "github"
},
"original": {
@@ -57,11 +57,28 @@
"type": "github"
}
},
+ "nixpkgs-with-go_1_20": {
+ "locked": {
+ "lastModified": 1710843028,
+ "narHash": "sha256-CMbK45c4nSkGvayiEHFkGFH+doGPbgo3AWfecd2t1Fk=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "33c51330782cb486764eb598d5907b43dc87b4c2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "33c51330782cb486764eb598d5907b43dc87b4c2",
+ "type": "github"
+ }
+ },
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
- "nixpkgs": "nixpkgs"
+ "nixpkgs": "nixpkgs",
+ "nixpkgs-with-go_1_20": "nixpkgs-with-go_1_20"
}
},
"systems": {
diff --git a/flake.nix b/flake.nix
index 4d7480a98a..21b6eec41c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,6 +1,7 @@
{
description = "基于 ZeroBot 的 OneBot 插件";
+ inputs.nixpkgs-with-go_1_20.url = "github:NixOS/nixpkgs/33c51330782cb486764eb598d5907b43dc87b4c2";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.gomod2nix.url = "github:nix-community/gomod2nix";
@@ -10,14 +11,25 @@
outputs = {
self,
nixpkgs,
+ nixpkgs-with-go_1_20,
flake-utils,
gomod2nix,
- }: let
+ ...
+ } @ inputs: let
allSystems = flake-utils.lib.allSystems;
in (
flake-utils.lib.eachSystem allSystems
(system: let
- pkgs = nixpkgs.legacyPackages.${system};
+ old-nixpkgs = nixpkgs-with-go_1_20.legacyPackages.${system};
+ pkgs = import nixpkgs {
+ inherit system;
+
+ overlays = [
+ (_: _: {
+ go_1_20 = old-nixpkgs.go_1_20;
+ })
+ ];
+ };
# The current default sdk for macOS fails to compile go projects, so we use a newer one for now.
# This has no effect on other platforms.
@@ -25,11 +37,10 @@
in {
# doCheck will fail at write files
packages = rec {
-
- ZeroBot-Plugin =
- (callPackage ./. {
+ ZeroBot-Plugin = (callPackage ./. (inputs
+ // {
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
- })
+ }))
.overrideAttrs (_: {doCheck = false;});
default = ZeroBot-Plugin;
@@ -42,7 +53,6 @@
pkgs.cacert
];
};
-
};
devShells.default = callPackage ./shell.nix {
inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix;
diff --git a/go.mod b/go.mod
index ccebaf5048..d73abcea98 100644
--- a/go.mod
+++ b/go.mod
@@ -4,95 +4,104 @@ go 1.20
require (
github.com/Baidu-AIP/golang-sdk v1.1.1
- github.com/FloatTech/AnimeAPI v1.7.1-0.20240530072450-71c23d2f01f8
- github.com/FloatTech/floatbox v0.0.0-20240505082030-226ec6713e14
- github.com/FloatTech/gg v1.1.3-0.20230226151425-6ea91286ba08
+ github.com/FloatTech/AnimeAPI v1.7.1-0.20250901143505-180d33844860
+ github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80
+ github.com/FloatTech/gg v1.1.3
github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef
- github.com/FloatTech/rendercard v0.0.10-0.20230223064326-45d29fa4ede9
- github.com/FloatTech/sqlite v1.6.3
- github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1
- github.com/FloatTech/zbpctrl v1.6.1
- github.com/FloatTech/zbputils v1.7.2-0.20240530064059-af6f6773ba94
+ github.com/FloatTech/rendercard v0.2.0
+ github.com/FloatTech/sqlite v1.7.1
+ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562
+ github.com/FloatTech/zbpctrl v1.7.0
+ github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
- github.com/antchfx/htmlquery v1.3.1
+ github.com/Tnze/go-mc v1.20.2
+ github.com/antchfx/htmlquery v1.3.4
github.com/corona10/goimagehash v1.1.0
github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3
github.com/disintegration/imaging v1.6.2
github.com/fumiama/ahsai v0.1.0
github.com/fumiama/cron v1.3.0
+ github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d
github.com/fumiama/go-base16384 v1.7.0
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565
- github.com/fumiama/terasu v0.0.0-20240507144117-547a591149c0
+ github.com/fumiama/slowdo v0.0.0-20241001074058-27c4fe5259a4
+ github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce
github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6
+ github.com/go-ego/gse v0.80.3
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
+ github.com/google/uuid v1.6.0
github.com/jinzhu/gorm v1.9.16
github.com/jozsefsallai/gophersauce v1.0.1
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5
github.com/lithammer/fuzzysearch v1.1.8
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5
+ github.com/mmcdole/gofeed v1.3.0
github.com/mroth/weightedrand v1.0.0
github.com/notnil/chess v1.9.0
github.com/pkg/errors v0.9.1
- github.com/shirou/gopsutil/v3 v3.24.4
+ github.com/shirou/gopsutil/v3 v3.24.5
github.com/sirupsen/logrus v1.9.3
- github.com/tidwall/gjson v1.17.1
- github.com/wcharczuk/go-chart/v2 v2.1.1
- github.com/wdvxdr1123/ZeroBot v1.7.5-0.20240505070304-562ffeb33dcd
+ github.com/tidwall/gjson v1.18.0
+ github.com/wcharczuk/go-chart/v2 v2.1.2
+ github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250804063440-ccc03e33ac20
gitlab.com/gomidi/midi/v2 v2.1.7
- golang.org/x/image v0.16.0
- golang.org/x/sys v0.20.0
- golang.org/x/text v0.15.0
+ golang.org/x/image v0.24.0
+ golang.org/x/sys v0.30.0
+ golang.org/x/text v0.22.0
gopkg.in/yaml.v3 v3.0.1
)
require (
+ github.com/PuerkitoBio/goquery v1.8.0 // indirect
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // indirect
- github.com/antchfx/xpath v1.3.0 // indirect
- github.com/blend/go-sdk v1.20220411.3 // indirect
+ github.com/andybalholm/cascadia v1.3.1 // indirect
+ github.com/antchfx/xpath v1.3.3 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // indirect
github.com/faiface/beep v1.1.0 // indirect
github.com/fumiama/go-simple-protobuf v0.2.0 // indirect
github.com/fumiama/gofastTEA v0.0.10 // indirect
- github.com/fumiama/imgsz v0.0.4 // indirect
+ github.com/fumiama/imgsz v0.0.2 // indirect
github.com/gabriel-vasile/mimetype v1.0.4 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
- github.com/google/uuid v1.6.0 // indirect
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/jfreymuth/oggvorbis v1.0.1 // indirect
github.com/jfreymuth/vorbis v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
- github.com/kr/text v0.2.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
github.com/pkumza/numcn v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
- github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tetratelabs/wazero v1.5.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
+ github.com/vcaesar/cedar v0.20.2 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
- golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
- golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
- golang.org/x/net v0.24.0 // indirect
- modernc.org/libc v1.49.3 // indirect
+ golang.org/x/exp/shiny v0.0.0-20250305212735-054e65f0b394 // indirect
+ golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
+ golang.org/x/net v0.33.0 // indirect
+ modernc.org/libc v1.61.0 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
- modernc.org/sqlite v1.20.0 // indirect
+ modernc.org/sqlite v1.33.1 // indirect
)
replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.29.10-simp
diff --git a/go.sum b/go.sum
index 9eef0d5c5f..d337514073 100644
--- a/go.sum
+++ b/go.sum
@@ -1,43 +1,46 @@
github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw=
github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
-github.com/FloatTech/AnimeAPI v1.7.1-0.20240530072450-71c23d2f01f8 h1:2i36tl5VlBWxWxv4WyfWmCA23NaV1fB5/smJtdORHf4=
-github.com/FloatTech/AnimeAPI v1.7.1-0.20240530072450-71c23d2f01f8/go.mod h1:Ru6q5pZUnfMg1iu0M1Hp73q9N3LNIbDr16kjkzyG6Xk=
-github.com/FloatTech/floatbox v0.0.0-20240505082030-226ec6713e14 h1:8O0Iq9MnKsKowltY9txhOqcJdmGTjxHPQ4gEYzbJc9A=
-github.com/FloatTech/floatbox v0.0.0-20240505082030-226ec6713e14/go.mod h1:OzGLhvmtz1TKIdGaJDd8pQumvD36UqK+dWsiCISmzQQ=
-github.com/FloatTech/gg v1.1.3-0.20230226151425-6ea91286ba08 h1:dPLeoiTVSBlgls+66EB/UJ2e38BaASmBN5nANaycSBU=
-github.com/FloatTech/gg v1.1.3-0.20230226151425-6ea91286ba08/go.mod h1:uzPzAeT35egARdRuu+1oyjU3CmTwCceoq3Vvje7LpcI=
+github.com/FloatTech/AnimeAPI v1.7.1-0.20250901143505-180d33844860 h1:ddthsMzYC2LZ517/71W//9VsXT82CSBALVt3sQY5vfA=
+github.com/FloatTech/AnimeAPI v1.7.1-0.20250901143505-180d33844860/go.mod h1:CzpSeo5Pvslnq7Ho14E438Yn/flFMKzjGeX2nbC1mzk=
+github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 h1:lFD1pd8NkYCrw0QpTX/T5pJ67I7AL5eGxQ4v0r9f81Q=
+github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80/go.mod h1:IWoFFqu+0FeaHHQdddyiTRL5z7gJME6qHC96qh0R2sc=
+github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4=
+github.com/FloatTech/gg v1.1.3/go.mod h1:/9oLP54CMfq4r+71XL26uaFTJ1uL1boAyX67680/1HE=
github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef h1:CJbK/2FRwPuZpeb6M4sWK2d7oXDnBEGhpkQuQrgc91A=
github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef/go.mod h1:el5hGpj1C1bDRxcTXYRwEivDCr40zZeJpcrLrB1fajs=
-github.com/FloatTech/rendercard v0.0.10-0.20230223064326-45d29fa4ede9 h1:hffajvmQFfP68U6wUwHemPuuwCUoss+SEFfoLYwbGwE=
-github.com/FloatTech/rendercard v0.0.10-0.20230223064326-45d29fa4ede9/go.mod h1:NBFPhWae4hqVMeG8ELBBnUQkKce3nDjkljVn6PdiUNs=
-github.com/FloatTech/sqlite v1.6.3 h1:MQkqBNlkPuCoKQQgoNLuTL/2Ci3tBTFAnVYBdD0Wy4M=
-github.com/FloatTech/sqlite v1.6.3/go.mod h1:zFbHzRfB+CJ+VidfjuVbrcin3DAz283F7hF1hIeHzpY=
-github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 h1:g4pTnDJUW4VbJ9NvoRfUvdjDrHz/6QhfN/LoIIpICbo=
-github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
-github.com/FloatTech/zbpctrl v1.6.1 h1:SilK5R2poO8iUT6JPzpgr/BSzxYAaybBYNZkMyZ8STw=
-github.com/FloatTech/zbpctrl v1.6.1/go.mod h1:I+MetM++1sJhNPg3zww1aw04BicYsNohvHC4Jh52XSo=
-github.com/FloatTech/zbputils v1.7.2-0.20240530064059-af6f6773ba94 h1:ITQPmNSHE5bNFBpdwldUpfTDLXROEahSKspkJrSLWvQ=
-github.com/FloatTech/zbputils v1.7.2-0.20240530064059-af6f6773ba94/go.mod h1:nHWYtF4g2NRv3GXZiAZDvgPjdcHGUaQHxGgD0aHz30I=
+github.com/FloatTech/rendercard v0.2.0 h1:PBTZ2gCEy/dAEGSfWecrGTrWDYpiBJD1dVzNDDaOxh4=
+github.com/FloatTech/rendercard v0.2.0/go.mod h1:Sbojcy1t3NfFz7/WicZRmR/uKFxNMYkKF8qHx69dxY0=
+github.com/FloatTech/sqlite v1.7.1 h1:XKUY0+MNaRmvEIgRv7QLbl7PFVpUfQ72+XQg+no2Vq0=
+github.com/FloatTech/sqlite v1.7.1/go.mod h1:/4tzfCGhrZnnjC1U8vcfwGQeF6eR649fhOsS3+Le0+s=
+github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ/VCf80LiQo9C7jHgrunZDwiRcY=
+github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
+github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo=
+github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE=
+github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f h1:5jnrFe9FTydb/pcUhxkWHuQVCwmYIZmneOkvmgHOwGI=
+github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f/go.mod h1:HG/yZwExV3b1Vqu4chbqwhfX4hx7gDS07QO436JkwIg=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
+github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 h1:bBmmB7he0iVN4m5mcehfheeRUEer/Avo4ujnxI3uCqs=
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5/go.mod h1:0UcFaCkhp6vZw6l5Dpq0Dp673CoF9GdvA8lTfst0GiU=
+github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q=
+github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5UaBEQLjcvn7aAU01nqU/NUyOBEU+ew=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
-github.com/antchfx/htmlquery v1.3.1 h1:wm0LxjLMsZhRHfQKKZscDf2COyH4vDYA3wyH+qZ+Ylc=
-github.com/antchfx/htmlquery v1.3.1/go.mod h1:PTj+f1V2zksPlwNt7uVvZPsxpKNa7mlVliCRxLX6Nx8=
-github.com/antchfx/xpath v1.3.0 h1:nTMlzGAK3IJ0bPpME2urTuFL76o4A96iYvoKFHRXJgc=
-github.com/antchfx/xpath v1.3.0/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
-github.com/blend/go-sdk v1.20220411.3 h1:GFV4/FQX5UzXLPwWV03gP811pj7B8J2sbuq+GJQofXc=
-github.com/blend/go-sdk v1.20220411.3/go.mod h1:7lnH8fTi6U4i1fArEXRyOIY2E1X4MALg09qsQqY1+ak=
+github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
+github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ=
+github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM=
+github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs=
+github.com/antchfx/xpath v1.3.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/corona10/goimagehash v1.1.0 h1:teNMX/1e+Wn/AYSbLHX8mj+mF9r60R1kBeqE9MkoYwI=
github.com/corona10/goimagehash v1.1.0/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -60,6 +63,8 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4=
github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk=
github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo=
github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY=
+github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d h1:iGxnST620IHrJ47DXkjzrZJ2rskBogWze+UyvnAxT6g=
+github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY=
github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs=
@@ -70,16 +75,18 @@ github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoG
github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk=
github.com/fumiama/gotracemoe v0.0.3 h1:iI5EbE9A3UUbfukG6+/soYPjp1S31eCNYf4tw7s6/Jc=
github.com/fumiama/gotracemoe v0.0.3/go.mod h1:tyqahdUzHf0bQIAVY/GYmDWvYYe5ik1ZbhnGYh+zl40=
-github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI=
-github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E=
+github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak=
+github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4=
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 h1:sQuR2+N5HurnvsZhiKdEg+Ig354TaqgCQRxd/0KgIOQ=
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565/go.mod h1:UUEvyLTJ7yoOA/viKG4wEis4ERydM7+Ny6gZUWgkS80=
github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5 h1:jDxsIupsT84A6WHcs6kWbst+KqrRQ8/o0VyoFMnbBOA=
github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5/go.mod h1:15P6ublJ9FJR8YQCGy8DeQ2Uwur7iW9Hserr/T3OFZE=
+github.com/fumiama/slowdo v0.0.0-20241001074058-27c4fe5259a4 h1:zN9e09TYKXI1mNkuS6YbH+Sn+4k5tBir+ovhZZcRYAs=
+github.com/fumiama/slowdo v0.0.0-20241001074058-27c4fe5259a4/go.mod h1:iZf1H/Jcw5gjOOFb4C5nlweJtViWc7uwUxRCe14pbYk=
github.com/fumiama/sqlite3 v1.29.10-simp h1:c5y3uKyU0q9t0/SyfynzYyuslQ5zP+5CD8e0yYY554A=
github.com/fumiama/sqlite3 v1.29.10-simp/go.mod h1:ItX2a1OVGgNsFh6Dv60JQvGfJfTPHPVpV6DF59akYOA=
-github.com/fumiama/terasu v0.0.0-20240507144117-547a591149c0 h1:So/3Bg/m2ZcUvqCzzEjjkjHBjcvnV3AN5tCxwsdMwYU=
-github.com/fumiama/terasu v0.0.0-20240507144117-547a591149c0/go.mod h1:UVx8YP1jKKL1Cj+uy+OnQRM2Ih6U36Mqy9GSf7jabsI=
+github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce h1:T6iDDU16rFyxV/FwfJJR6qcgkIlXJEIFlUTSmTD1h6s=
+github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce/go.mod h1:UVx8YP1jKKL1Cj+uy+OnQRM2Ih6U36Mqy9GSf7jabsI=
github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6 h1:LtDgr628eji8jRpjPCxsk7ibjcfi97QieZVCTjxLCBw=
github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6/go.mod h1:lEaZsT4FRSqcjnQ5q8y+mkenkzR/r1D3BJmfdp0vqDg=
github.com/gabriel-vasile/mimetype v1.0.4 h1:uBejfH8l3/2f+5vjl1e4xIaSyNEhRBZ5N/ij7ohpNd8=
@@ -89,6 +96,8 @@ github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebK
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
+github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
+github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -101,9 +110,9 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
@@ -120,15 +129,14 @@ github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jozsefsallai/gophersauce v1.0.1 h1:BA3ovtQRrAb1qYU9JoRLbDHpxnDunlNcEkEfhCvDDCM=
github.com/jozsefsallai/gophersauce v1.0.1/go.mod h1:YVEI7djliMTmZ1Vh01YPF8bUHi+oKhe3yXgKf1T49vg=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY=
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5/go.mod h1:c9+VS9GaommgIOzNWb5ze4lYwfT8BZ2UDyGiuQTT7yc=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
@@ -149,6 +157,15 @@ github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
+github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4=
+github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE=
+github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk=
+github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E=
github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
@@ -170,30 +187,22 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
-github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
-github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
-github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
+github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
+github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
-github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0=
github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
-github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
-github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
@@ -202,10 +211,13 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
-github.com/wcharczuk/go-chart/v2 v2.1.1 h1:2u7na789qiD5WzccZsFz4MJWOJP72G+2kUuJoSNqWnE=
-github.com/wcharczuk/go-chart/v2 v2.1.1/go.mod h1:CyCAUt2oqvfhCl6Q5ZvAZwItgpQKZOkCJGb+VGv6l14=
-github.com/wdvxdr1123/ZeroBot v1.7.5-0.20240505070304-562ffeb33dcd h1:atmeLC1rrs5XIk61rYDgFZDTaezYtSQzndvQ+L7fgaU=
-github.com/wdvxdr1123/ZeroBot v1.7.5-0.20240505070304-562ffeb33dcd/go.mod h1:J6uHaXS/Am2VsLxF9TcU6il19PbOeC4SvgxHJ1E2jaE=
+github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4=
+github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
+github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
+github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
+github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
+github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250804063440-ccc03e33ac20 h1:Yzd+cbiJQYtf6cZDP5ZB/LqjNWiV752+5P6Eua+wnic=
+github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250804063440-ccc03e33ac20/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
@@ -215,20 +227,29 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp/shiny v0.0.0-20250305212735-054e65f0b394 h1:bFYqOIMdeiCEdzPJkLiOoMDzW/v3tjW4AA/RmUZYsL8=
+golang.org/x/exp/shiny v0.0.0-20250305212735-054e65f0b394/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
-golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
-golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
-golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
+golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
+golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
+golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
+golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -236,14 +257,23 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
-golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
+golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -253,6 +283,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -261,29 +292,45 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/gomod2nix.toml b/gomod2nix.toml
index 7d85c7286f..12c63f8e72 100644
--- a/gomod2nix.toml
+++ b/gomod2nix.toml
@@ -5,38 +5,41 @@ schema = 3
version = "v1.1.1"
hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo="
[mod."github.com/FloatTech/AnimeAPI"]
- version = "v1.7.1-0.20240530072450-71c23d2f01f8"
- hash = "sha256-NUYNGhjVW5bdpWIeKjBhnVTsjf6OXNqCcqzrRd3c+gE="
+ version = "v1.7.1-0.20250901143505-180d33844860"
+ hash = "sha256-k1MlgaBGwpaqoVk+8WYfXoVLfzqyEQW5LQaJgBKlhUA="
[mod."github.com/FloatTech/floatbox"]
- version = "v0.0.0-20240505082030-226ec6713e14"
- hash = "sha256-v296D9T1QzFmcHQJNxJvx7sMtK+Jd1TUHXWqZtIvvf4="
+ version = "v0.0.0-20250513111443-adba80e84e80"
+ hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE="
[mod."github.com/FloatTech/gg"]
- version = "v1.1.3-0.20230226151425-6ea91286ba08"
- hash = "sha256-AeMzjMK1ZwFERb5xuNAV5PdHp7rzoT4ZF7kZ6Kj2/0s="
+ version = "v1.1.3"
+ hash = "sha256-7K/R2mKjUHVnoJ3b1wDObJ5Un2Htj59Y97G1Ja1tuPo="
[mod."github.com/FloatTech/imgfactory"]
version = "v0.2.2-0.20230413152719-e101cc3606ef"
hash = "sha256-2okFyPQSYIxrc8hxICsbjEM9xq25a3I2A4wmDIYFCg8="
[mod."github.com/FloatTech/rendercard"]
- version = "v0.0.10-0.20230223064326-45d29fa4ede9"
- hash = "sha256-Zn8agmyWWEC2QQfIBa60jlQrEap9Bps+z1Ekay6Y0cg="
+ version = "v0.2.0"
+ hash = "sha256-fgntEYGh2mEl618hM13kb0GGeQEXdP+lochYX8F2OXs="
[mod."github.com/FloatTech/sqlite"]
- version = "v1.6.3"
- hash = "sha256-zWPByEMi89ms67ubPg0fAPIRxfpBC2IRKc0iNVLqkPU="
+ version = "v1.7.1"
+ hash = "sha256-1x8xH5fFDlLts8YfzgO3vLF45Q7Ah+mYI6Wn8JG/qE0="
[mod."github.com/FloatTech/ttl"]
- version = "v0.0.0-20230307105452-d6f7b2b647d1"
- hash = "sha256-BQzWUzoIOLwfZa7WElqaa3EYrcz1Ql6JApgxZIQHBro="
+ version = "v0.0.0-20240716161252-965925764562"
+ hash = "sha256-/XjfdVXEzYgeM+OYuyy76tf13lO91vCcwpjWgkRGteU="
[mod."github.com/FloatTech/zbpctrl"]
- version = "v1.6.1"
- hash = "sha256-S5KSLZSUt6s7i6ZfKovlJawXF5NYkenPSNVWk+xtO8o="
+ version = "v1.7.0"
+ hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU="
[mod."github.com/FloatTech/zbputils"]
- version = "v1.7.2-0.20240530064059-af6f6773ba94"
- hash = "sha256-cpanspZVMKWAXUtXUFKmIiCS+Xmzr6JNNYc6xxRSMb0="
+ version = "v1.7.2-0.20250812085410-2741050f465f"
+ hash = "sha256-NoCU7tqzihm2xEr1LelrfMzeg9RDQ9OsFBVXfNDcxvs="
[mod."github.com/RomiChan/syncx"]
version = "v0.0.0-20240418144900-b7402ffdebc7"
hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4="
[mod."github.com/RomiChan/websocket"]
version = "v1.4.3-0.20220227141055-9b2c6168c9c5"
hash = "sha256-Adx+gvqB+CCoUXx7ebIaBDjVkav+wS5qZPmaqcApBWA="
+ [mod."github.com/Tnze/go-mc"]
+ version = "v1.20.2"
+ hash = "sha256-Nu4PXNxeARH0itm6yIIplFaywL2yQnPJFksmmuyIptI="
[mod."github.com/adamzy/cedar-go"]
version = "v0.0.0-20170805034717-80a9c64b256d"
hash = "sha256-N19KTxh70IUBqnchFuWkrJD8uuFOIVqv1iSuN3YFIT0="
@@ -44,14 +47,11 @@ schema = 3
version = "v0.0.0-20200320125537-f189e35d30ca"
hash = "sha256-ALeRuEJN9jHjGb4wNKJcxC59vVx8Tj7hHikEGkaZZ0s="
[mod."github.com/antchfx/htmlquery"]
- version = "v1.3.1"
- hash = "sha256-4ZzKk7Z+vH8ytisdtcZz/Y0MbnVVhruiO/7gtUy3ouQ="
+ version = "v1.3.4"
+ hash = "sha256-nrtIgRgdOvo0iIQyrhHOFKOmoT8e2gduUsct3f5zDNA="
[mod."github.com/antchfx/xpath"]
- version = "v1.3.0"
- hash = "sha256-SU+Tnf5c9vsDCrY1BVKjqYLhB91xt9oHBS5bicbs2cA="
- [mod."github.com/blend/go-sdk"]
- version = "v1.20220411.3"
- hash = "sha256-yxrf24hru8NeTPUmoaJG1PcmHE5pn/U36Sj9Qg+JVqg="
+ version = "v1.3.3"
+ hash = "sha256-Ent9bgBTjKS8/61LKrIu/JcBI/Qsv6EEIojwsMjCgdY="
[mod."github.com/corona10/goimagehash"]
version = "v1.1.0"
hash = "sha256-HyS8nc7kUNnDaVBDzJ9Ym4pRs83YB4M2vHSRwfm6mr4="
@@ -76,6 +76,9 @@ schema = 3
[mod."github.com/fumiama/cron"]
version = "v1.3.0"
hash = "sha256-/sN7X8dKXQgv8J+EDzVUB+o+AY9gBC8e1C6sYhaTy1k="
+ [mod."github.com/fumiama/deepinfra"]
+ version = "v0.0.0-20250910022828-8cde75e137f4"
+ hash = "sha256-1CV8t3R91maqJztHg7whECqvS4+sxWcSvq+EyO4PyZ8="
[mod."github.com/fumiama/go-base16384"]
version = "v1.7.0"
hash = "sha256-vTAsBBYe2ISzb2Nba5E96unodZSkhMcqo6hbwR01nz8="
@@ -92,20 +95,26 @@ schema = 3
version = "v0.0.3"
hash = "sha256-O3cDkVXu5NG1ZtzubxhH+S91zfgu4uH1L+OiSGYSNXQ="
[mod."github.com/fumiama/imgsz"]
- version = "v0.0.4"
- hash = "sha256-rrGx+v41OEl0ATwL6u5TNcpfkCQbj3jFNnGiQUNu2qs="
+ version = "v0.0.2"
+ hash = "sha256-eYUjP1TKWUrsY++rzg4rezOvmvmjADZFBizIIDHnZtY="
[mod."github.com/fumiama/jieba"]
version = "v0.0.0-20221203025406-36c17a10b565"
hash = "sha256-DvDx1pdldkdaSszrbadM/VwqT9TTSmWl6G6a+ysXYEM="
+ [mod."github.com/fumiama/slowdo"]
+ version = "v0.0.0-20241001074058-27c4fe5259a4"
+ hash = "sha256-rsV3MKRCSOBMIgJXFCGbCHRY2aBAb32ftU49hT3GjqY="
[mod."github.com/fumiama/terasu"]
- version = "v0.0.0-20240507144117-547a591149c0"
- hash = "sha256-ZZG5/Ckq4R0eojmiuli5ZRToDNQt4VeRwdy0jjVCvbg="
+ version = "v0.0.0-20241027183601-987ab91031ce"
+ hash = "sha256-WiG5BD1Icwq61KpqkQdf6dl64jEhaDJb2zAQROqXwvc="
[mod."github.com/fumiama/unibase2n"]
version = "v0.0.0-20240530074540-ec743fd5a6d6"
hash = "sha256-I3xNzjrj5y0fy0dfa75V57GanfmHIHmubEn9/y0BBHw="
[mod."github.com/gabriel-vasile/mimetype"]
version = "v1.0.4"
hash = "sha256-5hl9zBo3nkPt8dZfcLoOix8lAKLm3qIkWhopoS4V34E="
+ [mod."github.com/go-ego/gse"]
+ version = "v0.80.3"
+ hash = "sha256-uxTQN4cxE/ZReZqjlIEQ3WYD9w2Ec37LRHQftJXsSZQ="
[mod."github.com/go-ole/go-ole"]
version = "v1.2.6"
hash = "sha256-+oxitLeJxYF19Z6g+6CgmCHJ1Y5D8raMi2Cb3M6nXCs="
@@ -139,9 +148,6 @@ schema = 3
[mod."github.com/kanrichan/resvg-go"]
version = "v0.0.2-0.20231001163256-63db194ca9f5"
hash = "sha256-plRZ3yhyCafCXmAD4vnFUoCTRsHmLp7Jn9gFKcEKbds="
- [mod."github.com/kr/text"]
- version = "v0.2.0"
- hash = "sha256-fadcWxZOORv44oak3jTxm6YcITcFxdGt4bpn869HxUE="
[mod."github.com/lithammer/fuzzysearch"]
version = "v1.1.8"
hash = "sha256-aMMRcrlUc9CBiiNkcnWWn4hfNMNyVhrAt67kvP4D4Do="
@@ -187,12 +193,9 @@ schema = 3
[mod."github.com/remyoudompheng/bigfft"]
version = "v0.0.0-20230129092748-24d4a6f8daec"
hash = "sha256-vYmpyCE37eBYP/navhaLV4oX4/nu0Z/StAocLIFqrmM="
- [mod."github.com/rogpeppe/go-internal"]
- version = "v1.12.0"
- hash = "sha256-qvDNCe3l84/LgrA8X4O15e1FeDcazyX91m9LmXGXX6M="
[mod."github.com/shirou/gopsutil/v3"]
- version = "v3.24.4"
- hash = "sha256-ubkBxu9X4LRhI1HqkjsIShR4e8rQsuKQs4VNOIIhZCU="
+ version = "v3.24.5"
+ hash = "sha256-tc+t1u7gf5A+Bd956dYeM8pGbxs9ezQHqKAKfLQLpuQ="
[mod."github.com/shoenig/go-m1cpu"]
version = "v0.1.6"
hash = "sha256-hT+JP30BBllsXosK/lo89HV/uxxPLsUyO3dRaDiLnCg="
@@ -203,8 +206,8 @@ schema = 3
version = "v1.5.0"
hash = "sha256-fGdJM4LJrZA9jxHuYVo4EUQ3I1k0IVG3QQCBCgZkeZI="
[mod."github.com/tidwall/gjson"]
- version = "v1.17.1"
- hash = "sha256-5R38cFZFaVbdem2B+9rsbr+0hRxbtDQ0i5PYWPT6kj0="
+ version = "v1.18.0"
+ hash = "sha256-CO6hqDu8Y58Po6A01e5iTpwiUBQ5khUZsw7czaJHw0I="
[mod."github.com/tidwall/match"]
version = "v1.1.1"
hash = "sha256-M2klhPId3Q3T3VGkSbOkYl/2nLHnsG+yMbXkPkyrRdg="
@@ -217,36 +220,39 @@ schema = 3
[mod."github.com/tklauser/numcpus"]
version = "v0.6.1"
hash = "sha256-8eFcw4YI0w6+GPhU5xMMQjiio94q/O5PpNO3QsvXve0="
+ [mod."github.com/vcaesar/cedar"]
+ version = "v0.20.2"
+ hash = "sha256-3WblBdkR9AZcvZCKSteBV5kdhahiFHG2dbLWfwrVkwM="
[mod."github.com/wcharczuk/go-chart/v2"]
- version = "v2.1.1"
- hash = "sha256-emvjt/ze8skM+MBflwV0EgS/svpaEGU/mn27Ie4VTXs="
+ version = "v2.1.2"
+ hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM="
[mod."github.com/wdvxdr1123/ZeroBot"]
- version = "v1.7.5-0.20240505070304-562ffeb33dcd"
- hash = "sha256-2VKVJJ9jqbWjEPrvqLaMEK+Qpl4HiB4nJX7ebHcbDYA="
+ version = "v1.8.2-0.20250804063440-ccc03e33ac20"
+ hash = "sha256-2bFcPmcDsZxTD3sU3i2QD4M/ehSF43Ohf5ltuq1QtOQ="
[mod."github.com/yusufpapurcu/wmi"]
version = "v1.2.4"
hash = "sha256-N+YDBjOW59YOsZ2lRBVtFsEEi48KhNQRb63/0ZSU3bA="
[mod."gitlab.com/gomidi/midi/v2"]
version = "v2.1.7"
hash = "sha256-fbgxSMCk7PVII3sNEKuGWbN56fy3eM564Xb+lnYTxRQ="
- [mod."golang.org/x/exp"]
- version = "v0.0.0-20190306152737-a1d7652674e8"
- hash = "sha256-VJ0sxFsqnx2O/NmXamL2F5bQeUw5sizVQ7NLusceK5Q="
+ [mod."golang.org/x/exp/shiny"]
+ version = "v0.0.0-20250305212735-054e65f0b394"
+ hash = "sha256-+xzaSlgRHFa+sGnQG90/72vcJMhletsob/L+KG24P/A="
[mod."golang.org/x/image"]
- version = "v0.16.0"
- hash = "sha256-+BOLefaFM/c+AV3kmnNvztbhZ+a9GCNwkEya8hZSKYg="
+ version = "v0.24.0"
+ hash = "sha256-nhcznNf4ePM7d0Jy2Si0dpMt7KQfRF5Y5QzMpwFCAVg="
[mod."golang.org/x/mobile"]
- version = "v0.0.0-20190415191353-3e0bab5405d6"
- hash = "sha256-Ds7JS9muxzDc7WgCncAd0rMSFeBI88/I0dQsk13/56k="
+ version = "v0.0.0-20231127183840-76ac6878050a"
+ hash = "sha256-GdXSvrqQiJX6pOqc2Yr8gG0ZWysEE81YRl5qkt3JCMA="
[mod."golang.org/x/net"]
- version = "v0.24.0"
- hash = "sha256-w1c21ljta5wNIyel9CSIn/crPzwOCRofNKhqmfs4aEQ="
+ version = "v0.33.0"
+ hash = "sha256-9swkU9vp6IflUUqAzK+y8PytSmrKLuryidP3RmRfe0w="
[mod."golang.org/x/sys"]
- version = "v0.20.0"
- hash = "sha256-mowlaoG2k4n1c1rApWef5EMiXd3I77CsUi8jPh6pTYA="
+ version = "v0.30.0"
+ hash = "sha256-BuhWtwDkciVioc03rxty6G2vcZVnPX85lI7tgQOFVP8="
[mod."golang.org/x/text"]
- version = "v0.15.0"
- hash = "sha256-pBnj0AEkfkvZf+3bN7h6epCD2kurw59clDP7yWvxKlk="
+ version = "v0.22.0"
+ hash = "sha256-kUwLNFk9K/YuWmO5/u2IshrmhT2CCuk+mAShSlTTeZo="
[mod."gopkg.in/yaml.v3"]
version = "v3.0.1"
hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU="
diff --git a/kanban/banner/banner.go b/kanban/banner/banner.go
index 79c6e2ef08..cf6bd49f1c 100644
--- a/kanban/banner/banner.go
+++ b/kanban/banner/banner.go
@@ -3,13 +3,13 @@
package banner
// Version ...
-var Version = "v1.8.1"
+var Version = "v1.9.9"
// Copyright ...
-var Copyright = "© 2020 - 2024 FloatTech"
+var Copyright = "© 2020 - 2025 FloatTech"
// Banner ...
var Banner = "* OneBot + ZeroBot + Golang\n" +
- "* Version " + Version + " - 2024-05-30 16:47:27 +0900 JST\n" +
+ "* Version " + Version + " - 2025-09-10 10:40:39 +0800 CST\n" +
"* Copyright " + Copyright + ". All Rights Reserved.\n" +
"* Project: https://github.com/FloatTech/ZeroBot-Plugin"
diff --git a/kanban/gen/banner.go b/kanban/gen/banner.go
index a39a1c343c..3af4a9984c 100644
--- a/kanban/gen/banner.go
+++ b/kanban/gen/banner.go
@@ -27,7 +27,7 @@ var Banner = "* OneBot + ZeroBot + Golang\n" +
"* Project: https://github.com/FloatTech/ZeroBot-Plugin"
`
-const timeformat = `2006-01-02 15:04:05 +0900 JST`
+const timeformat = `2006-01-02 15:04:05 +0800 CST`
func main() {
f, err := os.Create("banner/banner.go")
diff --git a/main.go b/main.go
index 7bd6a05b7e..01b5511e27 100644
--- a/main.go
+++ b/main.go
@@ -34,8 +34,12 @@ import (
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chat" // 基础词库
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chatcount" // 聊天时长统计
+
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/sleepmanage" // 统计睡眠时间
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/airecord" // 群应用:AI声聊
+
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/atri" // ATRI词库
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/manager" // 群管
@@ -60,89 +64,96 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai" // ahsai tts
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aifalse" // 服务器监控
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/alipayvoice" // 支付宝到账语音
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/autowithdraw" // 触发者撤回时也自动撤回
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/baiduaudit" // 百度内容审核
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/base16384" // base16384加解密
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/base64gua" // base64卦加解密
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/baseamasiro" // base天城文加解密
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili" // b站相关
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bookreview" // 哀伤雪刃吧推书记录
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chess" // 国际象棋
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/choose" // 选择困难症帮手
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chouxianghua" // 说抽象话
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chrev" // 英文字符翻转
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/coser" // 三次元小姐姐
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/cpstory" // cp短打
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/dailynews" // 今日早报
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/dish" // 程序员做饭指南
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/drawlots" // 多功能抽签
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/driftbottle" // 漂流瓶
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/emojimix" // 合成emoji
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/event" // 好友申请群聊邀请事件处理
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/font" // 渲染任意文字到图片
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/fortune" // 运势
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/funny" // 笑话
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/genshin" // 原神抽卡
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/gif" // 制图
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/github" // 搜索GitHub仓库
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic" // 猜歌
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/hitokoto" // 一言
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/hs" // 炉石
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/hyaku" // 百人一首
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/inject" // 注入指令
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/jptingroom" // 日语听力学习材料
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/kfccrazythursday" // 疯狂星期四
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolimi" // 桑帛云 API
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/magicprompt" // magicprompt吟唱提示
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/mcfish" // 钓鱼模拟器
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moegoe" // 日韩 VITS 模型拟声
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyucalendar" // 摸鱼人日历
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativesetu" // 本地涩图
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nbnhhsh" // 拼音首字母缩写释义工具
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nihongo" // 日语语法学习
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/novel" // 铅笔小说网搜索
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nsfw" // nsfw图片识别
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nwife" // 本地老婆
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/poker" // 抽扑克
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/qzone" // qq空间表白墙
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/realcugan" // realcugan清晰术
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/robbery" // 打劫群友的ATRI币
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/score" // 分数
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/setutime" // 来份涩图
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/shadiao" // 沙雕app
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/shindan" // 测定
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/steam" // steam相关
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/tarot" // 抽塔罗牌
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/tiangou" // 舔狗日记
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/tracemoe" // 搜番
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/translation" // 翻译
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/vitsnyaru" // vits猫雷
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wallet" // 钱包
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wantquotes" // 据意查句
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/warframeapi" // warframeAPI插件
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenxinvilg" // 百度文心AI画图
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wife" // 抽老婆
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordcount" // 聊天热词
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ygo" // 游戏王相关插件
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/yujn" // 遇见API
+ _ "github.com/FloatTech/ZeroBot-Plugin/custom" // 自定义插件合集
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai" // ahsai tts
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aifalse" // 服务器监控
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiimage" // AI画图
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/alipayvoice" // 支付宝到账语音
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace" // AnimeTrace 动画/Galgame识别
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/autowithdraw" // 触发者撤回时也自动撤回
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/baiduaudit" // 百度内容审核
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/base16384" // base16384加解密
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/base64gua" // base64卦加解密
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/baseamasiro" // base天城文加解密
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili" // b站相关
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bookreview" // 哀伤雪刃吧推书记录
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chess" // 国际象棋
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/choose" // 选择困难症帮手
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chouxianghua" // 说抽象话
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chrev" // 英文字符翻转
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/coser" // 三次元小姐姐
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/cpstory" // cp短打
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/crypter" // 奇怪语言加解密
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/dailynews" // 今日早报
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/dish" // 程序员做饭指南
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/drawlots" // 多功能抽签
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/driftbottle" // 漂流瓶
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/emojimix" // 合成emoji
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/emozi" // 颜文字抽象转写
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/event" // 好友申请群聊邀请事件处理
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/font" // 渲染任意文字到图片
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/fortune" // 运势
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/funny" // 笑话
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/genshin" // 原神抽卡
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/gif" // 制图
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/github" // 搜索GitHub仓库
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic" // 猜歌
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/hitokoto" // 一言
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/hs" // 炉石
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/hyaku" // 百人一首
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/inject" // 注入指令
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/jptingroom" // 日语听力学习材料
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/kfccrazythursday" // 疯狂星期四
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolimi" // 桑帛云 API
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/magicprompt" // magicprompt吟唱提示
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/mcfish" // 钓鱼模拟器
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/minecraftobserver" // Minecraft服务器监控&订阅
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/movies" // 电影插件
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyucalendar" // 摸鱼人日历
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativesetu" // 本地涩图
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nbnhhsh" // 拼音首字母缩写释义工具
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nihongo" // 日语语法学习
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/niuniu" // 牛牛大作战
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/novel" // 铅笔小说网搜索
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nsfw" // nsfw图片识别
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nwife" // 本地老婆
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/poker" // 抽扑克
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/qzone" // qq空间表白墙
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/realcugan" // realcugan清晰术
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/robbery" // 打劫群友的ATRI币
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub" // RSSHub订阅姬
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/score" // 分数
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/setutime" // 来份涩图
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/shadiao" // 沙雕app
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/shindan" // 测定
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/steam" // steam相关
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/tarot" // 抽塔罗牌
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/tiangou" // 舔狗日记
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/tracemoe" // 搜番
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/translation" // 翻译
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wallet" // 钱包
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wantquotes" // 据意查句
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/warframeapi" // warframeAPI插件
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenxinvilg" // 百度文心AI画图
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wife" // 抽老婆
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordcount" // 聊天热词
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ygo" // 游戏王相关插件
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/yujn" // 遇见API
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wtf" // 鬼东西
@@ -164,9 +175,9 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse" // 骂人
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat" // AI聊天
- _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aireply" // 人工智能回复
+ _ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse" // 骂人
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/thesaurus" // 词典匹配回复
diff --git a/plugin/aichat/cfg.go b/plugin/aichat/cfg.go
new file mode 100644
index 0000000000..f92875320e
--- /dev/null
+++ b/plugin/aichat/cfg.go
@@ -0,0 +1,198 @@
+package aichat
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/chat"
+ "github.com/fumiama/deepinfra"
+ "github.com/fumiama/deepinfra/model"
+ "github.com/sirupsen/logrus"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+var (
+ cfg = newconfig()
+)
+
+type config struct {
+ ModelName string
+ Type int
+ MaxN uint
+ TopP float32
+ SystemP string
+ API string
+ Key string
+ Separator string
+ NoReplyAT bool
+ NoSystemP bool
+ NoRecord bool
+}
+
+func newconfig() config {
+ return config{
+ ModelName: model.ModelDeepDeek,
+ SystemP: chat.SystemPrompt,
+ API: deepinfra.OpenAIDeepInfra,
+ }
+}
+
+func (c *config) isvalid() bool {
+ return c.ModelName != "" && c.API != "" && c.Key != ""
+}
+
+func ensureconfig(ctx *zero.Ctx) bool {
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ return false
+ }
+ if !cfg.isvalid() {
+ err := c.GetExtra(&cfg)
+ if err != nil {
+ logrus.Warnln("ERROR: get extra err:", err)
+ }
+ if !cfg.isvalid() {
+ cfg = newconfig()
+ }
+ }
+ return true
+}
+
+func newextrasetstr(ptr *string) func(ctx *zero.Ctx) {
+ return func(ctx *zero.Ctx) {
+ args := strings.TrimSpace(ctx.State["args"].(string))
+ if args == "" {
+ ctx.SendChain(message.Text("ERROR: empty args"))
+ return
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ *ptr = args
+ err := c.SetExtra(&cfg)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set extra err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ }
+}
+
+func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) {
+ return func(ctx *zero.Ctx) {
+ args := ctx.State["regex_matched"].([]string)
+ isno := args[1] == "不"
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ *ptr = isno
+ err := c.SetExtra(&cfg)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set extra err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ }
+}
+
+func newextrasetuint(ptr *uint) func(ctx *zero.Ctx) {
+ return func(ctx *zero.Ctx) {
+ args := strings.TrimSpace(ctx.State["args"].(string))
+ if args == "" {
+ ctx.SendChain(message.Text("ERROR: empty args"))
+ return
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ n, err := strconv.ParseUint(args, 10, 64)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: parse args err: ", err))
+ return
+ }
+ *ptr = uint(n)
+ err = c.SetExtra(&cfg)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set extra err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ }
+}
+
+func newextrasetfloat32(ptr *float32) func(ctx *zero.Ctx) {
+ return func(ctx *zero.Ctx) {
+ args := strings.TrimSpace(ctx.State["args"].(string))
+ if args == "" {
+ ctx.SendChain(message.Text("ERROR: empty args"))
+ return
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ n, err := strconv.ParseFloat(args, 32)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: parse args err: ", err))
+ return
+ }
+ *ptr = float32(n)
+ err = c.SetExtra(&cfg)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set extra err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ }
+}
+
+func printConfig(rate int64, temperature int64, cfg config) string {
+ maxn := cfg.MaxN
+ if maxn == 0 {
+ maxn = 4096
+ }
+ topp := cfg.TopP
+ if topp == 0 {
+ topp = 0.9
+ }
+ var builder strings.Builder
+ builder.WriteString("当前AI聊天配置:\n")
+ builder.WriteString(fmt.Sprintf("• 模型名:%s\n", cfg.ModelName))
+ builder.WriteString(fmt.Sprintf("• 接口类型:%d(%s)\n", cfg.Type, apilist[cfg.Type]))
+ builder.WriteString(fmt.Sprintf("• 触发概率:%d%%\n", rate))
+ builder.WriteString(fmt.Sprintf("• 温度:%.2f\n", float32(temperature)/100))
+ builder.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn))
+ builder.WriteString(fmt.Sprintf("• TopP:%.1f\n", topp))
+ builder.WriteString(fmt.Sprintf("• 系统提示词:%s\n", cfg.SystemP))
+ builder.WriteString(fmt.Sprintf("• 接口地址:%s\n", cfg.API))
+ builder.WriteString(fmt.Sprintf("• 密钥:%s\n", maskKey(cfg.Key)))
+ builder.WriteString(fmt.Sprintf("• 分隔符:%s\n", cfg.Separator))
+ builder.WriteString(fmt.Sprintf("• 响应@:%s\n", yesNo(!cfg.NoReplyAT)))
+ builder.WriteString(fmt.Sprintf("• 支持系统提示词:%s\n", yesNo(!cfg.NoSystemP)))
+ builder.WriteString(fmt.Sprintf("• 以AI语音输出:%s\n", yesNo(!cfg.NoRecord)))
+ return builder.String()
+}
+
+func maskKey(key string) string {
+ if len(key) <= 4 {
+ return "****"
+ }
+ return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:]
+}
+
+func yesNo(b bool) string {
+ if b {
+ return "是"
+ }
+ return "否"
+}
diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go
new file mode 100644
index 0000000000..fc78af58c6
--- /dev/null
+++ b/plugin/aichat/main.go
@@ -0,0 +1,541 @@
+// Package aichat OpenAI聊天和群聊总结
+package aichat
+
+import (
+ "errors"
+ "math/rand"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/fumiama/deepinfra"
+ "github.com/fumiama/deepinfra/model"
+ "github.com/sirupsen/logrus"
+ "github.com/tidwall/gjson"
+
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ "github.com/FloatTech/AnimeAPI/airecord"
+ "github.com/FloatTech/floatbox/process"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/chat"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+)
+
+var (
+ // en data [8 temp] [8 rate] LSB
+ en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Extra: control.ExtraFromString("aichat"),
+ Brief: "OpenAI聊天",
+ Help: "- 设置AI聊天触发概率10\n" +
+ "- 设置AI聊天温度80\n" +
+ "- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" +
+ "- 设置AI聊天(不)支持系统提示词\n" +
+ "- 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
+ "- 设置AI聊天密钥xxx\n" +
+ "- 设置AI聊天模型名Qwen/Qwen3-8B\n" +
+ "- 查看AI聊天系统提示词\n" +
+ "- 重置AI聊天系统提示词\n" +
+ "- 设置AI聊天系统提示词xxx\n" +
+ "- 设置AI聊天分隔符(留空则清除)\n" +
+ "- 设置AI聊天(不)响应AT\n" +
+ "- 设置AI聊天最大长度4096\n" +
+ "- 设置AI聊天TopP 0.9\n" +
+ "- 设置AI聊天(不)以AI语音输出\n" +
+ "- 查看AI聊天配置\n" +
+ "- 重置AI聊天\n" +
+ "- 群聊总结 [消息数目]|群聊总结 1000\n" +
+ "- /gpt [内容] (使用大模型聊天)\n",
+
+ PrivateDataFolder: "aichat",
+ })
+)
+
+var (
+ apitypes = map[string]uint8{
+ "OpenAI": 0,
+ "OLLaMA": 1,
+ "GenAI": 2,
+ }
+ apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"}
+ limit = ctxext.NewLimiterManager(time.Second*30, 1)
+)
+
+// getModelParams 获取模型参数:温度(float32(temp)/100)、TopP和最大长度
+func getModelParams(temp int64) (temperature float32, topp float32, maxn uint) {
+ // 处理温度参数
+ if temp <= 0 {
+ temp = 70 // default setting
+ }
+ if temp > 100 {
+ temp = 100
+ }
+ temperature = float32(temp) / 100
+
+ // 处理TopP参数
+ topp = cfg.TopP
+ if topp == 0 {
+ topp = 0.9
+ }
+
+ // 处理最大长度参数
+ maxn = cfg.MaxN
+ if maxn == 0 {
+ maxn = 4096
+ }
+
+ return temperature, topp, maxn
+}
+
+func init() {
+ en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool {
+ return ctx.ExtractPlainText() != "" &&
+ (!cfg.NoReplyAT || (cfg.NoReplyAT && !ctx.Event.IsToMe))
+ }).SetBlock(false).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ return
+ }
+ rate := c.GetData(gid)
+ temp := (rate >> 8) & 0xff
+ rate &= 0xff
+ if !ctx.Event.IsToMe && rand.Intn(100) >= int(rate) {
+ return
+ }
+ if ctx.Event.IsToMe {
+ ctx.Block()
+ }
+ if cfg.Key == "" {
+ logrus.Warnln("ERROR: get extra err: empty key")
+ return
+ }
+
+ temperature, topp, maxn := getModelParams(temp)
+
+ x := deepinfra.NewAPI(cfg.API, cfg.Key)
+ var mod model.Protocol
+ switch cfg.Type {
+ case 0:
+ mod = model.NewOpenAI(
+ cfg.ModelName, cfg.Separator,
+ temperature, topp, maxn,
+ )
+ case 1:
+ mod = model.NewOLLaMA(
+ cfg.ModelName, cfg.Separator,
+ temperature, topp, maxn,
+ )
+ case 2:
+ mod = model.NewGenAI(
+ cfg.ModelName,
+ temperature, topp, maxn,
+ )
+ default:
+ logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
+ return
+ }
+
+ data, err := x.Request(chat.Ask(mod, gid, cfg.SystemP, cfg.NoSystemP))
+ if err != nil {
+ logrus.Warnln("[aichat] post err:", err)
+ return
+ }
+
+ txt := chat.Sanitize(strings.Trim(data, "\n "))
+ if len(txt) > 0 {
+ chat.Reply(gid, txt)
+ nick := zero.BotConfig.NickName[rand.Intn(len(zero.BotConfig.NickName))]
+ txt = strings.ReplaceAll(txt, "{name}", ctx.CardOrNickName(ctx.Event.UserID))
+ txt = strings.ReplaceAll(txt, "{me}", nick)
+ id := any(nil)
+ if ctx.Event.IsToMe {
+ id = ctx.Event.MessageID
+ }
+ for _, t := range strings.Split(txt, "{segment}") {
+ if t == "" {
+ continue
+ }
+ logrus.Infoln("[aichat] 回复内容:", t)
+ recCfg := airecord.GetConfig()
+ record := ""
+ if !cfg.NoRecord {
+ record = ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, t)
+ }
+ if record != "" {
+ ctx.SendChain(message.Record(record))
+ } else {
+ if id != nil {
+ id = ctx.SendChain(message.Reply(id), message.Text(t))
+ } else {
+ id = ctx.SendChain(message.Text(t))
+ }
+ }
+ process.SleepAbout1sTo2s()
+ }
+ }
+ })
+ en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ args := strings.TrimSpace(ctx.State["args"].(string))
+ if args == "" {
+ ctx.SendChain(message.Text("ERROR: empty args"))
+ return
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ r, err := strconv.Atoi(args)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: parse rate err: ", err))
+ return
+ }
+ if r > 100 {
+ r = 100
+ } else if r < 0 {
+ r = 0
+ }
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ val := c.GetData(gid) & (^0xff)
+ err = c.SetData(gid, val|int64(r&0xff))
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set data err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ })
+ en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ args := strings.TrimSpace(ctx.State["args"].(string))
+ if args == "" {
+ ctx.SendChain(message.Text("ERROR: empty args"))
+ return
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ r, err := strconv.Atoi(args)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: parse rate err: ", err))
+ return
+ }
+ if r > 100 {
+ r = 100
+ } else if r < 0 {
+ r = 0
+ }
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ val := c.GetData(gid) & (^0xff00)
+ err = c.SetData(gid, val|(int64(r&0xff)<<8))
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set data err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ })
+ en.OnPrefix("设置AI聊天接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ args := strings.TrimSpace(ctx.State["args"].(string))
+ if args == "" {
+ ctx.SendChain(message.Text("ERROR: empty args"))
+ return
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ typ, ok := apitypes[args]
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: 未知类型 ", args))
+ return
+ }
+ cfg.Type = int(typ)
+ err := c.SetExtra(&cfg)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set extra err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ })
+ en.OnPrefix("设置AI聊天接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetstr(&cfg.API))
+ en.OnPrefix("设置AI聊天密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetstr(&cfg.Key))
+ en.OnPrefix("设置AI聊天模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetstr(&cfg.ModelName))
+ en.OnPrefix("设置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetstr(&cfg.SystemP))
+ en.OnFullMatch("查看AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text(cfg.SystemP))
+ })
+ en.OnFullMatch("重置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ cfg.SystemP = chat.SystemPrompt
+ err := c.SetExtra(&cfg)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set extra err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功"))
+ })
+ en.OnPrefix("设置AI聊天分隔符", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetstr(&cfg.Separator))
+ en.OnRegex("^设置AI聊天(不)?响应AT$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetbool(&cfg.NoReplyAT))
+ en.OnRegex("^设置AI聊天(不)?支持系统提示词$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetbool(&cfg.NoSystemP))
+ en.OnPrefix("设置AI聊天最大长度", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetuint(&cfg.MaxN))
+ en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetfloat32(&cfg.TopP))
+ en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(newextrasetbool(&cfg.NoRecord))
+ en.OnFullMatch("查看AI聊天配置", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("ERROR: no such plugin"))
+ return
+ }
+ gid := ctx.Event.GroupID
+ rate := c.GetData(gid) & 0xff
+ temp := (c.GetData(gid) >> 8) & 0xff
+ if temp <= 0 {
+ temp = 70 // default setting
+ }
+ if temp > 100 {
+ temp = 100
+ }
+ ctx.SendChain(message.Text(printConfig(rate, temp, cfg)))
+ })
+ en.OnFullMatch("重置AI聊天", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ chat.Reset()
+ ctx.SendChain(message.Text("成功"))
+ })
+
+ // 添加群聊总结功能
+ en.OnRegex(`^群聊总结\s?(\d*)$`, ensureconfig, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(limit.LimitByGroup).Handle(func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text("少女思考中..."))
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ return
+ }
+ rate := c.GetData(gid)
+ temp := (rate >> 8) & 0xff
+ p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
+ if p > 1000 {
+ p = 1000
+ }
+ if p == 0 {
+ p = 200
+ }
+ group := ctx.GetGroupInfo(gid, false)
+ if group.MemberCount == 0 {
+ ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取总结"))
+ return
+ }
+
+ var messages []string
+
+ h := ctx.GetGroupMessageHistory(gid, 0, p, false)
+ h.Get("messages").ForEach(func(_, msgObj gjson.Result) bool {
+ nickname := msgObj.Get("sender.nickname").Str
+ text := strings.TrimSpace(message.ParseMessageFromString(msgObj.Get("raw_message").Str).ExtractPlainText())
+ if text != "" {
+ messages = append(messages, nickname+": "+text)
+ }
+ return true
+ })
+
+ if len(messages) == 0 {
+ ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息"))
+ return
+ }
+
+ // 构造总结请求提示
+ summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n" +
+ strings.Join(messages, "\n")
+
+ // 调用大模型API进行总结
+ summary, err := llmchat(summaryPrompt, temp)
+
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+
+ var b strings.Builder
+ b.WriteString("群 ")
+ b.WriteString(group.Name)
+ b.WriteByte('(')
+ b.WriteString(strconv.FormatInt(gid, 10))
+ b.WriteString(") 的 ")
+ b.WriteString(strconv.FormatInt(p, 10))
+ b.WriteString(" 条消息总结:\n\n")
+ b.WriteString(summary)
+
+ // 分割总结内容为多段(按1000字符长度切割)
+ summaryText := b.String()
+ msg := make(message.Message, 0)
+ for len(summaryText) > 0 {
+ if len(summaryText) <= 1000 {
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(summaryText)))
+ break
+ }
+
+ // 查找1000字符内的最后一个换行符,尽量在换行处分割
+ chunk := summaryText[:1000]
+ lastNewline := strings.LastIndex(chunk, "\n")
+ if lastNewline > 0 {
+ chunk = summaryText[:lastNewline+1]
+ }
+
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
+ summaryText = summaryText[len(chunk):]
+ }
+ if len(msg) > 0 {
+ ctx.Send(msg)
+ }
+ })
+
+ // 添加 /gpt 命令处理(同时支持回复消息和直接使用)
+ en.OnKeyword("/gpt", ensureconfig).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ if gid == 0 {
+ gid = -ctx.Event.UserID
+ }
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ return
+ }
+ rate := c.GetData(gid)
+ temp := (rate >> 8) & 0xff
+ text := ctx.MessageString()
+
+ var query string
+ var replyContent string
+
+ // 检查是否是回复消息 (使用MessageElement检查而不是CQ码)
+ for _, elem := range ctx.Event.Message {
+ if elem.Type == "reply" {
+ // 提取被回复的消息ID
+ replyIDStr := elem.Data["id"]
+ replyID, err := strconv.ParseInt(replyIDStr, 10, 64)
+ if err == nil {
+ // 获取被回复的消息内容
+ replyMsg := ctx.GetMessage(replyID)
+ if replyMsg.Elements != nil {
+ replyContent = replyMsg.Elements.ExtractPlainText()
+ }
+ }
+ break // 找到回复元素后退出循环
+ }
+ }
+
+ // 提取 /gpt 后面的内容
+ parts := strings.SplitN(text, "/gpt", 2)
+
+ var gContent string
+ if len(parts) > 1 {
+ gContent = strings.TrimSpace(parts[1])
+ }
+
+ // 组合内容:优先使用回复内容,如果同时有/gpt内容则拼接
+ switch {
+ case replyContent != "" && gContent != "":
+ query = replyContent + "\n" + gContent
+ case replyContent != "":
+ query = replyContent
+ case gContent != "":
+ query = gContent
+ default:
+ return
+ }
+
+ // 调用大模型API进行聊天
+ reply, err := llmchat(query, temp)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+
+ // 分割总结内容为多段(按1000字符长度切割)
+ msg := make(message.Message, 0)
+ for len(reply) > 0 {
+ if len(reply) <= 1000 {
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(reply)))
+ break
+ }
+
+ // 查找1000字符内的最后一个换行符,尽量在换行处分割
+ chunk := reply[:1000]
+ lastNewline := strings.LastIndex(chunk, "\n")
+ if lastNewline > 0 {
+ chunk = reply[:lastNewline+1]
+ }
+
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk)))
+ reply = reply[len(chunk):]
+ }
+ if len(msg) > 0 {
+ ctx.Send(msg)
+ }
+ })
+}
+
+// llmchat 调用大模型API包装
+func llmchat(prompt string, temp int64) (string, error) {
+ temperature, topp, maxn := getModelParams(temp) // 使用默认温度70
+
+ x := deepinfra.NewAPI(cfg.API, cfg.Key)
+ var mod model.Protocol
+ switch cfg.Type {
+ case 0:
+ mod = model.NewOpenAI(
+ cfg.ModelName, cfg.Separator,
+ temperature, topp, maxn,
+ )
+ case 1:
+ mod = model.NewOLLaMA(
+ cfg.ModelName, cfg.Separator,
+ temperature, topp, maxn,
+ )
+ case 2:
+ mod = model.NewGenAI(
+ cfg.ModelName,
+ temperature, topp, maxn,
+ )
+ default:
+ logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
+ return "", errors.New("不支持的AI类型")
+ }
+
+ data, err := x.Request(mod.User(prompt))
+ if err != nil {
+ return "", err
+ }
+
+ return strings.TrimSpace(data), nil
+}
diff --git a/plugin/aifalse/main.go b/plugin/aifalse/main.go
index a8addb24da..dc93720751 100644
--- a/plugin/aifalse/main.go
+++ b/plugin/aifalse/main.go
@@ -20,7 +20,6 @@ import (
"github.com/FloatTech/floatbox/web"
"github.com/FloatTech/gg"
"github.com/FloatTech/imgfactory"
- "github.com/FloatTech/rendercard"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
@@ -41,7 +40,7 @@ import (
)
const (
- backgroundURL = "https://iw233.cn/api.php?sort=mp"
+ backgroundURL = "https://pic.re/image"
referer = "https://weibo.com/"
)
@@ -184,7 +183,7 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
return
}
- data, err = web.GetData("http://q4.qlogo.cn/g?b=qq&nk=" + strconv.FormatInt(uid, 10) + "&s=640")
+ data, err = web.GetData("https://q4.qlogo.cn/g?b=qq&nk=" + strconv.FormatInt(uid, 10) + "&s=640")
if err != nil {
return
}
@@ -237,14 +236,17 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
defer wg.Done()
titlecard := gg.NewContext(cardw, titlecardh)
bwg.Wait()
- titlecard.DrawImage(blurback, -70, -70)
titlecard.DrawRoundedRectangle(1, 1, float64(titlecard.W()-1*2), float64(titlecardh-1*2), 16)
+ titlecard.ClipPreserve()
+ titlecard.DrawImage(blurback, -70, -70)
+ titlecard.SetColor(colorswitch(140))
+ titlecard.FillPreserve()
+
titlecard.SetLineWidth(3)
titlecard.SetColor(colorswitch(100))
- titlecard.StrokePreserve()
- titlecard.SetColor(colorswitch(140))
- titlecard.Fill()
+ titlecard.ResetClip()
+ titlecard.Stroke()
titlecard.DrawImage(avatarf.Circle(0).Image(), (titlecardh-avatarf.H())/2, (titlecardh-avatarf.H())/2)
@@ -288,20 +290,23 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
fw, _ = titlecard.MeasureString(bs)
titlecard.DrawStringAnchored(bs, float64(titlecardh)+fw/2, float64(titlecardh)*(0.5+0.75/2), 0.5, 0.5)
- titleimg = rendercard.Fillet(titlecard.Image(), 16)
+ titleimg = titlecard.Image()
}()
go func() {
defer wg.Done()
basiccard := gg.NewContext(cardw, basiccardh)
bwg.Wait()
- basiccard.DrawImage(blurback, -70, -70-titlecardh-40)
basiccard.DrawRoundedRectangle(1, 1, float64(basiccard.W()-1*2), float64(basiccardh-1*2), 16)
+ basiccard.ClipPreserve()
+ basiccard.DrawImage(blurback, -70, -70-titlecardh-40)
+ basiccard.SetColor(colorswitch(140))
+ basiccard.FillPreserve()
+
basiccard.SetLineWidth(3)
basiccard.SetColor(colorswitch(100))
- basiccard.StrokePreserve()
- basiccard.SetColor(colorswitch(140))
- basiccard.Fill()
+ basiccard.ResetClip()
+ basiccard.Stroke()
bslen := len(basicstate)
for i, v := range basicstate {
@@ -361,20 +366,23 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
basiccard.DrawStringAnchored(s, (float64(basiccard.W())-200*float64(bslen))/float64(bslen+1)+200/2+offset, 20+200+15+fw+15+basiccard.FontHeight()/2+float64(k)*textoffsety, 0.5, 0.5)
}
}
- basicimg = rendercard.Fillet(basiccard.Image(), 16)
+ basicimg = basiccard.Image()
}()
go func() {
defer wg.Done()
diskcard := gg.NewContext(cardw, diskcardh)
bwg.Wait()
- diskcard.DrawImage(blurback, -70, -70-titlecardh-40-basiccardh-40)
diskcard.DrawRoundedRectangle(1, 1, float64(diskcard.W()-1*2), float64(diskcardh-1*2), 16)
+ diskcard.ClipPreserve()
+ diskcard.DrawImage(blurback, -70, -70-titlecardh-40-basiccardh-40)
+ diskcard.SetColor(colorswitch(140))
+ diskcard.FillPreserve()
+
diskcard.SetLineWidth(3)
diskcard.SetColor(colorswitch(100))
- diskcard.StrokePreserve()
- diskcard.SetColor(colorswitch(140))
- diskcard.Fill()
+ diskcard.ResetClip()
+ diskcard.Stroke()
err = diskcard.ParseFontFace(fontbyte, 32)
if err != nil {
@@ -427,6 +435,7 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
}
diskcard.DrawRoundedRectangle(40, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+offset, float64(diskcard.W())-40-100, 50, 12)
+ diskcard.ClipPreserve()
diskcard.Fill()
colors := darkcolor
@@ -445,6 +454,7 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
diskcard.DrawRoundedRectangle(40, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+offset, (float64(diskcard.W())-40-100)*v.precent*0.01, 50, 12)
diskcard.Fill()
+ diskcard.ResetClip()
diskcard.SetColor(fontcolorswitch())
@@ -456,20 +466,23 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
diskcard.DrawStringAnchored(strconv.FormatFloat(v.precent, 'f', 0, 64)+"%", float64(diskcard.W())-100/2, 40+(float64(diskcardh-40*2)-50*float64(dslen))/float64(dslen-1)+50/2+offset, 0.5, 0.5)
}
}
- diskimg = rendercard.Fillet(diskcard.Image(), 16)
+ diskimg = diskcard.Image()
}()
go func() {
defer wg.Done()
moreinfocard := gg.NewContext(cardw, moreinfocardh)
bwg.Wait()
- moreinfocard.DrawImage(blurback, -70, -70-titlecardh-40-basiccardh-40-diskcardh-40)
moreinfocard.DrawRoundedRectangle(1, 1, float64(moreinfocard.W()-1*2), float64(moreinfocard.H()-1*2), 16)
+ moreinfocard.ClipPreserve()
+ moreinfocard.DrawImage(blurback, -70, -70-titlecardh-40-basiccardh-40-diskcardh-40)
+ moreinfocard.SetColor(colorswitch(140))
+ moreinfocard.FillPreserve()
+
moreinfocard.SetLineWidth(3)
moreinfocard.SetColor(colorswitch(100))
- moreinfocard.StrokePreserve()
- moreinfocard.SetColor(colorswitch(140))
- moreinfocard.Fill()
+ moreinfocard.ResetClip()
+ moreinfocard.Stroke()
err = moreinfocard.ParseFontFace(fontbyte, 32)
if err != nil {
@@ -488,7 +501,7 @@ func drawstatus(m *ctrl.Control[*zero.Ctx], uid int64, botname string, botrunsta
moreinfocard.DrawStringAnchored(v.name, 20+fw/2, 30+(float64(moreinfocardh-30*2)-moreinfocard.FontHeight()*float64(milen))/float64(milen-1)+moreinfocard.FontHeight()/2+offset, 0.5, 0.5)
moreinfocard.DrawStringAnchored(v.text[0], float64(moreinfocard.W())-20-fw1/2, 30+(float64(moreinfocardh-30*2)-moreinfocard.FontHeight()*float64(milen))/float64(milen-1)+moreinfocard.FontHeight()/2+offset, 0.5, 0.5)
}
- moreinfoimg = rendercard.Fillet(moreinfocard.Image(), 16)
+ moreinfoimg = moreinfocard.Image()
}()
go func() {
defer wg.Done()
@@ -668,7 +681,7 @@ func diskstate() (stateinfo []*status, err error) {
func moreinfo(m *ctrl.Control[*zero.Ctx]) (stateinfo []*status, err error) {
var mems runtime.MemStats
runtime.ReadMemStats(&mems)
- fmtmem := storagefmt(float64(mems.Sys))
+ fmtmem := storagefmt(float64(mems.Alloc))
hostinfo, err := host.Info()
if err != nil {
diff --git a/plugin/aiimage/config.go b/plugin/aiimage/config.go
new file mode 100644
index 0000000000..e48ec5c926
--- /dev/null
+++ b/plugin/aiimage/config.go
@@ -0,0 +1,56 @@
+// Package aiimage 提供AI画图功能配置
+package aiimage
+
+import (
+ "fmt"
+ "strings"
+ "sync"
+
+ sql "github.com/FloatTech/sqlite"
+)
+
+// storage 管理画图配置存储
+type storage struct {
+ sync.RWMutex
+ db sql.Sqlite
+}
+
+// imageConfig 存储AI画图配置信息
+type imageConfig struct {
+ ID int64 `db:"id"` // 主键ID
+ APIKey string `db:"apiKey"` // API密钥
+ APIURL string `db:"apiUrl"` // API地址
+ ModelName string `db:"modelName"` // 画图模型名称
+}
+
+// getConfig 获取当前配置
+func (sdb *storage) getConfig() imageConfig {
+ sdb.RLock()
+ defer sdb.RUnlock()
+ cfg := imageConfig{}
+ _ = sdb.db.Find("config", &cfg, "WHERE id = 1")
+ return cfg
+}
+
+// setConfig 设置AI画图配置
+func (sdb *storage) setConfig(apiKey, apiURL, modelName string) error {
+ sdb.Lock()
+ defer sdb.Unlock()
+ return sdb.db.Insert("config", &imageConfig{
+ ID: 1,
+ APIKey: apiKey,
+ APIURL: apiURL,
+ ModelName: modelName,
+ })
+}
+
+// PrintConfig 返回格式化后的配置信息
+func (sdb *storage) PrintConfig() string {
+ cfg := sdb.getConfig()
+ var builder strings.Builder
+ builder.WriteString("当前AI画图配置:\n")
+ builder.WriteString(fmt.Sprintf("• 密钥: %s\n", cfg.APIKey))
+ builder.WriteString(fmt.Sprintf("• 接口地址: %s\n", cfg.APIURL))
+ builder.WriteString(fmt.Sprintf("• 模型名: %s\n", cfg.ModelName))
+ return builder.String()
+}
diff --git a/plugin/aiimage/main.go b/plugin/aiimage/main.go
new file mode 100644
index 0000000000..519065e92a
--- /dev/null
+++ b/plugin/aiimage/main.go
@@ -0,0 +1,171 @@
+// Package aiimage AI画图
+package aiimage
+
+import (
+ "bytes"
+ "encoding/json"
+ "net/http"
+ "strings"
+ "time"
+
+ fcext "github.com/FloatTech/floatbox/ctxext"
+ "github.com/FloatTech/floatbox/web"
+ sql "github.com/FloatTech/sqlite"
+ "github.com/tidwall/gjson"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+)
+
+func init() {
+ var sdb = &storage{}
+
+ en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Extra: control.ExtraFromString("aiimage"),
+ Brief: "AI画图",
+ Help: "- 设置AI画图密钥xxx\n" +
+ "- 设置AI画图接口地址https://api.siliconflow.cn/v1/images/generations\n" +
+ "- 设置AI画图模型名Kwai-Kolors/Kolors\n" +
+ "- 查看AI画图配置\n" +
+ "- AI画图 [描述]",
+ PrivateDataFolder: "aiimage",
+ })
+
+ getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
+ sdb.db = sql.New(en.DataFolder() + "aiimage.db")
+ err := sdb.db.Open(time.Hour)
+ if err == nil {
+ // 创建配置表
+ err = sdb.db.Create("config", &imageConfig{})
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:", err))
+ return false
+ }
+ return true
+ }
+ ctx.SendChain(message.Text("[ERROR]:", err))
+ return false
+ })
+
+ en.OnPrefix("设置AI画图密钥", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ apiKey := strings.TrimSpace(ctx.State["args"].(string))
+ cfg := sdb.getConfig()
+ err := sdb.setConfig(apiKey, cfg.APIURL, cfg.ModelName)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: 设置API密钥失败: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功设置API密钥"))
+ })
+
+ en.OnPrefix("设置AI画图接口地址", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ apiURL := strings.TrimSpace(ctx.State["args"].(string))
+ cfg := sdb.getConfig()
+ err := sdb.setConfig(cfg.APIKey, apiURL, cfg.ModelName)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: 设置API地址失败: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功设置API地址"))
+ })
+
+ en.OnPrefix("设置AI画图模型名", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ modelName := strings.TrimSpace(ctx.State["args"].(string))
+ cfg := sdb.getConfig()
+ err := sdb.setConfig(cfg.APIKey, cfg.APIURL, modelName)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: 设置模型失败: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("成功设置模型: ", modelName))
+ })
+
+ en.OnFullMatch("查看AI画图配置", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text(sdb.PrintConfig()))
+ })
+
+ en.OnPrefix("AI画图", getdb).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text("少女思考中..."))
+ prompt := strings.TrimSpace(ctx.State["args"].(string))
+ if prompt == "" {
+ ctx.SendChain(message.Text("请输入图片描述"))
+ return
+ }
+
+ cfg := sdb.getConfig()
+ if cfg.APIKey == "" || cfg.APIURL == "" || cfg.ModelName == "" {
+ ctx.SendChain(message.Text("请先配置API密钥、地址和模型"))
+ return
+ }
+
+ // 准备请求数据
+ reqBytes, _ := json.Marshal(map[string]interface{}{
+ "model": cfg.ModelName,
+ "prompt": prompt,
+ "image_size": "1024x1024",
+ "batch_size": 4,
+ "num_inference_steps": 20,
+ "guidance_scale": 7.5,
+ })
+
+ // 发送API请求
+ data, err := web.RequestDataWithHeaders(
+ web.NewDefaultClient(),
+ cfg.APIURL,
+ "POST",
+ func(req *http.Request) error {
+ req.Header.Set("Authorization", "Bearer "+cfg.APIKey)
+ req.Header.Set("Content-Type", "application/json")
+ return nil
+ },
+ bytes.NewReader(reqBytes),
+ )
+ if err != nil {
+ ctx.SendChain(message.Text("API请求失败: ", err))
+ return
+ }
+
+ // 解析API响应
+ jsonData := gjson.ParseBytes(data)
+ images := jsonData.Get("images")
+ if !images.Exists() {
+ images = jsonData.Get("data")
+ if !images.Exists() {
+ ctx.SendChain(message.Text("未获取到图片URL"))
+ return
+ }
+ }
+
+ // 发送生成的图片和相关信息
+ inferenceTime := jsonData.Get("timings.inference").Float()
+ seed := jsonData.Get("seed").Int()
+ msg := make(message.Message, 0, 1)
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text("图片生成成功!\n",
+ "提示词: ", prompt, "\n",
+ "模型: ", cfg.ModelName, "\n",
+ "推理时间: ", inferenceTime, "秒\n",
+ "种子: ", seed)))
+
+ // 添加所有图片
+ images.ForEach(func(_, value gjson.Result) bool {
+ url := value.Get("url").String()
+ if url != "" {
+ msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Image(url)))
+ }
+ return true
+ })
+
+ if len(msg) > 0 {
+ ctx.Send(msg)
+ }
+ })
+}
diff --git a/plugin/airecord/record.go b/plugin/airecord/record.go
new file mode 100644
index 0000000000..908784d483
--- /dev/null
+++ b/plugin/airecord/record.go
@@ -0,0 +1,134 @@
+// Package airecord 群应用:AI声聊
+package airecord
+
+import (
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/tidwall/gjson"
+
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ "github.com/FloatTech/AnimeAPI/airecord"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+)
+
+func init() {
+ en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Extra: control.ExtraFromString("airecord"),
+ Brief: "群应用:AI声聊",
+ Help: "- 设置AI语音群号1048452984(tips:机器人任意所在群聊即可)\n" +
+ "- 设置AI语音模型\n" +
+ "- 查看AI语音配置\n" +
+ "- 发送AI语音xxx",
+ PrivateDataFolder: "airecord",
+ })
+
+ en.OnPrefix("设置AI语音群号", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ u := strings.TrimSpace(ctx.State["args"].(string))
+ num, err := strconv.ParseInt(u, 10, 64)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: parse gid err: ", err))
+ return
+ }
+ err = airecord.SetCustomGID(num)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set gid err: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("设置AI语音群号为", num))
+ })
+ en.OnFullMatch("设置AI语音模型", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession())
+ recv, cancel := next.Repeat()
+ defer cancel()
+ jsonData := ctx.GetAICharacters(0, 1)
+
+ // 转换为字符串数组
+ var names []string
+ // 初始化两个映射表
+ nameToID := make(map[string]string)
+ nameToURL := make(map[string]string)
+ characters := jsonData.Get("#.characters")
+
+ // 遍历每个角色对象
+ characters.ForEach(func(_, group gjson.Result) bool {
+ group.ForEach(func(_, character gjson.Result) bool {
+ // 提取当前角色的三个字段
+ name := character.Get("character_name").String()
+ names = append(names, name)
+ // 存入映射表(重复名称会覆盖,保留最后出现的条目)
+ nameToID[name] = character.Get("character_id").String()
+ nameToURL[name] = character.Get("preview_url").String()
+ return true // 继续遍历
+ })
+ return true // 继续遍历
+ })
+ var builder strings.Builder
+ // 写入开头文本
+ builder.WriteString("请选择语音模型序号:\n")
+
+ // 遍历names数组,拼接序号和名称
+ for i, v := range names {
+ // 将数字转换为字符串(不依赖fmt)
+ numStr := strconv.Itoa(i)
+ // 拼接格式:"序号. 名称\n"
+ builder.WriteString(numStr)
+ builder.WriteString(". ")
+ builder.WriteString(v)
+ builder.WriteString("\n")
+ }
+ // 获取最终字符串
+ ctx.SendChain(message.Text(builder.String()))
+ for {
+ select {
+ case <-time.After(time.Second * 120):
+ ctx.SendChain(message.Text("设置AI语音模型指令过期"))
+ return
+ case ct := <-recv:
+ msg := ct.Event.Message.ExtractPlainText()
+ num, err := strconv.Atoi(msg)
+ if err != nil {
+ ctx.SendChain(message.Text("请输入数字!"))
+ continue
+ }
+ if num < 0 || num >= len(names) {
+ ctx.SendChain(message.Text("序号非法!"))
+ continue
+ }
+ err = airecord.SetRecordModel(names[num], nameToID[names[num]])
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: set model err: ", err))
+ continue
+ }
+ ctx.SendChain(message.Text("已选择语音模型: ", names[num]))
+ ctx.SendChain(message.Record(nameToURL[names[num]]))
+ return
+ }
+ }
+ })
+ en.OnFullMatch("查看AI语音配置", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text(airecord.PrintRecordConfig()))
+ })
+ en.OnPrefix("发送AI语音", zero.UserOrGrpAdmin).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ u := strings.TrimSpace(ctx.State["args"].(string))
+ recCfg := airecord.GetConfig()
+ record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, u)
+ if record == "" {
+ id := ctx.SendGroupAIRecord(recCfg.ModelID, ctx.Event.GroupID, u)
+ if id == "" {
+ ctx.SendChain(message.Text("ERROR: get record err: empty record"))
+ return
+ }
+ }
+ ctx.SendChain(message.Record(record))
+ })
+}
diff --git a/plugin/aireply/ai_tts.go b/plugin/aireply/ai_tts.go
deleted file mode 100644
index dcf9a2a154..0000000000
--- a/plugin/aireply/ai_tts.go
+++ /dev/null
@@ -1,285 +0,0 @@
-package aireply
-
-import (
- "errors"
- "strings"
-
- "github.com/RomiChan/syncx"
- zero "github.com/wdvxdr1123/ZeroBot"
-
- "github.com/FloatTech/AnimeAPI/aireply"
- "github.com/FloatTech/AnimeAPI/tts"
- "github.com/FloatTech/AnimeAPI/tts/baidutts"
- "github.com/FloatTech/AnimeAPI/tts/genshin"
- "github.com/FloatTech/AnimeAPI/tts/lolimi"
- "github.com/FloatTech/AnimeAPI/tts/ttscn"
- ctrl "github.com/FloatTech/zbpctrl"
- "github.com/FloatTech/zbputils/control"
-)
-
-// 数据结构: [8 bits] [8 bits] [8 bits]
-// [具体人物] [tts模式] [回复模式]
-
-// defaultttsindexkey
-// 数据结构: [8 bits] [8 bits]
-// [具体人物] [tts模式]
-
-// [tts模式]: 0~200 genshin 201 baidu 202 ttscn 203 lolimi
-
-const (
- baiduttsindex = 201 + iota
- ttscnttsindex
- lolimittsindex
-)
-
-// extrattsname is the tts other than genshin vits
-var extrattsname = []string{"百度", "TTSCN", "桑帛云"}
-
-var ttscnspeakers = [...]string{
- "晓晓(女 - 年轻人)",
- "云扬(男 - 年轻人)",
- "晓辰(女 - 年轻人 - 抖音热门)",
- "晓涵(女 - 年轻人)",
- "晓墨(女 - 年轻人)",
- "晓秋(女 - 中年人)",
- "晓睿(女 - 老年)",
- "晓双(女 - 儿童)",
- "晓萱(女 - 年轻人)",
- "晓颜(女 - 年轻人)",
- "晓悠(女 - 儿童)",
- "云希(男 - 年轻人 - 抖音热门)",
- "云野(男 - 中年人)",
- "晓梦(女 - 年轻人)",
- "晓伊(女 - 儿童)",
- "晓甄(女 - 年轻人)",
-}
-
-const defaultttsindexkey = -2905
-
-var (
- 原 = newapikeystore("./data/tts/o.txt")
- ཆཏ = newapikeystore("./data/tts/c.txt")
- 百 = newapikeystore("./data/tts/b.txt")
-)
-
-type replymode []string
-
-func (r replymode) setReplyMode(ctx *zero.Ctx, name string) error {
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- var ok bool
- var index int64
- for i, s := range r {
- if s == name {
- ok = true
- index = int64(i)
- break
- }
- }
- if !ok {
- return errors.New("no such mode")
- }
- m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- if !ok {
- return errors.New("no such plugin")
- }
- return m.SetData(gid, (m.GetData(gid)&^0xff)|(index&0xff))
-}
-
-func (r replymode) getReplyMode(ctx *zero.Ctx) aireply.AIReply {
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- if ok {
- switch m.GetData(gid) & 0xff {
- case 0:
- return aireply.NewLolimiAi(aireply.JingfengURL, aireply.JingfengBotName)
- case 1:
- return aireply.NewLolimiAi(aireply.MomoURL, aireply.MomoBotName)
- case 2:
- return aireply.NewQYK(aireply.QYKURL, aireply.QYKBotName)
- case 3:
- return aireply.NewXiaoAi(aireply.XiaoAiURL, aireply.XiaoAiBotName)
- case 4:
- k := ཆཏ.k
- if k != "" {
- return aireply.NewChatGPT(aireply.ChatGPTURL, k)
- }
- return aireply.NewLolimiAi(aireply.JingfengURL, aireply.JingfengBotName)
- }
- }
- return aireply.NewLolimiAi(aireply.JingfengURL, aireply.JingfengBotName)
-}
-
-var ttsins = func() map[string]tts.TTS {
- m := make(map[string]tts.TTS, 512)
- for _, mode := range append(genshin.SoundList[:], extrattsname...) {
- m[mode] = nil
- }
- return m
-}()
-
-var ttsModes = func() []string {
- s := append(genshin.SoundList[:], make([]string, baiduttsindex-len(genshin.SoundList))...) // 0-200
- s = append(s, extrattsname...) // 201 202 ...
- return s
-}()
-
-type ttsmode syncx.Map[int64, int64]
-
-func list(list []string, num int) string {
- s := ""
- for i, value := range list {
- s += value
- if (i+1)%num == 0 {
- s += "\n"
- } else {
- s += " | "
- }
- }
- return s
-}
-
-func newttsmode() *ttsmode {
- t := &ttsmode{}
- m, ok := control.Lookup("tts")
- (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, 0)
- if ok {
- index := m.GetData(defaultttsindexkey)
- msk := index & 0xff
- if msk >= 0 && (msk < int64(len(ttsModes))) {
- (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, index)
- }
- }
- return t
-}
-
-func (t *ttsmode) setSoundMode(ctx *zero.Ctx, name string, character int) error {
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- _, ok := ttsins[name]
- if !ok {
- return errors.New("不支持设置语音人物" + name)
- }
- var index = int64(-1)
- for i, s := range genshin.SoundList {
- if s == name {
- index = int64(i + 1)
- break
- }
- }
- if index == -1 {
- switch name {
- case extrattsname[0]:
- index = baiduttsindex
- case extrattsname[1]:
- index = ttscnttsindex
- case extrattsname[2]:
- index = lolimittsindex
- default:
- return errors.New("语音人物" + name + "未注册index")
- }
- }
- m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- // 按原来的逻辑map存的是前16位
- storeIndex := (m.GetData(gid) &^ 0xffff00) | ((index << 8) & 0xff00) | ((int64(character) << 16) & 0xff0000)
- (*syncx.Map[int64, int64])(t).Store(gid, (storeIndex>>8)&0xffff)
- return m.SetData(gid, storeIndex)
-}
-
-func (t *ttsmode) getSoundMode(ctx *zero.Ctx) (tts.TTS, error) {
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- i, ok := (*syncx.Map[int64, int64])(t).Load(gid)
- if !ok {
- m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- i = m.GetData(gid) >> 8
- }
- m := i & 0xff
- if m <= 0 || (m >= int64(len(ttsModes))) {
- i, _ = (*syncx.Map[int64, int64])(t).Load(defaultttsindexkey)
- if i == 0 {
- i = ctx.State["manager"].(*ctrl.Control[*zero.Ctx]).GetData(defaultttsindexkey)
- (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, i)
- }
- m = i & 0xff
- }
- mode := ttsModes[m]
- ins, ok := ttsins[mode]
- if !ok || ins == nil {
- switch mode {
- case extrattsname[0]:
- id, sec, _ := strings.Cut(百.k, ",")
- ins = baidutts.NewBaiduTTS(int(i&0xff00)>>8, id, sec)
- case extrattsname[1]:
- var err error
- ins, err = ttscn.NewTTSCN("中文(普通话,简体)", ttscnspeakers[int(i&0xff00)>>8], ttscn.KBRates[0])
- if err != nil {
- return nil, err
- }
- case extrattsname[2]:
- ins = lolimi.NewLolimi(int(i&0xff00) >> 8)
- default: // 原神
- k := 原.k
- if k != "" {
- ins = genshin.NewGenshin(int(m-1), 原.k)
- ttsins[mode] = ins
- } else {
- ins = lolimi.NewLolimi(int(i&0xff00) >> 8)
- }
- }
- }
- return ins, nil
-}
-
-func (t *ttsmode) resetSoundMode(ctx *zero.Ctx) error {
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- // 只保留后面8位
- (*syncx.Map[int64, int64])(t).Delete(gid)
- return m.SetData(gid, (m.GetData(gid) & 0xff)) // 重置数据
-}
-
-func (t *ttsmode) setDefaultSoundMode(name string, character int) error {
- _, ok := ttsins[name]
- if !ok {
- return errors.New("不支持设置语音人物" + name)
- }
- index := int64(-1)
- for i, s := range genshin.SoundList {
- if s == name {
- index = int64(i + 1)
- break
- }
- }
- if index == -1 {
- switch name {
- case extrattsname[0]:
- index = baiduttsindex
- case extrattsname[1]:
- index = ttscnttsindex
- case extrattsname[2]:
- index = lolimittsindex
- default:
- return errors.New("语音人物" + name + "未注册index")
- }
- }
- m, ok := control.Lookup("tts")
- if !ok {
- return errors.New("[tts] service not found")
- }
- storeIndex := (index & 0xff) | ((int64(character) << 8) & 0xff00)
- (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, storeIndex)
- return m.SetData(defaultttsindexkey, storeIndex)
-}
diff --git a/plugin/aireply/main.go b/plugin/aireply/main.go
deleted file mode 100644
index e7e3158ea2..0000000000
--- a/plugin/aireply/main.go
+++ /dev/null
@@ -1,227 +0,0 @@
-// Package aireply AI 回复
-package aireply
-
-import (
- "os"
- "regexp"
- "strconv"
- "strings"
- "time"
-
- "github.com/FloatTech/AnimeAPI/tts/genshin"
- ctrl "github.com/FloatTech/zbpctrl"
- "github.com/FloatTech/zbputils/control"
- "github.com/FloatTech/zbputils/ctxext"
- "github.com/sirupsen/logrus"
- zero "github.com/wdvxdr1123/ZeroBot"
- "github.com/wdvxdr1123/ZeroBot/message"
-)
-
-var replmd = replymode([]string{"婧枫", "沫沫", "青云客", "小爱", "ChatGPT"})
-
-var ttsmd = newttsmode()
-
-func init() { // 插件主体
- ent := control.Register("tts", &ctrl.Options[*zero.Ctx]{
- DisableOnDefault: true,
- Brief: "人工智能语音回复",
- Help: "- @Bot 任意文本(任意一句话回复)\n" +
- "- 设置语音模式[原神人物/百度/TTSCN/桑帛云] 数字(百度/TTSCN说话人/桑帛云)\n" +
- "- 设置默认语音模式[原神人物/百度/TTSCN/桑帛云] 数字(百度/TTSCN说话人/桑帛云)\n" +
- "- 恢复成默认语音模式\n" +
- "- 设置语音回复模式[沫沫|婧枫|青云客|小爱|ChatGPT]\n" +
- "- 设置原神语音 api key xxxxxx (key请加开发群获得)\n" +
- "- 设置百度语音 api id xxxxxx secret xxxxxx (请自行获得)\n" +
- "当前适用的原神人物含有以下: \n" + list(genshin.SoundList[:], 5) +
- "\n当前适用的TTSCN人物含有以下(以数字顺序代表): \n" + list(ttscnspeakers[:], 5),
- PrivateDataFolder: "tts",
- })
-
- enr := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
- DisableOnDefault: false,
- Brief: "人工智能回复",
- Help: "- @Bot 任意文本(任意一句话回复)\n- 设置文字回复模式[婧枫|沫沫|青云客|小爱|ChatGPT]\n- 设置 ChatGPT api key xxx",
- PrivateDataFolder: "aireply",
- })
-
- enr.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser).
- Handle(func(ctx *zero.Ctx) {
- aireply := replmd.getReplyMode(ctx)
- reply := message.ParseMessageFromString(aireply.Talk(ctx.Event.UserID, ctx.ExtractPlainText(), zero.BotConfig.NickName[0]))
- // 回复
- time.Sleep(time.Second * 1)
- reply = append(reply, message.Reply(ctx.Event.MessageID))
- ctx.Send(reply)
- })
- setReplyMode := func(ctx *zero.Ctx) {
- param := ctx.State["args"].(string)
- err := replmd.setReplyMode(ctx, param)
- if err != nil {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
- return
- }
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("成功"))
- }
- enr.OnPrefix("设置文字回复模式", zero.AdminPermission).SetBlock(true).Handle(setReplyMode)
- enr.OnRegex(`^设置\s*ChatGPT\s*api\s*key\s*(.*)$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- err := ཆཏ.set(ctx.State["regex_matched"].([]string)[1])
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(message.Text("设置成功"))
- })
-
- endpre := regexp.MustCompile(`\pP$`)
- ttscachedir := ent.DataFolder() + "cache/"
- _ = os.RemoveAll(ttscachedir)
- err := os.MkdirAll(ttscachedir, 0755)
- if err != nil {
- panic(err)
- }
- ent.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser).
- Handle(func(ctx *zero.Ctx) {
- msg := ctx.ExtractPlainText()
- // 获取回复模式
- r := replmd.getReplyMode(ctx)
- // 获取回复的文本
- reply := message.ParseMessageFromString(r.TalkPlain(ctx.Event.UserID, msg, zero.BotConfig.NickName[0]))
- // 过滤掉文字消息
- filterMsg := make([]message.MessageSegment, 0, len(reply))
- sb := strings.Builder{}
- for _, v := range reply {
- if v.Type != "text" {
- filterMsg = append(filterMsg, v)
- } else {
- sb.WriteString(v.Data["text"])
- }
- }
- // 纯文本
- plainReply := sb.String()
- plainReply = strings.ReplaceAll(plainReply, "\n", "")
- // 获取语音
- speaker, err := ttsmd.getSoundMode(ctx)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- rec, err := speaker.Speak(ctx.Event.UserID, func() string {
- if !endpre.MatchString(plainReply) {
- return plainReply + "。"
- }
- return plainReply
- })
- // 发送前面的图片
- if len(filterMsg) != 0 {
- filterMsg = append(filterMsg, message.Reply(ctx.Event.MessageID))
- ctx.Send(filterMsg)
- }
- if err != nil {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(plainReply))
- return
- }
- // 发送语音
- if id := ctx.SendChain(message.Record(rec)); id.ID() == 0 {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(plainReply))
- }
- })
- ent.OnPrefix("设置语音回复模式", zero.AdminPermission).SetBlock(true).Handle(setReplyMode)
- ent.OnRegex(`^设置语音模式\s*([\S\D]*)\s*(\d*)$`, zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- param := ctx.State["regex_matched"].([]string)[1]
- num := ctx.State["regex_matched"].([]string)[2]
- n := 0
- var err error
- if num != "" {
- n, err = strconv.Atoi(num)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- }
- // 保存设置
- logrus.Debugln("[tts] t.setSoundMode( ctx", param, n, ")")
- err = ttsmd.setSoundMode(ctx, param, n)
- if err != nil {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
- return
- }
- banner := genshin.TestRecord[param]
- if banner == "" {
- banner = genshin.TestRecord["默认"]
- }
- logrus.Debugln("[tts] banner:", banner, "get sound mode...")
- // 设置验证
- speaker, err := ttsmd.getSoundMode(ctx)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- logrus.Debugln("[tts] got sound mode, speaking...")
- rec, err := speaker.Speak(ctx.Event.UserID, func() string { return banner })
- if err != nil {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("无法发送测试语音,请重试。"))
- return
- }
- logrus.Debugln("[tts] sending...")
- if id := ctx.SendChain(message.Record(rec).Add("cache", 0)); id.ID() == 0 {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("无法发送测试语音,请重试。"))
- return
- }
- time.Sleep(time.Second * 2)
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,当前为", speaker))
- })
-
- ent.OnRegex(`^设置默认语音模式\s*([\S\D]*)\s+(\d*)$`, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- param := ctx.State["regex_matched"].([]string)[1]
- num := ctx.State["regex_matched"].([]string)[2]
- n := 0
- var err error
- if num != "" {
- n, err = strconv.Atoi(num)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- }
- // 保存设置
- err = ttsmd.setDefaultSoundMode(param, n)
- if err != nil {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
- return
- }
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功"))
- })
-
- ent.OnFullMatch("恢复成默认语音模式", zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- err := ttsmd.resetSoundMode(ctx)
- if err != nil {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
- return
- }
- // 设置验证
- speaker, err := ttsmd.getSoundMode(ctx)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,当前为", speaker))
- })
-
- ent.OnRegex(`^设置原神语音\s*api\s*key\s*([0-9a-zA-Z-_]{54}==)$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- err := 原.set(ctx.State["regex_matched"].([]string)[1])
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(message.Text("设置成功"))
- })
-
- ent.OnRegex(`^设置百度语音\s*api\s*id\s*(.*)\s*secret\s*(.*)\s*$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- err := 百.set(ctx.State["regex_matched"].([]string)[1] + "," + ctx.State["regex_matched"].([]string)[2])
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(message.Text("设置成功"))
- })
-}
diff --git a/plugin/aireply/model.go b/plugin/aireply/model.go
deleted file mode 100644
index 1e79d5e3ff..0000000000
--- a/plugin/aireply/model.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package aireply
-
-import (
- "os"
-
- "github.com/FloatTech/floatbox/binary"
- "github.com/FloatTech/floatbox/file"
-)
-
-type apikeystore struct {
- k string
- p string
-}
-
-func newapikeystore(p string) (s apikeystore) {
- s.p = p
- if file.IsExist(p) {
- data, err := os.ReadFile(p)
- if err == nil {
- s.k = binary.BytesToString(data)
- }
- }
- return
-}
-
-func (s *apikeystore) set(k string) error {
- s.k = k
- return os.WriteFile(s.p, binary.StringToBytes(k), 0644)
-}
diff --git a/plugin/animetrace/main.go b/plugin/animetrace/main.go
new file mode 100644
index 0000000000..e738d18f23
--- /dev/null
+++ b/plugin/animetrace/main.go
@@ -0,0 +1,145 @@
+// Package animetrace AnimeTrace 动画/Galgame识别
+package animetrace
+
+import (
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "image"
+ "image/jpeg"
+ "mime/multipart"
+ "strings"
+
+ "github.com/FloatTech/floatbox/web"
+ "github.com/FloatTech/imgfactory"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ "github.com/disintegration/imaging"
+ "github.com/tidwall/gjson"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+func init() {
+ engine := control.Register("animetrace", &ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "AnimeTrace 动画/Galgame识别插件",
+ Help: "- Gal识图\n- 动漫识图\n- 动漫识图 2\n- 动漫识图 [模型名]\n- Gal识图 [模型名]",
+ })
+
+ engine.OnPrefix("gal识图", zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ args := ctx.State["args"].(string)
+ var model string
+ switch strings.TrimSpace(args) {
+ case "":
+ model = "full_game_model_kira" // 默认使用的模型
+ default:
+ model = args // 自定义设置模型
+ }
+ processImageRecognition(ctx, model)
+ })
+
+ engine.OnPrefix("动漫识图", zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ args := ctx.State["args"].(string)
+ var model string
+ switch strings.TrimSpace(args) {
+ case "":
+ model = "anime_model_lovelive"
+ case "2":
+ model = "pre_stable"
+ default:
+ model = args
+ }
+ processImageRecognition(ctx, model)
+ })
+}
+
+// 处理图片识别
+func processImageRecognition(ctx *zero.Ctx, model string) {
+ urls := ctx.State["image_url"].([]string)
+ if len(urls) == 0 {
+ return
+ }
+ imageData, err := imgfactory.Load(urls[0])
+ if err != nil {
+ ctx.Send(message.Text("下载图片失败: ", err))
+ return
+ }
+ // ctx.Send(message.Text(model))
+ respBody, err := createAndSendMultipartRequest("https://api.animetrace.com/v1/search", imageData, map[string]string{
+ "is_multi": "0",
+ "model": model,
+ "ai_detect": "0",
+ })
+ if err != nil {
+ ctx.Send(message.Text("识别请求失败: ", err))
+ return
+ }
+ code := gjson.Get(string(respBody), "code").Int()
+ if code != 0 {
+ ctx.Send(message.Text("错误: ", gjson.Get(string(respBody), "zh_message").String()))
+ return
+ }
+ dataArray := gjson.Get(string(respBody), "data").Array()
+ if len(dataArray) == 0 {
+ ctx.Send(message.Text("未识别到任何角色"))
+ return
+ }
+ var sk message.Message
+ sk = append(sk, ctxext.FakeSenderForwardNode(ctx, message.Text("共识别到 ", len(dataArray), " 个角色,可能是以下来源")))
+ for _, value := range dataArray {
+ boxArray := value.Get("box").Array()
+ imgWidth, imgHeight := imageData.Bounds().Dx(), imageData.Bounds().Dy() // 你可以从 `imageData.Bounds()` 获取
+ box := []int{
+ int(boxArray[0].Float() * float64(imgWidth)),
+ int(boxArray[1].Float() * float64(imgHeight)),
+ int(boxArray[2].Float() * float64(imgWidth)),
+ int(boxArray[3].Float() * float64(imgHeight)),
+ }
+ croppedImg := imaging.Crop(imageData, image.Rect(box[0], box[1], box[2], box[3]))
+ var buf bytes.Buffer
+ if err := imaging.Encode(&buf, croppedImg, imaging.JPEG, imaging.JPEGQuality(80)); err != nil {
+ ctx.Send(message.Text("图片编码失败: ", err))
+ continue
+ }
+
+ base64Str := base64.StdEncoding.EncodeToString(buf.Bytes())
+ var sb strings.Builder
+ value.Get("character").ForEach(func(_, character gjson.Result) bool {
+ sb.WriteString(fmt.Sprintf("《%s》的角色 %s\n", character.Get("work").String(), character.Get("character").String()))
+ return true
+ })
+ sk = append(sk, ctxext.FakeSenderForwardNode(ctx, message.Image("base64://"+base64Str), message.Text(sb.String())))
+ }
+ ctx.SendGroupForwardMessage(ctx.Event.GroupID, sk)
+}
+
+// 发送图片识别请求
+func createAndSendMultipartRequest(url string, img image.Image, formFields map[string]string) ([]byte, error) {
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+
+ // 直接编码图片
+ part, err := writer.CreateFormFile("file", "image.jpg")
+ if err != nil {
+ return nil, errors.New("创建文件字段失败: " + err.Error())
+ }
+ if err := jpeg.Encode(part, img, &jpeg.Options{Quality: 80}); err != nil {
+ return nil, errors.New("图片编码失败: " + err.Error())
+ }
+
+ // 写入其他字段
+ for key, value := range formFields {
+ if err := writer.WriteField(key, value); err != nil {
+ return nil, errors.New("写入表单字段失败 (" + key + "): " + err.Error())
+ }
+ }
+
+ if err := writer.Close(); err != nil {
+ return nil, errors.New("关闭 multipart writer 失败: " + err.Error())
+ }
+
+ return web.PostData(url, writer.FormDataContentType(), body)
+}
diff --git a/plugin/antiabuse/anti.go b/plugin/antiabuse/anti.go
index b4ba459f68..dfe64fbdff 100644
--- a/plugin/antiabuse/anti.go
+++ b/plugin/antiabuse/anti.go
@@ -17,7 +17,12 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
)
-const bandur time.Duration = time.Minute * 10
+const (
+ bandur time.Duration = time.Minute * 2
+ add = "添加违禁词"
+ del = "删除违禁词"
+ list = "查看违禁词"
+)
var (
managers *ctrl.Manager[*zero.Ctx] // managers lazy load
@@ -41,7 +46,7 @@ func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "违禁词检测",
- Help: "- /[添加|删除|查看]违禁词",
+ Help: "- [添加|删除|查看]违禁词",
PrivateDataFolder: "anti_abuse",
})
@@ -56,10 +61,14 @@ func init() {
return true
})
- engine.OnMessage(onceRule, zero.OnlyGroup, func(ctx *zero.Ctx) bool {
- if !ctx.Event.IsToMe {
- return true
+ notAntiabuse := func(ctx *zero.Ctx) bool {
+ if zero.PrefixRule(add)(ctx) || zero.PrefixRule(del)(ctx) || zero.PrefixRule(list)(ctx) {
+ return false
}
+ return true
+ }
+
+ engine.OnMessage(onceRule, notAntiabuse, zero.OnlyGroup, func(ctx *zero.Ctx) bool {
uid := ctx.Event.UserID
gid := ctx.Event.GroupID
msg := strings.ReplaceAll(ctx.MessageString(), "\n", "")
@@ -70,7 +79,8 @@ func init() {
if err := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]).Manager.DoBlock(uid); err == nil {
t := time.Now().Unix()
cache.Set(uid, struct{}{})
- ctx.SetThisGroupBan(uid, int64(bandur.Minutes()))
+ ctx.SetThisGroupBan(uid, int64(bandur.Seconds()))
+ ctx.DeleteMessage(ctx.Event.MessageID)
ctx.SendChain(message.Text("检测到违禁词, 已封禁/屏蔽", bandur))
db.Lock()
defer db.Unlock()
@@ -92,9 +102,9 @@ func init() {
return true
})
- engine.OnCommand("添加违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle(
+ engine.OnPrefix(add, zero.OnlyGroup, zero.AdminPermission, onceRule).SetBlock(true).Handle(
func(ctx *zero.Ctx) {
- args := ctx.State["args"].(string)
+ args := strings.TrimSpace(ctx.State["args"].(string))
if err := db.insertWord(ctx.Event.GroupID, args); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
} else {
@@ -102,9 +112,9 @@ func init() {
}
})
- engine.OnCommand("删除违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle(
+ engine.OnPrefix(del, zero.OnlyGroup, zero.AdminPermission, onceRule).SetBlock(true).Handle(
func(ctx *zero.Ctx) {
- args := ctx.State["args"].(string)
+ args := strings.TrimSpace(ctx.State["args"].(string))
if err := db.deleteWord(ctx.Event.GroupID, args); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
} else {
@@ -112,7 +122,7 @@ func init() {
}
})
- engine.OnCommand("查看违禁词", zero.OnlyGroup, onceRule).Handle(
+ engine.OnPrefix(list, zero.OnlyGroup, onceRule).SetBlock(true).Handle(
func(ctx *zero.Ctx) {
b, err := text.RenderToBase64(db.listWords(ctx.Event.GroupID), text.FontFile, 400, 20)
if err != nil {
diff --git a/plugin/antiabuse/db.go b/plugin/antiabuse/db.go
index 8f48577884..d18201ce82 100644
--- a/plugin/antiabuse/db.go
+++ b/plugin/antiabuse/db.go
@@ -30,7 +30,7 @@ var (
)
func newantidb(path string) (*antidb, error) {
- db := &antidb{Sqlite: sqlite.Sqlite{DBPath: path}}
+ db := &antidb{Sqlite: sqlite.New(path)}
err := db.Open(bandur)
if err != nil {
return nil, err
@@ -46,7 +46,7 @@ func newantidb(path string) (*antidb, error) {
cache.Touch(nilbt.ID, -time.Since(t))
return nil
})
- _ = db.Del("__bantime__", "WHERE time<="+strconv.FormatInt(time.Now().Add(time.Minute-bandur).Unix(), 10))
+ _ = db.Del("__bantime__", "WHERE time <= ?", strconv.FormatInt(time.Now().Add(time.Minute-bandur).Unix(), 10))
return db, nil
}
diff --git a/plugin/atri/atri.go b/plugin/atri/atri.go
index 9367315889..571c4a8f09 100644
--- a/plugin/atri/atri.go
+++ b/plugin/atri/atri.go
@@ -19,7 +19,7 @@ import (
type datagetter func(string, bool) ([]byte, error)
-func (dgtr datagetter) randImage(file ...string) message.MessageSegment {
+func (dgtr datagetter) randImage(file ...string) message.Segment {
data, err := dgtr(file[rand.Intn(len(file))], true)
if err != nil {
return message.Text("ERROR: ", err)
@@ -27,7 +27,7 @@ func (dgtr datagetter) randImage(file ...string) message.MessageSegment {
return message.ImageBytes(data)
}
-func (dgtr datagetter) randRecord(file ...string) message.MessageSegment {
+func (dgtr datagetter) randRecord(file ...string) message.Segment {
data, err := dgtr(file[rand.Intn(len(file))], true)
if err != nil {
return message.Text("ERROR: ", err)
@@ -35,7 +35,7 @@ func (dgtr datagetter) randRecord(file ...string) message.MessageSegment {
return message.Record("base64://" + base64.StdEncoding.EncodeToString(data))
}
-func randText(text ...string) message.MessageSegment {
+func randText(text ...string) message.Segment {
return message.Text(text[rand.Intn(len(text))])
}
diff --git a/plugin/baiduaudit/model.go b/plugin/baiduaudit/model.go
index e5dd8caf77..15b6ff05c9 100644
--- a/plugin/baiduaudit/model.go
+++ b/plugin/baiduaudit/model.go
@@ -224,7 +224,7 @@ func (g *group) reply(bdres *baiduRes) message.Message {
g.mu.Lock()
defer g.mu.Unlock()
// 建立消息段
- msgs := make([]message.MessageSegment, 0, 8)
+ msgs := make([]message.Segment, 0, 8)
// 生成简略审核结果回复
msgs = append(msgs, message.Text(bdres.Conclusion, "\n"))
// 查看是否开启详细审核内容提示, 并确定审核内容值为疑似, 或者不合规
diff --git a/plugin/bilibili/bilibili_parse.go b/plugin/bilibili/bilibili_parse.go
index ad628edafd..81dd03bdaa 100644
--- a/plugin/bilibili/bilibili_parse.go
+++ b/plugin/bilibili/bilibili_parse.go
@@ -2,25 +2,33 @@
package bilibili
import (
+ "bytes"
"encoding/json"
"fmt"
"net/http"
+ "os"
+ "os/exec"
"regexp"
"strings"
"time"
bz "github.com/FloatTech/AnimeAPI/bilibili"
+ "github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
+ "github.com/pkg/errors"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
- enableHex = 0x10
- unableHex = 0x7fffffff_fffffffd
+ enableVideoSummary = int64(0x10)
+ disableVideoSummary = ^enableVideoSummary
+ enableVideoDownload = int64(0x20)
+ disableVideoDownload = ^enableVideoDownload
+ bilibiliparseReferer = "https://www.bilibili.com"
)
var (
@@ -33,6 +41,7 @@ var (
searchDynamicRe = regexp.MustCompile(searchDynamic)
searchArticleRe = regexp.MustCompile(searchArticle)
searchLiveRoomRe = regexp.MustCompile(searchLiveRoom)
+ cachePath string
)
// 插件主体
@@ -42,6 +51,9 @@ func init() {
Brief: "b站链接解析",
Help: "例:- t.bilibili.com/642277677329285174\n- bilibili.com/read/cv17134450\n- bilibili.com/video/BV13B4y1x7pS\n- live.bilibili.com/22603245 ",
})
+ cachePath = en.DataFolder() + "cache/"
+ _ = os.RemoveAll(cachePath)
+ _ = os.MkdirAll(cachePath, 0755)
en.OnRegex(`((b23|acg).tv|bili2233.cn)\\?/[0-9a-zA-Z]+`).SetBlock(true).Limit(limit.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
u := ctx.State["regex_matched"].([]string)[0]
@@ -82,9 +94,9 @@ func init() {
data := c.GetData(ctx.Event.GroupID)
switch option {
case "开启", "打开", "启用":
- data |= enableHex
+ data |= enableVideoSummary
case "关闭", "关掉", "禁用":
- data &= unableHex
+ data &= disableVideoSummary
default:
return
}
@@ -95,6 +107,35 @@ func init() {
}
ctx.SendChain(message.Text("已", option, "视频总结"))
})
+ en.OnRegex(`^(开启|打开|启用|关闭|关掉|禁用)视频上传$`, zero.AdminPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ if gid <= 0 {
+ // 个人用户设为负数
+ gid = -ctx.Event.UserID
+ }
+ option := ctx.State["regex_matched"].([]string)[1]
+ c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
+ if !ok {
+ ctx.SendChain(message.Text("找不到服务!"))
+ return
+ }
+ data := c.GetData(ctx.Event.GroupID)
+ switch option {
+ case "开启", "打开", "启用":
+ data |= enableVideoDownload
+ case "关闭", "关掉", "禁用":
+ data &= disableVideoDownload
+ default:
+ return
+ }
+ err := c.SetData(gid, data)
+ if err != nil {
+ ctx.SendChain(message.Text("出错啦: ", err))
+ return
+ }
+ ctx.SendChain(message.Text("已", option, "视频上传"))
+ })
en.OnRegex(searchVideo).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleVideo)
en.OnRegex(searchDynamic).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleDynamic)
en.OnRegex(searchArticle).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleArticle)
@@ -117,7 +158,7 @@ func handleVideo(ctx *zero.Ctx) {
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
- if ok && c.GetData(ctx.Event.GroupID)&enableHex == enableHex {
+ if ok && c.GetData(ctx.Event.GroupID)&enableVideoSummary == enableVideoSummary {
summaryMsg, err := getVideoSummary(cfg, card)
if err != nil {
msg = append(msg, message.Text("ERROR: ", err))
@@ -126,6 +167,14 @@ func handleVideo(ctx *zero.Ctx) {
}
}
ctx.SendChain(msg...)
+ if ok && c.GetData(ctx.Event.GroupID)&enableVideoDownload == enableVideoDownload {
+ downLoadMsg, err := getVideoDownload(cfg, card, cachePath)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.SendChain(downLoadMsg...)
+ }
}
func handleDynamic(ctx *zero.Ctx) {
@@ -147,7 +196,12 @@ func handleArticle(ctx *zero.Ctx) {
}
func handleLive(ctx *zero.Ctx) {
- card, err := bz.GetLiveRoomInfo(ctx.State["regex_matched"].([]string)[1])
+ cookie, err := cfg.Load()
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ card, err := bz.GetLiveRoomInfo(ctx.State["regex_matched"].([]string)[1], cookie)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
@@ -156,7 +210,7 @@ func handleLive(ctx *zero.Ctx) {
}
// getVideoSummary AI视频总结
-func getVideoSummary(cookiecfg *bz.CookieConfig, card bz.Card) (msg []message.MessageSegment, err error) {
+func getVideoSummary(cookiecfg *bz.CookieConfig, card bz.Card) (msg []message.Segment, err error) {
var (
data []byte
videoSummary bz.VideoSummary
@@ -177,7 +231,7 @@ func getVideoSummary(cookiecfg *bz.CookieConfig, card bz.Card) (msg []message.Me
return
}
err = json.Unmarshal(data, &videoSummary)
- msg = make([]message.MessageSegment, 0, 16)
+ msg = make([]message.Segment, 0, 16)
msg = append(msg, message.Text("已为你生成视频总结\n\n"))
msg = append(msg, message.Text(videoSummary.Data.ModelResult.Summary, "\n\n"))
for _, v := range videoSummary.Data.ModelResult.Outline {
@@ -189,3 +243,47 @@ func getVideoSummary(cookiecfg *bz.CookieConfig, card bz.Card) (msg []message.Me
}
return
}
+
+func getVideoDownload(cookiecfg *bz.CookieConfig, card bz.Card, cachePath string) (msg []message.Segment, err error) {
+ var (
+ data []byte
+ videoDownload bz.VideoDownload
+ stderr bytes.Buffer
+ )
+ today := time.Now().Format("20060102")
+ videoFile := fmt.Sprintf("%s%s%s.mp4", cachePath, card.BvID, today)
+ if file.IsExist(videoFile) {
+ msg = append(msg, message.Video("file:///"+file.BOTPATH+"/"+videoFile))
+ return
+ }
+ data, err = web.RequestDataWithHeaders(web.NewDefaultClient(), bz.SignURL(fmt.Sprintf(bz.VideoDownloadURL, card.BvID, card.CID)), "GET", func(req *http.Request) error {
+ if cookiecfg != nil {
+ cookie := ""
+ cookie, err = cookiecfg.Load()
+ if err != nil {
+ return err
+ }
+ req.Header.Add("cookie", cookie)
+ }
+ req.Header.Set("User-Agent", ua)
+ return nil
+ }, nil)
+ if err != nil {
+ return
+ }
+ err = json.Unmarshal(data, &videoDownload)
+ if err != nil {
+ return
+ }
+ headers := fmt.Sprintf("User-Agent: %s\nReferer: %s", ua, bilibiliparseReferer)
+ // 限制最多下载8分钟视频
+ cmd := exec.Command("ffmpeg", "-ss", "0", "-t", "480", "-headers", headers, "-i", videoDownload.Data.Durl[0].URL, "-c", "copy", videoFile)
+ cmd.Stderr = &stderr
+ err = cmd.Run()
+ if err != nil {
+ err = errors.Errorf("未配置ffmpeg,%v", stderr)
+ return
+ }
+ msg = append(msg, message.Video("file:///"+file.BOTPATH+"/"+videoFile))
+ return
+}
diff --git a/plugin/bilibili/bilibilipush.go b/plugin/bilibili/bilibilipush.go
index 8a944305fb..52310db211 100644
--- a/plugin/bilibili/bilibilipush.go
+++ b/plugin/bilibili/bilibilipush.go
@@ -388,7 +388,7 @@ func sendLive(ctx *zero.Ctx) error {
if lCover == "" {
lCover = value.Get("keyframe").String()
}
- var msg []message.MessageSegment
+ var msg []message.Segment
msg = append(msg, message.Text(lName+" 正在直播:\n"))
msg = append(msg, message.Text(lTitle))
msg = append(msg, message.Image(lCover))
@@ -399,7 +399,7 @@ func sendLive(ctx *zero.Ctx) error {
switch {
case gid > 0:
if res := bdb.getAtAll(gid); res == 1 {
- msg = append([]message.MessageSegment{message.AtAll()}, msg...)
+ msg = append([]message.Segment{message.AtAll()}, msg...)
}
ctx.SendGroupMessage(gid, msg)
case gid < 0:
diff --git a/plugin/bilibili/card2msg.go b/plugin/bilibili/card2msg.go
index 5d3b2f3fe0..d2dcc5fe30 100644
--- a/plugin/bilibili/card2msg.go
+++ b/plugin/bilibili/card2msg.go
@@ -2,10 +2,12 @@ package bilibili
import (
"encoding/json"
+ "fmt"
"time"
bz "github.com/FloatTech/AnimeAPI/bilibili"
"github.com/FloatTech/floatbox/binary"
+ "github.com/FloatTech/floatbox/web"
"github.com/wdvxdr1123/ZeroBot/message"
)
@@ -25,13 +27,13 @@ var (
)
// dynamicCard2msg 处理DynCard
-func dynamicCard2msg(dynamicCard *bz.DynamicCard) (msg []message.MessageSegment, err error) {
+func dynamicCard2msg(dynamicCard *bz.DynamicCard) (msg []message.Segment, err error) {
var (
card bz.Card
vote bz.Vote
cType int
)
- msg = make([]message.MessageSegment, 0, 16)
+ msg = make([]message.Segment, 0, 16)
// 初始化结构体
err = json.Unmarshal(binary.StringToBytes(dynamicCard.Card), &card)
if err != nil {
@@ -50,7 +52,7 @@ func dynamicCard2msg(dynamicCard *bz.DynamicCard) (msg []message.MessageSegment,
msg = append(msg, message.Text(card.User.Uname, msgType[cType], "\n",
card.Item.Content, "\n",
"转发的内容: \n"))
- var originMsg []message.MessageSegment
+ var originMsg []message.Segment
var co bz.Card
co, err = bz.LoadCardDetail(card.Origin)
if err != nil {
@@ -146,18 +148,18 @@ func dynamicCard2msg(dynamicCard *bz.DynamicCard) (msg []message.MessageSegment,
}
// card2msg cType=1, 2, 4, 8, 16, 64, 256, 2048, 4200, 4308时,处理Card字符串,cType为card类型
-func card2msg(dynamicCard *bz.DynamicCard, card *bz.Card, cType int) (msg []message.MessageSegment, err error) {
+func card2msg(dynamicCard *bz.DynamicCard, card *bz.Card, cType int) (msg []message.Segment, err error) {
var (
vote bz.Vote
)
- msg = make([]message.MessageSegment, 0, 16)
+ msg = make([]message.Segment, 0, 16)
// 生成消息
switch cType {
case 1:
msg = append(msg, message.Text(card.User.Uname, msgType[cType], "\n",
card.Item.Content, "\n",
"转发的内容: \n"))
- var originMsg []message.MessageSegment
+ var originMsg []message.Segment
var co bz.Card
co, err = bz.LoadCardDetail(card.Origin)
if err != nil {
@@ -253,7 +255,7 @@ func card2msg(dynamicCard *bz.DynamicCard, card *bz.Card, cType int) (msg []mess
}
// dynamicDetail 用动态id查动态信息
-func dynamicDetail(cookiecfg *bz.CookieConfig, dynamicIDStr string) (msg []message.MessageSegment, err error) {
+func dynamicDetail(cookiecfg *bz.CookieConfig, dynamicIDStr string) (msg []message.Segment, err error) {
dyc, err := bz.GetDynamicDetail(cookiecfg, dynamicIDStr)
if err != nil {
return
@@ -262,8 +264,8 @@ func dynamicDetail(cookiecfg *bz.CookieConfig, dynamicIDStr string) (msg []messa
}
// articleCard2msg 专栏转消息
-func articleCard2msg(card bz.Card, defaultID string) (msg []message.MessageSegment) {
- msg = make([]message.MessageSegment, 0, 16)
+func articleCard2msg(card bz.Card, defaultID string) (msg []message.Segment) {
+ msg = make([]message.Segment, 0, 16)
for i := 0; i < len(card.OriginImageUrls); i++ {
msg = append(msg, message.Image(card.OriginImageUrls[i]))
}
@@ -274,8 +276,8 @@ func articleCard2msg(card bz.Card, defaultID string) (msg []message.MessageSegme
}
// liveCard2msg 直播卡片转消息
-func liveCard2msg(card bz.RoomCard) (msg []message.MessageSegment) {
- msg = make([]message.MessageSegment, 0, 16)
+func liveCard2msg(card bz.RoomCard) (msg []message.Segment) {
+ msg = make([]message.Segment, 0, 16)
msg = append(msg, message.Image(card.RoomInfo.Keyframe))
msg = append(msg, message.Text("\n", card.RoomInfo.Title, "\n",
"主播: ", card.AnchorInfo.BaseInfo.Uname, "\n",
@@ -302,25 +304,39 @@ func liveCard2msg(card bz.RoomCard) (msg []message.MessageSegment) {
}
// videoCard2msg 视频卡片转消息
-func videoCard2msg(card bz.Card) (msg []message.MessageSegment, err error) {
- var mCard bz.MemberCard
- msg = make([]message.MessageSegment, 0, 16)
+func videoCard2msg(card bz.Card) (msg []message.Segment, err error) {
+ var (
+ mCard bz.MemberCard
+ onlineTotal bz.OnlineTotal
+ )
+ msg = make([]message.Segment, 0, 16)
mCard, err = bz.GetMemberCard(card.Owner.Mid)
- if err != nil {
- return
- }
msg = append(msg, message.Text("标题: ", card.Title, "\n"))
if card.Rights.IsCooperation == 1 {
for i := 0; i < len(card.Staff); i++ {
msg = append(msg, message.Text(card.Staff[i].Title, ": ", card.Staff[i].Name, " 粉丝: ", bz.HumanNum(card.Staff[i].Follower), "\n"))
}
} else {
- msg = append(msg, message.Text("UP主: ", card.Owner.Name, " 粉丝: ", bz.HumanNum(mCard.Fans), "\n"))
+ if err != nil {
+ msg = append(msg, message.Text("UP主: ", card.Owner.Name, "\n"))
+ } else {
+ msg = append(msg, message.Text("UP主: ", card.Owner.Name, " 粉丝: ", bz.HumanNum(mCard.Fans), "\n"))
+ }
}
- msg = append(msg, message.Text("播放: ", bz.HumanNum(card.Stat.View), " 弹幕: ", bz.HumanNum(card.Stat.Danmaku)))
msg = append(msg, message.Image(card.Pic))
- msg = append(msg, message.Text("\n点赞: ", bz.HumanNum(card.Stat.Like), " 投币: ", bz.HumanNum(card.Stat.Coin), "\n",
- "收藏: ", bz.HumanNum(card.Stat.Favorite), " 分享: ", bz.HumanNum(card.Stat.Share), "\n",
+ data, err := web.GetData(fmt.Sprintf(bz.OnlineTotalURL, card.BvID, card.CID))
+ if err != nil {
+ return
+ }
+ err = json.Unmarshal(data, &onlineTotal)
+ if err != nil {
+ return
+ }
+ msg = append(msg, message.Text("👀播放: ", bz.HumanNum(card.Stat.View), " 💬弹幕: ", bz.HumanNum(card.Stat.Danmaku),
+ "\n👍点赞: ", bz.HumanNum(card.Stat.Like), " 💰投币: ", bz.HumanNum(card.Stat.Coin),
+ "\n📁收藏: ", bz.HumanNum(card.Stat.Favorite), " 🔗分享: ", bz.HumanNum(card.Stat.Share),
+ "\n📝简介: ", card.Desc,
+ "\n🏄♂️ 总共 ", onlineTotal.Data.Total, " 人在观看,", onlineTotal.Data.Count, " 人在网页端观看\n",
bz.VURL, card.BvID, "\n\n"))
return
}
diff --git a/plugin/bilibili/card2msg_test.go b/plugin/bilibili/card2msg_test.go
index 5c43c8491c..5d85d77507 100644
--- a/plugin/bilibili/card2msg_test.go
+++ b/plugin/bilibili/card2msg_test.go
@@ -47,7 +47,7 @@ func TestVideoInfo(t *testing.T) {
}
func TestLiveRoomInfo(t *testing.T) {
- card, err := bz.GetLiveRoomInfo("83171")
+ card, err := bz.GetLiveRoomInfo("83171", "b_ut=7;buvid3=0;i-wanna-go-back=-1;innersign=0;")
if err != nil {
t.Fatal(err)
}
diff --git a/plugin/bookreview/book_review.go b/plugin/bookreview/book_review.go
index 7336f68cbf..3778a903ea 100644
--- a/plugin/bookreview/book_review.go
+++ b/plugin/bookreview/book_review.go
@@ -10,6 +10,7 @@ import (
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
@@ -24,7 +25,7 @@ func init() {
})
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = engine.DataFolder() + "bookreview.db"
+ db = sql.New(engine.DataFolder() + "bookreview.db")
// os.RemoveAll(dbpath)
_, _ = engine.GetLazyData("bookreview.db", true)
err := db.Open(time.Hour)
diff --git a/plugin/bookreview/model.go b/plugin/bookreview/model.go
index 93d3993223..afd0217c74 100644
--- a/plugin/bookreview/model.go
+++ b/plugin/bookreview/model.go
@@ -7,11 +7,11 @@ type book struct {
BookReview string `db:"bookreview"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
// 暂时随机选择一个书评
func getBookReviewByKeyword(keyword string) (b book) {
- _ = db.Find("bookreview", &b, "where bookreview LIKE '%"+keyword+"%'")
+ _ = db.Find("bookreview", &b, "WHERE bookreview LIKE ?", "%"+keyword+"%")
return
}
diff --git a/plugin/chatcount/chatcount.go b/plugin/chatcount/chatcount.go
new file mode 100644
index 0000000000..caf88ebeed
--- /dev/null
+++ b/plugin/chatcount/chatcount.go
@@ -0,0 +1,108 @@
+// Package chatcount 聊天时长统计
+package chatcount
+
+import (
+ "fmt"
+ "image"
+ "net/http"
+ "strconv"
+ "sync"
+
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ "github.com/FloatTech/floatbox/file"
+ "github.com/FloatTech/imgfactory"
+ "github.com/FloatTech/rendercard"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ "github.com/FloatTech/zbputils/img/text"
+)
+
+const (
+ rankSize = 10
+)
+
+func init() {
+ engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "聊天时长统计",
+ Help: "- 查询水群@xxx\n- 查看水群排名",
+ PrivateDataFolder: "chatcount",
+ })
+ go func() {
+ ctdb = initialize(engine.DataFolder() + "chatcount.db")
+ }()
+ engine.OnMessage(zero.OnlyGroup).SetBlock(false).
+ Handle(func(ctx *zero.Ctx) {
+ remindTime, remindFlag := ctdb.updateChatTime(ctx.Event.GroupID, ctx.Event.UserID)
+ if remindFlag {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text(fmt.Sprintf("BOT提醒:你今天已经水群%d分钟了!", remindTime)))
+ }
+ })
+
+ engine.OnPrefix(`查询水群`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ param := ctx.State["args"].(string)
+ var uid int64
+ if len(ctx.Event.Message) > 1 && ctx.Event.Message[1].Type == "at" {
+ uid, _ = strconv.ParseInt(ctx.Event.Message[1].Data["qq"], 10, 64)
+ } else if param == "" {
+ uid = ctx.Event.UserID
+ }
+ name := ctx.NickName()
+ todayTime, todayMessage, totalTime, totalMessage := ctdb.getChatTime(ctx.Event.GroupID, uid)
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("%s今天水了%d分%d秒,发了%d条消息;总计水了%d分%d秒,发了%d条消息。", name, todayTime/60, todayTime%60, todayMessage, totalTime/60, totalTime%60, totalMessage)))
+ })
+ engine.OnFullMatch("查看水群排名", zero.OnlyGroup).Limit(ctxext.LimitByGroup).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ chatTimeList := ctdb.getChatRank(ctx.Event.GroupID)
+ if len(chatTimeList) == 0 {
+ ctx.SendChain(message.Text("ERROR: 没有水群数据"))
+ return
+ }
+ rankinfo := make([]*rendercard.RankInfo, len(chatTimeList))
+
+ wg := &sync.WaitGroup{}
+ wg.Add(len(chatTimeList))
+ for i := 0; i < len(chatTimeList) && i < rankSize; i++ {
+ go func(i int) {
+ defer wg.Done()
+ resp, err := http.Get("https://q4.qlogo.cn/g?b=qq&nk=" + strconv.FormatInt(chatTimeList[i].UserID, 10) + "&s=100")
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ img, _, err := image.Decode(resp.Body)
+ if err != nil {
+ return
+ }
+ rankinfo[i] = &rendercard.RankInfo{
+ TopLeftText: ctx.CardOrNickName(chatTimeList[i].UserID),
+ BottomLeftText: "消息数: " + strconv.FormatInt(chatTimeList[i].TodayMessage, 10) + " 条",
+ RightText: strconv.FormatInt(chatTimeList[i].TodayTime/60, 10) + "分" + strconv.FormatInt(chatTimeList[i].TodayTime%60, 10) + "秒",
+ Avatar: img,
+ }
+ }(i)
+ }
+ wg.Wait()
+ fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ img, err := rendercard.DrawRankingCard(fontbyte, "今日水群排行榜", rankinfo)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ sendimg, err := imgfactory.ToBytes(img)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ if id := ctx.SendChain(message.ImageBytes(sendimg)); id.ID() == 0 {
+ ctx.SendChain(message.Text("ERROR: 可能被风控了"))
+ }
+ })
+}
diff --git a/plugin/chatcount/model.go b/plugin/chatcount/model.go
new file mode 100644
index 0000000000..8fba57d17c
--- /dev/null
+++ b/plugin/chatcount/model.go
@@ -0,0 +1,225 @@
+package chatcount
+
+import (
+ "fmt"
+ "os"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/RomiChan/syncx"
+
+ "github.com/jinzhu/gorm"
+)
+
+const (
+ chatInterval = 300
+)
+
+var (
+ // ctdb 聊天时长数据库全局变量
+ ctdb *chattimedb
+ // l 水群提醒时间提醒段,单位分钟
+ l = newLeveler(60, 120, 180, 240, 300)
+)
+
+// chattimedb 聊天时长数据库结构体
+type chattimedb struct {
+ // ctdb.userTimestampMap 每个人发言的时间戳 key=groupID_userID
+ userTimestampMap syncx.Map[string, int64]
+ // ctdb.userTodayTimeMap 每个人今日水群时间 key=groupID_userID
+ userTodayTimeMap syncx.Map[string, int64]
+ // ctdb.userTodayMessageMap 每个人今日水群次数 key=groupID_userID
+ userTodayMessageMap syncx.Map[string, int64]
+ // db 数据库
+ db *gorm.DB
+ // chatmu 读写添加锁
+ chatmu sync.Mutex
+}
+
+// initialize 初始化
+func initialize(dbpath string) *chattimedb {
+ var err error
+ if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) {
+ // 生成文件
+ f, err := os.Create(dbpath)
+ if err != nil {
+ return nil
+ }
+ defer f.Close()
+ }
+ gdb, err := gorm.Open("sqlite3", dbpath)
+ if err != nil {
+ panic(err)
+ }
+ gdb.AutoMigrate(&chatTime{})
+ return &chattimedb{
+ db: gdb,
+ }
+}
+
+// Close 关闭
+func (ctdb *chattimedb) Close() error {
+ db := ctdb.db
+ return db.Close()
+}
+
+// chatTime 聊天时长,时间的单位都是秒
+type chatTime struct {
+ ID uint `gorm:"primary_key"`
+ GroupID int64 `gorm:"column:group_id"`
+ UserID int64 `gorm:"column:user_id"`
+ TodayTime int64 `gorm:"-"`
+ TodayMessage int64 `gorm:"-"`
+ TotalTime int64 `gorm:"column:total_time;default:0"`
+ TotalMessage int64 `gorm:"column:total_message;default:0"`
+}
+
+// TableName 表名
+func (chatTime) TableName() string {
+ return "chat_time"
+}
+
+// updateChatTime 更新发言时间,todayTime的单位是分钟
+func (ctdb *chattimedb) updateChatTime(gid, uid int64) (remindTime int64, remindFlag bool) {
+ ctdb.chatmu.Lock()
+ defer ctdb.chatmu.Unlock()
+ db := ctdb.db
+ now := time.Now()
+ keyword := fmt.Sprintf("%v_%v", gid, uid)
+ ts, ok := ctdb.userTimestampMap.Load(keyword)
+ if !ok {
+ ctdb.userTimestampMap.Store(keyword, now.Unix())
+ ctdb.userTodayMessageMap.Store(keyword, 1)
+ return
+ }
+ lastTime := time.Unix(ts, 0)
+ todayTime, _ := ctdb.userTodayTimeMap.Load(keyword)
+ totayMessage, _ := ctdb.userTodayMessageMap.Load(keyword)
+ // 这个消息数是必须统计的
+ ctdb.userTodayMessageMap.Store(keyword, totayMessage+1)
+ st := chatTime{
+ GroupID: gid,
+ UserID: uid,
+ TotalTime: todayTime,
+ TotalMessage: totayMessage,
+ }
+
+ // 如果不是同一天,把TotalTime,TotalMessage重置
+ if lastTime.YearDay() != now.YearDay() {
+ if err := db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).First(&st).Error; err != nil {
+ if gorm.IsRecordNotFoundError(err) {
+ db.Model(&st).Create(&st)
+ }
+ } else {
+ db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).Update(
+ map[string]any{
+ "total_time": st.TotalTime + todayTime,
+ "total_message": st.TotalMessage + totayMessage,
+ })
+ }
+ ctdb.userTimestampMap.Store(keyword, now.Unix())
+ ctdb.userTodayTimeMap.Delete(keyword)
+ ctdb.userTodayMessageMap.Delete(keyword)
+ return
+ }
+
+ userChatTime := int64(now.Sub(lastTime).Seconds())
+ // 当聊天时间在一定范围内的话,则计入时长
+ if userChatTime < chatInterval {
+ ctdb.userTodayTimeMap.Store(keyword, todayTime+userChatTime)
+ remindTime = (todayTime + userChatTime) / 60
+ remindFlag = l.level(int((todayTime+userChatTime)/60)) > l.level(int(todayTime/60))
+ }
+ ctdb.userTimestampMap.Store(keyword, now.Unix())
+ return
+}
+
+// getChatTime 获得用户聊天时长和消息次数,todayTime,totalTime的单位是秒,todayMessage,totalMessage单位是条数
+func (ctdb *chattimedb) getChatTime(gid, uid int64) (todayTime, todayMessage, totalTime, totalMessage int64) {
+ ctdb.chatmu.Lock()
+ defer ctdb.chatmu.Unlock()
+ db := ctdb.db
+ st := chatTime{}
+ db.Model(&st).Where("group_id = ? and user_id = ?", gid, uid).First(&st)
+ keyword := fmt.Sprintf("%v_%v", gid, uid)
+ todayTime, _ = ctdb.userTodayTimeMap.Load(keyword)
+ todayMessage, _ = ctdb.userTodayMessageMap.Load(keyword)
+ totalTime = st.TotalTime
+ totalMessage = st.TotalMessage
+ return
+}
+
+// getChatRank 获得水群排名,时间单位为秒
+func (ctdb *chattimedb) getChatRank(gid int64) (chatTimeList []chatTime) {
+ ctdb.chatmu.Lock()
+ defer ctdb.chatmu.Unlock()
+ chatTimeList = make([]chatTime, 0, 100)
+ keyList := make([]string, 0, 100)
+ ctdb.userTimestampMap.Range(func(key string, value int64) bool {
+ t := time.Unix(value, 0)
+ if strings.Contains(key, strconv.FormatInt(gid, 10)) && t.YearDay() == time.Now().YearDay() {
+ keyList = append(keyList, key)
+ }
+ return true
+ })
+ for _, v := range keyList {
+ _, a, _ := strings.Cut(v, "_")
+ uid, _ := strconv.ParseInt(a, 10, 64)
+ todayTime, _ := ctdb.userTodayTimeMap.Load(v)
+ todayMessage, _ := ctdb.userTodayMessageMap.Load(v)
+ chatTimeList = append(chatTimeList, chatTime{
+ GroupID: gid,
+ UserID: uid,
+ TodayTime: todayTime,
+ TodayMessage: todayMessage,
+ })
+ }
+ sort.Sort(sortChatTime(chatTimeList))
+ return
+}
+
+// leveler 结构体,包含一个 levelArray 字段
+type leveler struct {
+ levelArray []int
+}
+
+// newLeveler 构造函数,用于创建 Leveler 实例
+func newLeveler(levels ...int) *leveler {
+ return &leveler{
+ levelArray: levels,
+ }
+}
+
+// level 方法,封装了 getLevel 函数的逻辑
+func (l *leveler) level(t int) int {
+ for i := len(l.levelArray) - 1; i >= 0; i-- {
+ if t >= l.levelArray[i] {
+ return i + 1
+ }
+ }
+ return 0
+}
+
+// sortChatTime chatTime排序数组
+type sortChatTime []chatTime
+
+// Len 实现 sort.Interface
+func (a sortChatTime) Len() int {
+ return len(a)
+}
+
+// Less 实现 sort.Interface,按 TodayTime 降序,TodayMessage 降序
+func (a sortChatTime) Less(i, j int) bool {
+ if a[i].TodayTime == a[j].TodayTime {
+ return a[i].TodayMessage > a[j].TodayMessage
+ }
+ return a[i].TodayTime > a[j].TodayTime
+}
+
+// Swap 实现 sort.Interface
+func (a sortChatTime) Swap(i, j int) {
+ a[i], a[j] = a[j], a[i]
+}
diff --git a/plugin/chess/core.go b/plugin/chess/core.go
index d3e0926b04..44ecfd0a03 100644
--- a/plugin/chess/core.go
+++ b/plugin/chess/core.go
@@ -235,7 +235,7 @@ func play(groupCode, senderUin int64, moveStr string) (msg message.Message, err
chessRoomMap.Store(groupCode, room)
}
// 生成棋盘图片
- var boardImgEle message.MessageSegment
+ var boardImgEle message.Segment
if !room.isBlindfold {
boardImgEle, err = getBoardElement(groupCode)
if err != nil {
@@ -400,7 +400,7 @@ func createGame(isBlindfold bool, groupCode, senderUin int64, senderName string)
room.blackPlayer = senderUin
room.blackName = senderName
chessRoomMap.Store(groupCode, room)
- var boardImgEle message.MessageSegment
+ var boardImgEle message.Segment
if !room.isBlindfold {
boardImgEle, err = getBoardElement(groupCode)
if err != nil {
@@ -442,7 +442,7 @@ func abortGame(room chessRoom, groupCode int64, hint string) (message.Message, e
}
// getBoardElement 获取棋盘图片的消息内容
-func getBoardElement(groupCode int64) (imgMsg message.MessageSegment, err error) {
+func getBoardElement(groupCode int64) (imgMsg message.Segment, err error) {
fontdata, err := file.GetLazyData(text.GNUUnifontFontFile, control.Md5File, true)
if err != nil {
return
diff --git a/plugin/chouxianghua/chouxianghua.go b/plugin/chouxianghua/chouxianghua.go
index c7e0710542..8cffb4032e 100644
--- a/plugin/chouxianghua/chouxianghua.go
+++ b/plugin/chouxianghua/chouxianghua.go
@@ -9,6 +9,7 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
fcext "github.com/FloatTech/floatbox/ctxext"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
)
@@ -23,7 +24,7 @@ func init() {
en.OnRegex("^抽象翻译((\\s|[\\r\\n]|[\\p{Han}\\p{P}A-Za-z0-9])+)$",
fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = en.DataFolder() + "cxh.db"
+ db = sql.New(en.DataFolder() + "cxh.db")
// os.RemoveAll(dbpath)
_, _ = en.GetLazyData("cxh.db", true)
err := db.Open(time.Hour)
diff --git a/plugin/chouxianghua/model.go b/plugin/chouxianghua/model.go
index e7796a0dfb..9329fb433e 100644
--- a/plugin/chouxianghua/model.go
+++ b/plugin/chouxianghua/model.go
@@ -11,11 +11,11 @@ type emoji struct {
Emoji string `db:"emoji"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
func getPinyinByWord(word string) string {
var p pinyin
- _ = db.Find("pinyin", &p, "where word = '"+word+"'")
+ _ = db.Find("pinyin", &p, "WHERE word = ?", word)
return p.Pronun
}
@@ -25,6 +25,6 @@ func getPronunByDWord(w0, w1 rune) string {
func getEmojiByPronun(pronun string) string {
var e emoji
- _ = db.Find("emoji", &e, "where pronunciation = '"+pronun+"'")
+ _ = db.Find("emoji", &e, "WHERE pronunciation = ?", pronun)
return e.Emoji
}
diff --git a/plugin/cpstory/cpstory.go b/plugin/cpstory/cpstory.go
index 1d38ad9706..3bb2a8e5b3 100644
--- a/plugin/cpstory/cpstory.go
+++ b/plugin/cpstory/cpstory.go
@@ -11,6 +11,7 @@ import (
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/math"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
)
@@ -24,7 +25,7 @@ func init() {
})
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = engine.DataFolder() + "cp.db"
+ db = sql.New(engine.DataFolder() + "cp.db")
// os.RemoveAll(dbpath)
_, _ = engine.GetLazyData("cp.db", true)
err := db.Open(time.Hour)
diff --git a/plugin/cpstory/model.go b/plugin/cpstory/model.go
index 0eb11de8e0..31921bc985 100644
--- a/plugin/cpstory/model.go
+++ b/plugin/cpstory/model.go
@@ -9,7 +9,7 @@ type cpstory struct {
Story string `db:"story"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
func getRandomCpStory() (cs cpstory) {
_ = db.Pick("cp_story", &cs)
diff --git a/plugin/crypter/fumo.go b/plugin/crypter/fumo.go
new file mode 100644
index 0000000000..a96a1dfbf8
--- /dev/null
+++ b/plugin/crypter/fumo.go
@@ -0,0 +1,95 @@
+// Package crypter Fumo语
+package crypter
+
+import (
+ "encoding/base64"
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// Base64字符表
+const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+
+// Fumo语字符表 - 使用各种fumo变体来表示base64字符
+var fumoChars = []string{
+ "fumo-", "Fumo-", "fUmo-", "fuMo-", "fumO-", "FUmo-", "FuMo-", "FumO-",
+ "fUMo-", "fUmO-", "fuMO-", "FUMo-", "FUmO-", "fUMO-", "FUMO-", "fumo.",
+ "Fumo.", "fUmo.", "fuMo.", "fumO.", "FUmo.", "FuMo.", "FumO.", "fUMo.",
+ "fUmO.", "fuMO.", "FUMo.", "FUmO.", "fUMO.", "FUMO.", "fumo,", "Fumo,",
+ "fUmo,", "fuMo,", "fumO,", "FUmo,", "FuMo,", "FumO,", "fUMo,", "fUmO,",
+ "fuMO,", "FUMo,", "FuMO,", "fUMO,", "FUMO,", "fumo+", "Fumo+", "fUmo+",
+ "fuMo+", "fumO+", "FUmo+", "FuMo+", "FumO+", "fUMo+", "fUmO+", "fuMO+",
+ "FUMo+", "FUmO+", "fUMO+", "FUMO+", "fumo|", "Fumo|", "fUmo|", "fuMo|",
+ "fumO|", "FUmo|", "FuMo|", "FumO|", "fUMo|", "fUmO|", "fuMO|", "fumo/",
+ "Fumo/", "fUmo/",
+}
+
+// Base64 2 Fumo
+// 创建编码映射表
+var encodeMap = make(map[byte]string)
+
+// 创建解码映射表
+var decodeMap = make(map[string]byte)
+
+func init() {
+ for i := 0; i < 64 && i < len(fumoChars); i++ {
+ base64Char := base64Chars[i]
+ fumoChar := fumoChars[i]
+
+ encodeMap[base64Char] = fumoChar
+ decodeMap[fumoChar] = base64Char
+ }
+}
+
+// 加密
+func encryptFumo(text string) string {
+ if text == "" {
+ return "请输入要加密的文本"
+ }
+ textBytes := []byte(text)
+ base64String := base64.StdEncoding.EncodeToString(textBytes)
+ base64Body := strings.TrimRight(base64String, "=")
+ paddingCount := len(base64String) - len(base64Body)
+ var fumoBody strings.Builder
+ for _, char := range base64Body {
+ if fumoChar, exists := encodeMap[byte(char)]; exists {
+ fumoBody.WriteString(fumoChar)
+ } else {
+ return fmt.Sprintf("Fumo加密失败: 未知字符 %c", char)
+ }
+ }
+ result := fumoBody.String() + strings.Repeat("=", paddingCount)
+
+ return result
+}
+
+// 解密
+func decryptFumo(fumoText string) string {
+ if fumoText == "" {
+ return "请输入要解密的Fumo语密文"
+ }
+ fumoBody := strings.TrimRight(fumoText, "=")
+ paddingCount := len(fumoText) - len(fumoBody)
+ fumoPattern := regexp.MustCompile(`(\w+[-.,+|/])`)
+ fumoWords := fumoPattern.FindAllString(fumoBody, -1)
+ reconstructed := strings.Join(fumoWords, "")
+ if reconstructed != fumoBody {
+ return "Fumo解密失败: 包含无效的Fumo字符或格式错误"
+ }
+ var base64Body strings.Builder
+ for _, fumoWord := range fumoWords {
+ if base64Char, exists := decodeMap[fumoWord]; exists {
+ base64Body.WriteByte(base64Char)
+ } else {
+ return fmt.Sprintf("Fumo解密失败: 包含无效的Fumo字符 %s", fumoWord)
+ }
+ }
+ base64String := base64Body.String() + strings.Repeat("=", paddingCount)
+ decodedBytes, err := base64.StdEncoding.DecodeString(base64String)
+ if err != nil {
+ return fmt.Sprintf("Fumo解密失败: Base64解码错误 %v", err)
+ }
+ originalText := string(decodedBytes)
+ return originalText
+}
diff --git a/plugin/crypter/handlers.go b/plugin/crypter/handlers.go
new file mode 100644
index 0000000000..1cec8a47ee
--- /dev/null
+++ b/plugin/crypter/handlers.go
@@ -0,0 +1,42 @@
+// Package crypter 处理函数
+package crypter
+
+import (
+ "github.com/FloatTech/AnimeAPI/airecord"
+ "github.com/sirupsen/logrus"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+// hou
+func houEncryptHandler(ctx *zero.Ctx) {
+ text := ctx.State["regex_matched"].([]string)[1]
+ result := encodeHou(text)
+ logrus.Infoln("[crypter] 回复内容:", result)
+ recCfg := airecord.GetConfig()
+ record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, result)
+ if record != "" {
+ ctx.SendChain(message.Record(record))
+ } else {
+ ctx.SendChain(message.Text(result))
+ }
+}
+
+func houDecryptHandler(ctx *zero.Ctx) {
+ text := ctx.State["regex_matched"].([]string)[1]
+ result := decodeHou(text)
+ ctx.SendChain(message.Text(result))
+}
+
+// fumo
+func fumoEncryptHandler(ctx *zero.Ctx) {
+ text := ctx.State["regex_matched"].([]string)[1]
+ result := encryptFumo(text)
+ ctx.SendChain(message.Text(result))
+}
+
+func fumoDecryptHandler(ctx *zero.Ctx) {
+ text := ctx.State["regex_matched"].([]string)[1]
+ result := decryptFumo(text)
+ ctx.SendChain(message.Text(result))
+}
diff --git a/plugin/crypter/hou.go b/plugin/crypter/hou.go
new file mode 100644
index 0000000000..6302375bcd
--- /dev/null
+++ b/plugin/crypter/hou.go
@@ -0,0 +1,88 @@
+// Package crypter 齁语加解密
+package crypter
+
+import (
+ "strings"
+)
+
+// 齁语密码表
+var houCodebook = []string{
+ "齁", "哦", "噢", "喔", "咕", "咿", "嗯", "啊",
+ "~", "哈", "!", "唔", "哼", "❤", "呃", "呼",
+}
+
+// 索引: 0 1 2 3 4 5 6 7
+// 8 9 10 11 12 13 14 15
+
+// 创建映射表
+var houCodebookMap = make(map[string]int)
+
+// 初始化映射表
+func init() {
+ for idx, ch := range houCodebook {
+ houCodebookMap[ch] = idx
+ }
+}
+
+func encodeHou(text string) string {
+ if text == "" {
+ return "请输入要加密的文本"
+ }
+ var encoded strings.Builder
+ textBytes := []byte(text)
+ for _, b := range textBytes {
+ high := (b >> 4) & 0x0F
+ low := b & 0x0F
+ encoded.WriteString(houCodebook[high])
+ encoded.WriteString(houCodebook[low])
+ }
+
+ return encoded.String()
+}
+
+func decodeHou(code string) string {
+ if code == "" {
+ return "请输入要解密的齁语密文"
+ }
+
+ // 过滤出有效的齁语字符
+ var validChars []string
+ for _, r := range code {
+ charStr := string(r)
+ if _, exists := houCodebookMap[charStr]; exists {
+ validChars = append(validChars, charStr)
+ }
+ }
+
+ if len(validChars)%2 != 0 {
+ return "齁语密文长度错误,无法解密"
+ }
+
+ // 解密过程
+ var byteList []byte
+ for i := 0; i < len(validChars); i += 2 {
+ highIdx, highExists := houCodebookMap[validChars[i]]
+ lowIdx, lowExists := houCodebookMap[validChars[i+1]]
+
+ if !highExists || !lowExists {
+ return "齁语密文包含无效字符"
+ }
+
+ originalByte := byte((highIdx << 4) | lowIdx)
+ byteList = append(byteList, originalByte)
+ }
+
+ result := string(byteList)
+
+ if !isValidUTF8(result) {
+ return "齁语解密失败,结果不是有效的文本"
+ }
+
+ return result
+}
+
+// 检查字符串是否为有效的UTF-8编码
+func isValidUTF8(s string) bool {
+ // Go的string类型默认就是UTF-8,如果转换没有出错说明是有效的
+ return len(s) > 0 || s == ""
+}
diff --git a/plugin/crypter/main.go b/plugin/crypter/main.go
new file mode 100644
index 0000000000..4a94ffcaaa
--- /dev/null
+++ b/plugin/crypter/main.go
@@ -0,0 +1,31 @@
+// Package crypter 奇怪语言加解密
+package crypter
+
+import (
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ zero "github.com/wdvxdr1123/ZeroBot"
+)
+
+func init() {
+ engine := control.Register("crypter", &ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "奇怪语言加解密",
+ Help: "多种语言加解密插件\n" +
+ "- 齁语加解密:\n" +
+ "- 齁语加密 [文本] 或 h加密 [文本]\n" +
+ "- 齁语解密 [密文] 或 h解密 [密文]\n\n" +
+ "- Fumo语加解密:\n" +
+ "- fumo加密 [文本]\n" +
+ "- fumo解密 [密文]\n\n",
+ PublicDataFolder: "Crypter",
+ })
+
+ // hou
+ engine.OnRegex(`^(?:齁语加密|h加密)\s*(.+)$`).SetBlock(true).Handle(houEncryptHandler)
+ engine.OnRegex(`^(?:齁语解密|h解密)\s*(.+)$`).SetBlock(true).Handle(houDecryptHandler)
+
+ // Fumo
+ engine.OnRegex(`^fumo加密\s*(.+)$`).SetBlock(true).Handle(fumoEncryptHandler)
+ engine.OnRegex(`^fumo解密\s*(.+)$`).SetBlock(true).Handle(fumoDecryptHandler)
+}
diff --git a/plugin/curse/curse.go b/plugin/curse/curse.go
index e0761d033f..31425408ff 100644
--- a/plugin/curse/curse.go
+++ b/plugin/curse/curse.go
@@ -10,6 +10,7 @@ import (
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/process"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
@@ -29,7 +30,7 @@ func init() {
})
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = engine.DataFolder() + "curse.db"
+ db = sql.New(engine.DataFolder() + "curse.db")
_, err := engine.GetLazyData("curse.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
diff --git a/plugin/curse/model.go b/plugin/curse/model.go
index 8405aa5a6c..6cb0cea705 100644
--- a/plugin/curse/model.go
+++ b/plugin/curse/model.go
@@ -8,9 +8,9 @@ type curse struct {
Level string `db:"level"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
func getRandomCurseByLevel(level string) (c curse) {
- _ = db.Find("curse", &c, "where level = '"+level+"' ORDER BY RANDOM() limit 1")
+ _ = db.Find("curse", &c, "WHERE level = ? ORDER BY RANDOM() limit 1", level)
return
}
diff --git a/plugin/diana/data/text.go b/plugin/diana/data/text.go
index 29b8f621c5..0b6f211dcf 100644
--- a/plugin/diana/data/text.go
+++ b/plugin/diana/data/text.go
@@ -23,7 +23,7 @@ type text struct {
// LoadText 加载小作文
func LoadText(dbfile string) error {
_, err := file.GetLazyData(dbfile, control.Md5File, false)
- db.DBPath = dbfile
+ db = sql.New(dbfile)
if err != nil {
return err
}
@@ -63,7 +63,7 @@ func RandText() string {
// HentaiText 发大病
func HentaiText() string {
var t text
- err := db.Find("text", &t, "where id = -3802576048116006195")
+ err := db.Find("text", &t, "WHERE id = -3802576048116006195")
if err != nil {
return err.Error()
}
diff --git a/plugin/dish/dish.go b/plugin/dish/dish.go
index 6fd9985837..731aef6d4d 100644
--- a/plugin/dish/dish.go
+++ b/plugin/dish/dish.go
@@ -2,7 +2,6 @@
package dish
import (
- "fmt"
"strings"
"time"
@@ -25,7 +24,7 @@ type dish struct {
}
var (
- db = &sql.Sqlite{}
+ db sql.Sqlite
initialized = false
)
@@ -37,7 +36,7 @@ func init() {
PublicDataFolder: "Dish",
})
- db.DBPath = en.DataFolder() + "dishes.db"
+ db = sql.New(en.DataFolder() + "dishes.db")
if _, err := en.GetLazyData("dishes.db", true); err != nil {
logrus.Warnln("[dish]获取菜谱数据库文件失败")
@@ -62,7 +61,7 @@ func init() {
return
}
- name := ctx.NickName()
+ name := ctx.CardOrNickName(ctx.Event.UserID)
dishName := ctx.State["args"].(string)
if dishName == "" {
@@ -77,17 +76,15 @@ func init() {
}
var d dish
- if err := db.Find("dish", &d, fmt.Sprintf("WHERE name like '%%%s%%'", dishName)); err != nil {
+ if err := db.Find("dish", &d, "WHERE name LIKE ?", "%"+dishName+"%"); err != nil {
ctx.SendChain(message.Text("客官,本店没有" + dishName))
return
}
- ctx.SendChain(message.Text(fmt.Sprintf(
- "已为客官%s找到%s的做法辣!\n"+
- "原材料:%s\n"+
- "步骤:\n"+
- "%s",
- name, d.Name, d.Materials, d.Steps),
+ ctx.SendChain(message.Text(
+ "已为客官", name, "找到", d.Name, "的做法辣!\n",
+ "原材料:", d.Materials, "\n",
+ "步骤:", d.Steps,
))
})
@@ -105,12 +102,10 @@ func init() {
return
}
- ctx.SendChain(message.Text(fmt.Sprintf(
- "已为客官%s送上%s的做法:\n"+
- "原材料:%s\n"+
- "步骤:\n"+
- "%s",
- name, d.Name, d.Materials, d.Steps),
+ ctx.SendChain(message.Text(
+ "已为客官", name, "送上", d.Name, "的做法:\n",
+ "原材料:", d.Materials, "\n",
+ "步骤:", d.Steps,
))
})
}
diff --git a/plugin/drawlots/main.go b/plugin/drawlots/main.go
index 67edaea2a9..dde3530d58 100644
--- a/plugin/drawlots/main.go
+++ b/plugin/drawlots/main.go
@@ -252,17 +252,17 @@ func randGif(gifName string, uid int64) (image.Image, error) {
// https://zhuanlan.zhihu.com/p/27718135
rect := image.Rect(0, 0, config.Width, config.Height)
if rect.Min == rect.Max {
- var max image.Point
+ var maxP image.Point
for _, frame := range im.Image {
maxF := frame.Bounds().Max
- if max.X < maxF.X {
- max.X = maxF.X
+ if maxP.X < maxF.X {
+ maxP.X = maxF.X
}
- if max.Y < maxF.Y {
- max.Y = maxF.Y
+ if maxP.Y < maxF.Y {
+ maxP.Y = maxF.Y
}
}
- rect.Max = max
+ rect.Max = maxP
}
img := image.NewRGBA(rect)
b := fcext.RandSenderPerDayN(uid, len(im.Image)) + 1
diff --git a/plugin/driftbottle/main.go b/plugin/driftbottle/main.go
index 8aba986d55..38429e55ea 100644
--- a/plugin/driftbottle/main.go
+++ b/plugin/driftbottle/main.go
@@ -27,7 +27,7 @@ type sea struct {
Time string `db:"time"` // we need to know the current time,master>
}
-var seaSide = &sql.Sqlite{}
+var seaSide sql.Sqlite
var seaLocker sync.RWMutex
// We need a container to inject what we need :(
@@ -39,15 +39,15 @@ func init() {
Help: "- @bot pick" + "- @bot throw xxx (xxx为投递内容)",
PrivateDataFolder: "driftbottle",
})
- seaSide.DBPath = en.DataFolder() + "sea.db"
+ seaSide = sql.New(en.DataFolder() + "sea.db")
err := seaSide.Open(time.Hour)
if err != nil {
panic(err)
}
- _ = createChannel(seaSide)
+ _ = createChannel(&seaSide)
en.OnFullMatch("pick", zero.OnlyToMe, zero.OnlyGroup).Limit(ctxext.LimitByGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- be, err := fetchBottle(seaSide)
+ be, err := fetchBottle(&seaSide)
if err != nil {
ctx.SendChain(message.Text("ERR:", err))
}
@@ -75,7 +75,7 @@ func init() {
senderFormatTime,
ctx.CardOrNickName(ctx.Event.UserID),
rawMessageCallBack,
- ).throw(seaSide)
+ ).throw(&seaSide)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
diff --git a/plugin/emojimix/mix.go b/plugin/emojimix/mix.go
index b9aab0a627..69b5dde821 100644
--- a/plugin/emojimix/mix.go
+++ b/plugin/emojimix/mix.go
@@ -79,7 +79,7 @@ func match(ctx *zero.Ctx) bool {
return false
}
-func face2emoji(face message.MessageSegment) rune {
+func face2emoji(face message.Segment) rune {
if face.Type == "text" {
r := []rune(face.Data["text"])
if len(r) != 1 {
diff --git a/plugin/emozi/main.go b/plugin/emozi/main.go
new file mode 100644
index 0000000000..de61b41804
--- /dev/null
+++ b/plugin/emozi/main.go
@@ -0,0 +1,118 @@
+// Package emozi 颜文字抽象转写
+package emozi
+
+import (
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/FloatTech/AnimeAPI/emozi"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ "github.com/sirupsen/logrus"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+func init() {
+ en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "颜文字抽象转写",
+ Help: "- 抽象转写[文段]\n- 抽象还原[文段]\n- 抽象登录[用户名]",
+ PrivateDataFolder: "emozi",
+ })
+ usr := emozi.Anonymous()
+ data, err := os.ReadFile(en.DataFolder() + "user.txt")
+ refresh := func() {
+ go func() {
+ t := time.NewTicker(time.Hour)
+ defer t.Stop()
+ for range t.C {
+ if !usr.IsValid() {
+ time.Sleep(time.Second * 2)
+ err := usr.Login()
+ if err != nil {
+ logrus.Warnln("[emozi] 重新登录账号失败:", err)
+ }
+ }
+ }
+ }()
+ }
+ refresher := sync.Once{}
+ if err == nil {
+ arr := strings.Split(string(data), "\n")
+ if len(arr) >= 2 {
+ usr = emozi.NewUser(arr[0], arr[1])
+ err = usr.Login()
+ if err != nil {
+ logrus.Infoln("[emozi]", "以", arr[0], "身份登录失败:", err)
+ usr = emozi.Anonymous()
+ } else {
+ logrus.Infoln("[emozi]", "以", arr[0], "身份登录成功")
+ refresher.Do(refresh)
+ }
+ }
+ }
+
+ en.OnPrefix("抽象转写").Limit(ctxext.LimitByUser).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ txt := strings.TrimSpace(ctx.State["args"].(string))
+ out, chs, err := usr.Marshal(false, txt)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ if len(chs) == 0 {
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(out)))
+ return
+ }
+ for i, c := range chs {
+ ch := ctx.Get("请选择第" + strconv.Itoa(i) + "个多音字(1~" + strconv.Itoa(c) + ")")
+ n, err := strconv.Atoi(ch)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ if n < 1 || n > c {
+ ctx.SendChain(message.Text("ERROR: 输入越界"))
+ return
+ }
+ chs[i] = n - 1
+ }
+ out, _, err = usr.Marshal(false, txt, chs...)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(out)))
+ })
+ en.OnPrefix("抽象还原").Limit(ctxext.LimitByUser).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ txt := strings.TrimSpace(ctx.State["args"].(string))
+ out, err := usr.Unmarshal(false, txt)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(out)))
+ })
+ en.OnPrefix("抽象登录", zero.OnlyPrivate).Limit(ctxext.LimitByUser).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ name := strings.TrimSpace(ctx.State["args"].(string))
+ pswd := strings.TrimSpace(ctx.Get("请输入密码"))
+ newusr := emozi.NewUser(name, pswd)
+ err := newusr.Login()
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ err = os.WriteFile(en.DataFolder()+"user.txt", []byte(name+"\n"+pswd), 0644)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ usr = newusr
+ refresher.Do(refresh)
+ ctx.SendChain(message.Text("成功"))
+ })
+}
diff --git a/plugin/fortune/fortune.go b/plugin/fortune/fortune.go
index 705f852d70..17bae08f07 100644
--- a/plugin/fortune/fortune.go
+++ b/plugin/fortune/fortune.go
@@ -146,7 +146,7 @@ func init() {
digest := md5.Sum(helper.StringToBytes(zipfile + strconv.Itoa(index) + title + text))
cachefile := cache + hex.EncodeToString(digest[:])
- err = pool.SendImageFromPool(cachefile, cachefile, func() error {
+ err = pool.SendImageFromPool(cachefile, func(cachefile string) error {
f, err := os.Create(cachefile)
if err != nil {
return err
@@ -154,7 +154,7 @@ func init() {
_, err = draw(background, fontdata, title, text, f)
_ = f.Close()
return err
- }, ctxext.Send(ctx), ctxext.GetMessage(ctx))
+ }, ctxext.Send(ctx))
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
diff --git a/plugin/funny/laugh.go b/plugin/funny/laugh.go
index 52de4b14b1..12da99f609 100644
--- a/plugin/funny/laugh.go
+++ b/plugin/funny/laugh.go
@@ -21,7 +21,7 @@ type joke struct {
Text string `db:"text"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
func init() {
en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
@@ -32,7 +32,7 @@ func init() {
})
en.OnPrefixGroup([]string{"讲个笑话", "夸夸"}, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = en.DataFolder() + "jokes.db"
+ db = sql.New(en.DataFolder() + "jokes.db")
_, err := en.GetLazyData("jokes.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
diff --git a/plugin/gif/context.go b/plugin/gif/context.go
index a1161053fc..6db373bf2c 100644
--- a/plugin/gif/context.go
+++ b/plugin/gif/context.go
@@ -20,7 +20,7 @@ func dlchan(name string, s *string, wg *sync.WaitGroup, exit func(error)) {
defer wg.Done()
target := datapath + `materials/` + name
if file.IsNotExist(target) {
- data, err := web.RequestDataWith(web.NewTLS12Client(), `https://gitcode.net/m0_60838134/imagematerials/-/raw/main/`+name, "GET", "gitcode.net", web.RandUA(), nil)
+ data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/master/` + name)
if err != nil {
_ = os.Remove(target)
exit(err)
@@ -48,7 +48,7 @@ func dlchan(name string, s *string, wg *sync.WaitGroup, exit func(error)) {
func dlblock(name string) (string, error) {
target := datapath + `materials/` + name
if file.IsNotExist(target) {
- data, err := web.RequestDataWith(web.NewTLS12Client(), `https://gitcode.net/m0_60838134/imagematerials/-/raw/main/`+name, "GET", "gitcode.net", web.RandUA(), nil)
+ data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/master/` + name)
if err != nil {
_ = os.Remove(target)
return "", err
diff --git a/plugin/gif/logo.go b/plugin/gif/logo.go
index f0fe05b654..7bf668d003 100644
--- a/plugin/gif/logo.go
+++ b/plugin/gif/logo.go
@@ -16,7 +16,7 @@ func (cc *context) prepareLogos(s ...string) error {
if err != nil {
err = file.DownloadTo("https://gchat.qpic.cn/gchatpic_new//--"+strings.ToUpper(v)+"/0", cc.headimgsdir[i])
} else {
- err = file.DownloadTo("http://q4.qlogo.cn/g?b=qq&nk="+v+"&s=640", cc.headimgsdir[i])
+ err = file.DownloadTo("https://q4.qlogo.cn/g?b=qq&nk="+v+"&s=640", cc.headimgsdir[i])
}
if err != nil {
return err
diff --git a/plugin/gif/run.go b/plugin/gif/run.go
index 04524c2cd1..ad87880cac 100644
--- a/plugin/gif/run.go
+++ b/plugin/gif/run.go
@@ -150,7 +150,7 @@ func init() { // 插件主体
PrivateDataFolder: "gif",
}).ApplySingle(ctxext.DefaultSingle)
datapath = file.BOTPATH + "/" + en.DataFolder()
- en.OnRegex(`^(` + strings.Join(cmd, "|") + `)[\s\S]*?(\[CQ:(image\,file=([0-9a-zA-Z]{32}).*|at.+?(\d{5,11}))\].*|(\d+))$`).
+ en.OnRegex(`^(` + strings.Join(cmd, "|") + `)[\s\S]*?(\[CQ:(image\,file=([0-9a-zA-Z]{32}).*|at.+?qq=(\d{5,11})).*\].*|(\d+))$`).
SetBlock(true).Handle(func(ctx *zero.Ctx) {
list := ctx.State["regex_matched"].([]string)
atUserID, _ := strconv.ParseInt(list[4]+list[5]+list[6], 10, 64)
diff --git a/plugin/guessmusic/apiservice.go b/plugin/guessmusic/apiservice.go
index 98617de3a1..d0443f2975 100644
--- a/plugin/guessmusic/apiservice.go
+++ b/plugin/guessmusic/apiservice.go
@@ -246,11 +246,7 @@ func init() {
ctx.SendChain(message.Text(serviceErr, err))
return
}
- if err == nil {
- ctx.SendChain(message.Text("成功!"))
- } else {
- ctx.SendChain(message.Text(serviceErr, err))
- }
+ ctx.SendChain(message.Text("成功!"))
})
// 下载歌曲到对应的歌单里面
engine.OnRegex(`^下载歌单\s*((https:\/\/music\.163\.com\/#\/playlist\?id=)?(\d+)|http:\/\/music\.163\.com\/playlist\/(\d+).*[^\s$])\s*到\s*(.*)$`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
diff --git a/plugin/guessmusic/guessmusic.go b/plugin/guessmusic/guessmusic.go
index 07de481406..d18d763e5d 100644
--- a/plugin/guessmusic/guessmusic.go
+++ b/plugin/guessmusic/guessmusic.go
@@ -130,10 +130,10 @@ func init() {
after := time.NewTimer(120 * time.Second)
wg := sync.WaitGroup{}
var (
- messageStr message.MessageSegment // 文本信息
- tickCount = 0 // 音频数量
- answerCount = 0 // 问答次数
- win bool // 是否赢得游戏
+ messageStr message.Segment // 文本信息
+ tickCount = 0 // 音频数量
+ answerCount = 0 // 问答次数
+ win bool // 是否赢得游戏
)
for {
select {
@@ -281,7 +281,7 @@ func cutMusic(musicName, pathOfMusic, outputPath string) (err error) {
}
// 数据匹配(结果信息,答题次数,提示次数,是否结束游戏)
-func gameMatch(c *zero.Ctx, beginner int64, musicInfo []string, answerTimes, tickTimes int) (message.MessageSegment, int, int, bool) {
+func gameMatch(c *zero.Ctx, beginner int64, musicInfo []string, answerTimes, tickTimes int) (message.Segment, int, int, bool) {
answer := strings.Replace(c.Event.Message.String(), "-", "", 1)
// 回答内容转小写,比对时再把标准答案转小写
answer = ConvertText(answer)
diff --git a/plugin/guessmusic/main.go b/plugin/guessmusic/main.go
index 211da1279c..3c758d5553 100644
--- a/plugin/guessmusic/main.go
+++ b/plugin/guessmusic/main.go
@@ -535,7 +535,7 @@ func getFileURLbyFileName(ctx *zero.Ctx, fileName string) (fileSearchName, fileU
for _, fileNameOflist := range files {
if strings.Contains(fileNameOflist.Get("file_name").String(), fileName) {
fileSearchName = fileNameOflist.Get("file_name").String()
- fileURL = ctx.GetThisGroupFileUrl(fileNameOflist.Get("busid").Int(), fileNameOflist.Get("file_id").String())
+ fileURL = ctx.GetThisGroupFileURL(fileNameOflist.Get("busid").Int(), fileNameOflist.Get("file_id").String())
return
}
}
@@ -561,7 +561,7 @@ func getFileURLbyfolderID(ctx *zero.Ctx, fileName, folderid string) (fileSearchN
for _, fileNameOflist := range files {
if strings.Contains(fileNameOflist.Get("file_name").String(), fileName) {
fileSearchName = fileNameOflist.Get("file_name").String()
- fileURL = ctx.GetThisGroupFileUrl(fileNameOflist.Get("busid").Int(), fileNameOflist.Get("file_id").String())
+ fileURL = ctx.GetThisGroupFileURL(fileNameOflist.Get("busid").Int(), fileNameOflist.Get("file_id").String())
return
}
}
diff --git a/plugin/jandan/data.go b/plugin/jandan/data.go
index 21534d15fc..eeb2d14b79 100644
--- a/plugin/jandan/data.go
+++ b/plugin/jandan/data.go
@@ -6,7 +6,7 @@ import (
sql "github.com/FloatTech/sqlite"
)
-var db = &sql.Sqlite{}
+var db sql.Sqlite
var mu sync.RWMutex
type picture struct {
diff --git a/plugin/jandan/jandan.go b/plugin/jandan/jandan.go
index 873f98b1c2..9963520b62 100644
--- a/plugin/jandan/jandan.go
+++ b/plugin/jandan/jandan.go
@@ -10,6 +10,7 @@ import (
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/antchfx/htmlquery"
@@ -31,7 +32,7 @@ func init() {
})
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = engine.DataFolder() + "pics.db"
+ db = sql.New(engine.DataFolder() + "pics.db")
_, _ = engine.GetLazyData("pics.db", false)
err := db.Open(time.Hour)
if err != nil {
@@ -95,7 +96,7 @@ func init() {
u := "https:" + v.Attr[0].Val
i := crc64.Checksum(binary.StringToBytes(u), crc64.MakeTable(crc64.ISO))
mu.RLock()
- ok := db.CanFind("picture", "where id="+strconv.FormatUint(i, 10))
+ ok := db.CanFind("picture", "WHERE id = ?", i)
mu.RUnlock()
if !ok {
mu.Lock()
diff --git a/plugin/jptingroom/jptingroom.go b/plugin/jptingroom/jptingroom.go
index 611b184c1f..4f7ab8cdcb 100644
--- a/plugin/jptingroom/jptingroom.go
+++ b/plugin/jptingroom/jptingroom.go
@@ -6,6 +6,7 @@ import (
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
@@ -26,7 +27,7 @@ func init() { // 插件主体
})
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = engine.DataFolder() + "item.db"
+ db = sql.New(engine.DataFolder() + "item.db")
_, err := engine.GetLazyData("item.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
diff --git a/plugin/jptingroom/model.go b/plugin/jptingroom/model.go
index 4ab9de4afa..80ac149e68 100644
--- a/plugin/jptingroom/model.go
+++ b/plugin/jptingroom/model.go
@@ -17,14 +17,16 @@ type item struct {
Datetime time.Time `db:"datetime"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
func getRandomAudioByCategory(category string) (t item) {
- _ = db.Find("item", &t, "where category = '"+category+"' ORDER BY RANDOM() limit 1")
+ _ = db.Find("item", &t, "WHERE category = ? ORDER BY RANDOM() limit 1", category)
return
}
func getRandomAudioByCategoryAndKeyword(category string, keyword string) (t item) {
- _ = db.Find("item", &t, "where category = '"+category+"' and (title like '%"+keyword+"%' or content like '%"+keyword+"%') ORDER BY RANDOM() limit 1")
+ _ = db.Find("item", &t,
+ "WHERE category = ? and (title LIKE ? OR content LIKE ?) ORDER BY RANDOM() limit 1",
+ category, "%"+keyword+"%", "%"+keyword+"%")
return
}
diff --git a/plugin/kfccrazythursday/kfccrazythursday.go b/plugin/kfccrazythursday/kfccrazythursday.go
index 971dd1e452..d144cdaebb 100644
--- a/plugin/kfccrazythursday/kfccrazythursday.go
+++ b/plugin/kfccrazythursday/kfccrazythursday.go
@@ -2,7 +2,6 @@
package kfccrazythursday
import (
- "github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
@@ -11,7 +10,7 @@ import (
)
const (
- crazyURL = "https://api.jixs.cc/api/wenan-fkxqs/index.php"
+ crazyURL = "https://api.pearktrue.cn/api/kfc/"
)
func init() {
@@ -26,6 +25,8 @@ func init() {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
- ctx.SendChain(message.Text(binary.BytesToString(data)))
+
+ // 根据来源API修改返回方式到直接输出文本
+ ctx.SendChain(message.Text(string(data)))
})
}
diff --git a/plugin/lolicon/lolicon.go b/plugin/lolicon/lolicon.go
index 25f5eb3465..040f9d9659 100644
--- a/plugin/lolicon/lolicon.go
+++ b/plugin/lolicon/lolicon.go
@@ -18,7 +18,6 @@ import (
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
- imagepool "github.com/FloatTech/zbputils/img/pool"
)
const (
@@ -68,18 +67,7 @@ func init() {
ctx.SendChain(message.Text("ERROR: ", err))
continue
}
- name := imageurl[strings.LastIndex(imageurl, "/")+1 : len(imageurl)-4]
- m, err := imagepool.GetImage(name)
- if err != nil {
- m.SetFile(imageurl)
- _, _ = m.Push(ctxext.SendToSelf(ctx), ctxext.GetMessage(ctx))
- process.SleepAbout1sTo2s()
- }
- if err == nil {
- queue <- m.String()
- } else {
- queue <- imageurl
- }
+ queue <- imageurl
}
}()
select {
diff --git a/plugin/manager/gist.go b/plugin/manager/gist.go
index 61fc60c064..7dde8b0164 100644
--- a/plugin/manager/gist.go
+++ b/plugin/manager/gist.go
@@ -18,7 +18,7 @@ import (
const gistraw = "https://gist.githubusercontent.com/%s/%s/raw/%s"
func checkNewUser(qq, gid int64, ghun, hash string) (bool, string) {
- if db.CanFind("member", "where ghun="+ghun) {
+ if db.CanFind("member", "WHERE ghun = ?", ghun) {
return false, "该github用户已入群"
}
gidsum := md5.Sum(helper.StringToBytes(strconv.FormatInt(gid, 10)))
diff --git a/plugin/manager/manager.go b/plugin/manager/manager.go
index 415796d6df..2521412bdb 100644
--- a/plugin/manager/manager.go
+++ b/plugin/manager/manager.go
@@ -50,6 +50,7 @@ const (
"- 列出所有提醒\n" +
"- 翻牌\n" +
"- 赞我\n" +
+ "- 群签到\n" +
"- 对信息回复: 回应表情 [表情]\n" +
"- 设置欢迎语XXX 可选添加 [{at}] [{nickname}] [{avatar}] [{uid}] [{gid}] [{groupname}]\n" +
"- 测试欢迎语\n" +
@@ -63,7 +64,7 @@ const (
)
var (
- db = &sql.Sqlite{}
+ db sql.Sqlite
clock timer.Clock
)
@@ -76,12 +77,12 @@ func init() { // 插件主体
})
go func() {
- db.DBPath = engine.DataFolder() + "config.db"
+ db = sql.New(engine.DataFolder() + "config.db")
err := db.Open(time.Hour)
if err != nil {
panic(err)
}
- clock = timer.NewClock(db)
+ clock = timer.NewClock(&db)
err = db.Create("welcome", &welcome{})
if err != nil {
panic(err)
@@ -156,10 +157,11 @@ func init() { // 插件主体
ctx.SendChain(message.Text("全员自闭结束~"))
})
// 禁言
- engine.OnRegex(`^禁言.*?(\d+).*?\s(\d+)(.*)`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).
+ engine.OnMessage(zero.NewPattern(nil).Text("^禁言").At().Text("(\\d+)\\s*(.*)").AsRule(), zero.OnlyGroup, zero.AdminPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
- duration := math.Str2Int64(ctx.State["regex_matched"].([]string)[2])
- switch ctx.State["regex_matched"].([]string)[3] {
+ parsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ duration := math.Str2Int64(parsed[2].Text()[1])
+ switch parsed[2].Text()[2] {
case "分钟":
//
case "小时":
@@ -173,8 +175,8 @@ func init() { // 插件主体
duration = 43199 // qq禁言最大时长为一个月
}
ctx.SetThisGroupBan(
- math.Str2Int64(ctx.State["regex_matched"].([]string)[1]), // 要禁言的人的qq
- duration*60, // 要禁言的时间(分钟)
+ math.Str2Int64(parsed[1].At()), // 要禁言的人的qq
+ duration*60, // 要禁言的时间(分钟)
)
ctx.SendChain(message.Text("小黑屋收留成功~"))
})
@@ -404,6 +406,12 @@ func init() { // 插件主体
ctx.SendLike(ctx.Event.UserID, 10)
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("给你赞了10下哦,记得回我~"))
})
+ // 群签到
+ engine.OnFullMatch("群签到", zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByUser).
+ Handle(func(ctx *zero.Ctx) {
+ ctx.SetGroupSign(ctx.Event.GroupID)
+ ctx.SendChain(message.Text("群签到成功,可在手机端输入框中的打卡查看"))
+ })
facere := regexp.MustCompile(`\[CQ:face,id=(\d+)\]`)
// 给消息回应表情
engine.OnRegex(`^\[CQ:reply,id=(-?\d+)\].*回应表情\s*(.+)\s*$`, zero.AdminPermission, zero.OnlyGroup).SetBlock(true).
@@ -442,7 +450,7 @@ func init() { // 插件主体
Handle(func(ctx *zero.Ctx) {
if ctx.Event.NoticeType == "group_increase" && ctx.Event.SelfID != ctx.Event.UserID {
var w welcome
- err := db.Find("welcome", &w, "where gid = "+strconv.FormatInt(ctx.Event.GroupID, 10))
+ err := db.Find("welcome", &w, "WHERE gid = ?", ctx.Event.GroupID)
if err == nil {
ctx.SendGroupMessage(ctx.Event.GroupID, message.ParseMessageFromString(welcometocq(ctx, w.Msg)))
} else {
@@ -494,12 +502,12 @@ func init() { // 插件主体
Handle(func(ctx *zero.Ctx) {
if ctx.Event.NoticeType == "group_decrease" {
var w welcome
- err := db.Find("farewell", &w, "where gid = "+strconv.FormatInt(ctx.Event.GroupID, 10))
+ err := db.Find("farewell", &w, "WHERE gid = ?", ctx.Event.GroupID)
if err == nil {
- ctx.SendGroupMessage(ctx.Event.GroupID, message.ParseMessageFromString(welcometocq(ctx, w.Msg)))
+ collectsend(ctx, message.ParseMessageFromString(welcometocq(ctx, w.Msg))...)
} else {
userid := ctx.Event.UserID
- ctx.SendChain(message.Text(ctx.CardOrNickName(userid), "(", userid, ")", "离开了我们..."))
+ collectsend(ctx, message.Text(ctx.CardOrNickName(userid), "(", userid, ")", "离开了我们..."))
}
}
})
@@ -523,7 +531,7 @@ func init() { // 插件主体
engine.OnFullMatch("测试欢迎语", zero.OnlyGroup, zero.AdminPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
var w welcome
- err := db.Find("welcome", &w, "where gid = "+strconv.FormatInt(ctx.Event.GroupID, 10))
+ err := db.Find("welcome", &w, "WHERE gid = ?", ctx.Event.GroupID)
if err == nil {
ctx.SendGroupMessage(ctx.Event.GroupID, message.ParseMessageFromString(welcometocq(ctx, w.Msg)))
} else {
@@ -550,7 +558,7 @@ func init() { // 插件主体
engine.OnFullMatch("测试告别辞", zero.OnlyGroup, zero.AdminPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
var w welcome
- err := db.Find("farewell", &w, "where gid = "+strconv.FormatInt(ctx.Event.GroupID, 10))
+ err := db.Find("farewell", &w, "WHERE gid = ?", ctx.Event.GroupID)
if err == nil {
ctx.SendGroupMessage(ctx.Event.GroupID, message.ParseMessageFromString(welcometocq(ctx, w.Msg)))
} else {
@@ -649,7 +657,7 @@ func init() { // 插件主体
if rsp.RetCode == 0 {
ctx.SendChain(message.Text(option, "成功"))
} else {
- ctx.SendChain(message.Text(option, "失败, 信息: ", rsp.Msg, "解释: ", rsp.Wording))
+ ctx.SendChain(message.Text(option, "失败, 信息: ", rsp.Message, "解释: ", rsp.Wording))
}
})
engine.OnCommand("精华列表", zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
@@ -698,19 +706,19 @@ func init() { // 插件主体
if rsp.RetCode == 0 {
ctx.SendChain(message.Text("取消成功"))
} else {
- ctx.SendChain(message.Text("取消失败, 信息: ", rsp.Msg, "解释: ", rsp.Wording))
+ ctx.SendChain(message.Text("取消失败, 信息: ", rsp.Message, "解释: ", rsp.Wording))
}
})
}
// 传入 ctx 和 welcome格式string 返回cq格式string 使用方法:welcometocq(ctx,w.Msg)
func welcometocq(ctx *zero.Ctx, welcome string) string {
- uid := strconv.FormatInt(ctx.Event.UserID, 10) // 用户id
- nickname := ctx.CardOrNickName(ctx.Event.UserID) // 用户昵称
- at := "[CQ:at,qq=" + uid + "]" // at用户
- avatar := "[CQ:image,file=" + "http://q4.qlogo.cn/g?b=qq&nk=" + uid + "&s=640]" // 用户头像
- gid := strconv.FormatInt(ctx.Event.GroupID, 10) // 群id
- groupname := ctx.GetThisGroupInfo(true).Name // 群名
+ uid := strconv.FormatInt(ctx.Event.UserID, 10) // 用户id
+ nickname := ctx.CardOrNickName(ctx.Event.UserID) // 用户昵称
+ at := "[CQ:at,qq=" + uid + "]" // at用户
+ avatar := "[CQ:image,file=" + "https://q4.qlogo.cn/g?b=qq&nk=" + uid + "&s=640]" // 用户头像
+ gid := strconv.FormatInt(ctx.Event.GroupID, 10) // 群id
+ groupname := ctx.GetThisGroupInfo(true).Name // 群名
cqstring := strings.ReplaceAll(welcome, "{at}", at)
cqstring = strings.ReplaceAll(cqstring, "{nickname}", nickname)
cqstring = strings.ReplaceAll(cqstring, "{avatar}", avatar)
diff --git a/plugin/manager/manager.db.go b/plugin/manager/model.go
similarity index 100%
rename from plugin/manager/manager.db.go
rename to plugin/manager/model.go
diff --git a/plugin/manager/slow.go b/plugin/manager/slow.go
new file mode 100644
index 0000000000..5c2e2a850f
--- /dev/null
+++ b/plugin/manager/slow.go
@@ -0,0 +1,46 @@
+package manager
+
+import (
+ "time"
+
+ "github.com/RomiChan/syncx"
+ "github.com/fumiama/slowdo"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+var slowsenders = syncx.Map[int64, *syncx.Lazy[*slowdo.Job[*zero.Ctx, message.Segment]]]{}
+
+func collectsend(ctx *zero.Ctx, msgs ...message.Segment) {
+ id := ctx.Event.GroupID
+ if id == 0 {
+ // only support group
+ return
+ }
+ lazy, _ := slowsenders.LoadOrStore(id, &syncx.Lazy[*slowdo.Job[*zero.Ctx, message.Segment]]{
+ Init: func() *slowdo.Job[*zero.Ctx, message.Segment] {
+ x, err := slowdo.NewJob(time.Second*5, ctx, func(ctx *zero.Ctx, msg []message.Segment) {
+ if len(msg) == 1 {
+ ctx.Send(msg)
+ return
+ }
+ m := make(message.Message, len(msg))
+ for i, item := range msg {
+ m[i] = message.CustomNode(
+ zero.BotConfig.NickName[0],
+ ctx.Event.SelfID,
+ message.Message{item})
+ }
+ ctx.SendGroupForwardMessage(id, m)
+ })
+ if err != nil {
+ panic(err)
+ }
+ return x
+ },
+ })
+ job := lazy.Get()
+ for _, msg := range msgs {
+ job.Add(msg)
+ }
+}
diff --git a/plugin/manager/timer/parse.go b/plugin/manager/timer/parse.go
index 83a296fbf5..585a88edb6 100644
--- a/plugin/manager/timer/parse.go
+++ b/plugin/manager/timer/parse.go
@@ -96,12 +96,12 @@ func GetFilledTimer(dateStrs []string, botqq, grp int64, matchDateOnly bool) *Ti
if len(minuteStr) == 3 {
minuteStr = []rune{minuteStr[0], minuteStr[2]} // 去除中间的十
}
- min := chineseNum2Int(minuteStr)
- if min < -1 || min > 59 { // 分钟非法
+ minute := chineseNum2Int(minuteStr)
+ if minute < -1 || minute > 59 { // 分钟非法
t.Alert = "分钟非法!"
return &t
}
- t.SetMinute(min)
+ t.SetMinute(minute)
if !matchDateOnly {
urlStr := dateStrs[5]
if urlStr != "" { // 是图片url
diff --git a/plugin/manager/timer/timer.db.go b/plugin/manager/timer/timer.db.go
index 86305325e7..7a5afa9688 100644
--- a/plugin/manager/timer/timer.db.go
+++ b/plugin/manager/timer/timer.db.go
@@ -22,7 +22,7 @@ func (t *Timer) InsertInto(db *sql.Sqlite) error {
/*
func getTimerFrom(db *sql.Sqlite, id uint32) (t Timer, err error) {
- err = db.Find("timer", &t, "where id = "+strconv.Itoa(int(id)))
+ err = db.Find("timer", &t, "WHERE id = "+strconv.Itoa(int(id)))
return
}
*/
diff --git a/plugin/manager/timer/timer.go b/plugin/manager/timer/timer.go
index 281d33d2e0..43a7254017 100644
--- a/plugin/manager/timer/timer.go
+++ b/plugin/manager/timer/timer.go
@@ -2,7 +2,6 @@
package timer
import (
- "strconv"
"strings"
"sync"
"time"
@@ -29,7 +28,7 @@ type Clock struct {
var (
// @全体成员
- atall = message.MessageSegment{
+ atall = message.Segment{
Type: "at",
Data: map[string]string{
"qq": "all",
@@ -133,7 +132,7 @@ func (c *Clock) CancelTimer(key uint32) bool {
}
c.timersmu.Lock()
delete(*c.timers, key) // 避免重复取消
- e := c.db.Del("timer", "where id = "+strconv.Itoa(int(key)))
+ e := c.db.Del("timer", "WHERE id = ?", key)
c.timersmu.Unlock()
return e == nil
}
diff --git a/plugin/manager/timer/timer_test.go b/plugin/manager/timer/timer_test.go
index fabfad5c1d..ceb87c7482 100644
--- a/plugin/manager/timer/timer_test.go
+++ b/plugin/manager/timer/timer_test.go
@@ -25,8 +25,8 @@ func TestNextWakeTime(t *testing.T) {
}
func TestClock(t *testing.T) {
- db := &sql.Sqlite{DBPath: "test.db"}
- c := NewClock(db)
+ db := sql.New("test.db")
+ c := NewClock(&db)
c.AddTimerIntoDB(GetFilledTimer([]string{"", "12", "-1", "12", "0", "", "test"}, 0, 0, false))
t.Log(c.ListTimers(0))
t.Fail()
diff --git a/plugin/manager/timer/wrap.go b/plugin/manager/timer/wrap.go
index bb48f29d1b..8c1c42c4fc 100644
--- a/plugin/manager/timer/wrap.go
+++ b/plugin/manager/timer/wrap.go
@@ -44,10 +44,10 @@ func (t *Timer) Hour() (h int) {
}
// Minute 6bits
-func (t *Timer) Minute() (min int) {
- min = int(t.En1Month4Day5Week3Hour5Min6 & 0x00003f)
- if min == 0b111111 {
- min = -1
+func (t *Timer) Minute() (m int) {
+ m = int(t.En1Month4Day5Week3Hour5Min6 & 0x00003f)
+ if m == 0b111111 {
+ m = -1
}
return
}
@@ -82,6 +82,6 @@ func (t *Timer) SetHour(h int) {
}
// SetMinute ...
-func (t *Timer) SetMinute(min int) {
- t.En1Month4Day5Week3Hour5Min6 = (int32(min) & 0x00003f) | (t.En1Month4Day5Week3Hour5Min6 & 0xffffc0)
+func (t *Timer) SetMinute(m int) {
+ t.En1Month4Day5Week3Hour5Min6 = (int32(m) & 0x00003f) | (t.En1Month4Day5Week3Hour5Min6 & 0xffffc0)
}
diff --git a/plugin/mcfish/fish.go b/plugin/mcfish/fish.go
index df8b4c48e1..29cf4cf605 100644
--- a/plugin/mcfish/fish.go
+++ b/plugin/mcfish/fish.go
@@ -59,7 +59,7 @@ func init() {
for {
select {
case <-time.After(time.Second * 120):
- ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消钓鱼")))
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消购买")))
return
case e := <-recv:
nextcmd := e.Event.Message.String()
@@ -69,7 +69,7 @@ func init() {
}
money := wallet.GetWalletOf(uid)
if money < 100 {
- ctx.SendChain(message.Text("你钱包当前只有", money, "ATRI币,无法完成支付"))
+ ctx.SendChain(message.Text("你钱包当前只有", money, wallet.GetWalletName(), ",无法完成支付"))
return
}
err = wallet.InsertWalletOf(uid, -100)
@@ -129,12 +129,12 @@ func init() {
fishNumber *= 3
}
} else {
- fishNmaes, err := dbdata.pickFishFor(uid, fishNumber)
+ fishNames, err := dbdata.pickFishFor(uid, fishNumber*3)
if err != nil {
ctx.SendChain(message.Text("[ERROR at fish.go.5.1]:", err))
return
}
- if len(fishNmaes) == 0 {
+ if len(fishNames) == 0 {
equipInfo.Durable = 0
err = dbdata.updateUserEquip(equipInfo)
if err != nil {
@@ -143,14 +143,14 @@ func init() {
ctx.SendChain(message.Text("美西螈因为没吃到鱼,钓鱼时一直没回来,你失去了美西螈"))
return
}
- msg = "(美西螈吃掉了"
+ msg = "(美西螈掉落翻5倍,吃3倍鱼:\n吃掉了:"
fishNumber = 0
- for name, number := range fishNmaes {
+ for name, number := range fishNames {
fishNumber += number
- msg += strconv.Itoa(number) + name + "、"
+ msg += strconv.Itoa(number) + name + " "
}
msg += ")"
- fishNumber /= 2
+ fishNumber /= 3
}
waitTime := 120 / (equipInfo.Induce + 1)
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("你开始去钓鱼了,请耐心等待鱼上钩(预计要", time.Second*time.Duration(waitTime), ")"))
@@ -267,8 +267,8 @@ func init() {
thingName = "金竿"
case dice >= probabilities["钻石竿"].Min && dice < probabilities["钻石竿"].Max:
thingName = "钻石竿"
- case dice >= probabilities["下界合金竿竿竿"].Min && dice < probabilities["下界合金竿竿竿"].Max:
- thingName = "下界合金竿竿竿"
+ case dice >= probabilities["下界合金竿"].Min && dice < probabilities["下界合金竿"].Max:
+ thingName = "下界合金竿"
default:
thingName = "木竿"
}
@@ -323,7 +323,7 @@ func init() {
newThing = thingInfo[0]
}
if equipInfo.Equip == "美西螈" && thingName != "美西螈" {
- number += 2
+ number += 4
}
newThing.Number += number
}
diff --git a/plugin/mcfish/main.go b/plugin/mcfish/main.go
index 8b58a58585..3b4d3db4af 100644
--- a/plugin/mcfish/main.go
+++ b/plugin/mcfish/main.go
@@ -6,6 +6,7 @@ import (
"math/rand"
"os"
"strconv"
+ "strings"
"sync"
"time"
@@ -20,15 +21,15 @@ import (
)
type fishdb struct {
- db *sql.Sqlite
sync.RWMutex
+ db sql.Sqlite
}
// FishLimit 钓鱼次数上限
const FishLimit = 50
// version 规则版本号
-const version = "5.4.2"
+const version = "5.6.2"
// 各物品信息
type jsonInfo struct {
@@ -121,39 +122,31 @@ var (
durationList = make(map[string]int, 50) // 装备耐久分布
discountList = make(map[string]int, 50) // 价格波动信息
enchantLevel = []string{"0", "Ⅰ", "Ⅱ", "Ⅲ"}
- dbdata = &fishdb{
- db: &sql.Sqlite{},
- }
+ dbdata fishdb
)
var (
engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "钓鱼",
- Help: "一款钓鱼模拟器\n----------指令----------\n" +
- "- 钓鱼看板/钓鱼商店\n- 购买xxx\n- 购买xxx [数量]\n- 出售xxx\n- 出售xxx [数量]\n- 出售所有垃圾\n" +
- "- 钓鱼背包\n- 装备[xx竿|三叉戟|美西螈]\n- 附魔[诱钓|海之眷顾]\n- 修复鱼竿\n- 合成[xx竿|三叉戟]\n- 消除[绑定|宝藏]诅咒\n- 消除[绑定|宝藏]诅咒 [数量]\n" +
- "- 进行钓鱼\n- 进行n次钓鱼\n- 当前装备概率明细\n" +
- "规则V" + version + ":\n" +
- "1.每日的商店价格是波动的!!如何最大化收益自己考虑一下喔\n" +
- "2.装备信息:\n-> 木竿 : 耐久上限:30 均价:100 上钩概率:0.7%\n-> 铁竿 : 耐久上限:50 均价:300 上钩概率:0.2%\n-> 金竿 : 耐久上限:70 均价700 上钩概率:0.06%\n" +
- "-> 钻石竿 : 耐久上限:100 均价1500 上钩概率:0.03%\n-> 下界合金竿 : 耐久上限:150 均价3100 上钩概率:0.01%\n-> 三叉戟 : 可使1次钓鱼视为3次钓鱼. 耐久上限:300 均价4000 只能合成、修复和交易\n" +
- "3.附魔书信息:\n-> 诱钓 : 减少上钩时间. 均价:1000, 上钩概率:0.25%\n-> 海之眷顾 : 增加宝藏上钩概率. 均价:2500, 上钩概率:0.10%\n" +
- "4.稀有物品:\n-> 唱片 : 出售物品时使用该物品使价格翻倍. 均价:3000, 上钩概率:0.01%\n" +
- "-> 美西螈 : 可装备,获得隐形[钓鱼佬]buff,并让钓到除鱼竿和美西螈外的物品数量变成3,无耐久上限.不可修复/附魔,每次钓鱼消耗两任意鱼类物品. 均价:3000, 上钩概率:0.01%\n" +
- "-> 海豚 : 使空竿概率变成垃圾概率. 均价:1000, 上钩概率:0.19%\n" +
- "-> 宝藏诅咒 : 无法交易,每一层就会增加购买时10%价格和减少出售时10%价格(超过10层会变为倒贴钱). 上钩概率:0.25%\n-> 净化书 : 用于消除宝藏诅咒. 均价:5000, 上钩概率:0.19%\n" +
- "5.鱼类信息:\n-> 鳕鱼 : 均价:10 上钩概率:0.69%\n-> 鲑鱼 : 均价:50 上钩概率:0.2%\n-> 热带鱼 : 均价:100 上钩概率:0.06%\n-> 河豚 : 均价:300 上钩概率:0.03%\n-> 鹦鹉螺 : 均价:500 上钩概率:0.01%\n-> 墨鱼 : 均价:500 上钩概率:0.01%\n" +
- "6.垃圾:\n-> 均价:10 上钩概率:30%\n" +
- "7.物品BUFF:\n-> 钓鱼佬 : 当背包名字含有'鱼'的物品数量超过100时激活,钓到物品概率提高至90%\n-> 修复大师 : 当背包鱼竿数量超过10时激活,修复物品时耐久百分百继承\n" +
- "8.合成:\n-> 铁竿 : 3x木竿\n-> 金竿 : 3x铁竿\n-> 钻石竿 : 3x金竿\n-> 下界合金竿 : 3x钻石竿\n-> 三叉戟 : 3x下界合金竿\n注:合成成功率90%,继承附魔等级合/3的等级\n" +
- "9.杂项:\n-> 无装备的情况下,每人最多可以购买3次100块钱的鱼竿\n-> 默认状态钓鱼上钩概率为60%(理论值!!!)\n-> 附魔的鱼竿会因附魔变得昂贵,每个附魔最高3级\n-> 三叉戟不算鱼竿,修复时可直接满耐久\n" +
- "-> 鱼竿数量大于50的不能买东西;\n 鱼竿数量大于30的不能钓鱼;\n 每购/售10次鱼竿获得1层宝藏诅咒;\n 每购买20次物品将获得3次价格减半福利;\n 每钓鱼75次获得1本净化书;\n" +
- " 每天最多只可出售5个鱼竿和购买15次物品;",
+ Help: "一款钓鱼模拟器,规则:V" + version +
+ "\n----------指令----------\n" +
+ "- 钓鱼背包\n" +
+ "- 进行钓鱼 / 进行n次钓鱼\n" +
+ "- 修复鱼竿\n" +
+ "- 钓鱼商店 / 钓鱼看板\n" +
+ "- 购买xxx / 购买xxx [数量]\n- 出售xxx / 出售xxx [数量]\n" +
+ "- 消除[绑定|宝藏]诅咒 / 消除[绑定|宝藏]诅咒 [数量]\n" +
+ "- 装备[xx竿|三叉戟|美西螈]\n" +
+ "- 附魔[诱钓|海之眷顾]\n" +
+ "- 合成[xx竿|三叉戟]\n" +
+ "- 出售所有垃圾\n" +
+ "- 当前装备概率明细\n" +
+ "- 查看钓鱼规则\n",
PublicDataFolder: "McFish",
}).ApplySingle(ctxext.DefaultSingle)
getdb = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- dbdata.db.DBPath = engine.DataFolder() + "fishdata.db"
+ dbdata.db = sql.New(engine.DataFolder() + "fishdata.db")
err := dbdata.db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("[ERROR at main.go.1]:", err))
@@ -208,7 +201,7 @@ func init() {
Min: probableList[2],
Max: probableList[3],
}
- min := make(map[string]int, 4)
+ minMap := make(map[string]int, 4)
for _, info := range articlesInfo.ArticleInfo {
switch {
case info.Type == "pole" || info.Name == "美西螈":
@@ -228,10 +221,10 @@ func init() {
durationList[info.Name] = info.Durable
}
probabilities[info.Name] = probabilityLimit{
- Min: min[info.Type],
- Max: min[info.Type] + info.Probability,
+ Min: minMap[info.Type],
+ Max: minMap[info.Type] + info.Probability,
}
- min[info.Type] += info.Probability
+ minMap[info.Type] += info.Probability
}
// }()
}
@@ -245,7 +238,7 @@ func (sql *fishdb) updateFishInfo(uid int64, number int) (residue int, err error
if err != nil {
return 0, err
}
- _ = sql.db.Find("fishState", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ _ = sql.db.Find("fishState", &userInfo, "WHERE ID = ?", uid)
if time.Unix(userInfo.Duration, 0).Day() != time.Now().Day() {
userInfo.Fish = 0
userInfo.Duration = time.Now().Unix()
@@ -278,7 +271,7 @@ func (sql *fishdb) updateCurseFor(uid int64, info string, number int) (err error
changeCheck := false
add := 0
buffName := "宝藏诅咒"
- _ = sql.db.Find("fishState", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ _ = sql.db.Find("fishState", &userInfo, "WHERE ID = ?", uid)
if info == "fish" {
userInfo.Bless += number
for userInfo.Bless >= 75 {
@@ -306,7 +299,7 @@ func (sql *fishdb) updateCurseFor(uid int64, info string, number int) (err error
Name: buffName,
Type: "treasure",
}
- _ = sql.db.Find(table, &thing, "where Name = '"+buffName+"'")
+ _ = sql.db.Find(table, &thing, "WHERE Name = ?", buffName)
thing.Number += add
return sql.db.Insert(table, &thing)
}
@@ -325,10 +318,10 @@ func (sql *fishdb) checkEquipFor(uid int64) (ok bool, err error) {
if err != nil {
return false, err
}
- if !sql.db.CanFind("fishState", "where ID = "+strconv.FormatInt(uid, 10)) {
+ if !sql.db.CanFind("fishState", "WHERE ID = ?", uid) {
return true, nil
}
- err = sql.db.Find("fishState", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ err = sql.db.Find("fishState", &userInfo, "WHERE ID = ?", uid)
if err != nil {
return false, err
}
@@ -346,10 +339,7 @@ func (sql *fishdb) setEquipFor(uid int64) (err error) {
if err != nil {
return err
}
- _ = sql.db.Find("fishState", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
- if err != nil {
- return err
- }
+ _ = sql.db.Find("fishState", &userInfo, "WHERE ID = ?", uid)
userInfo.Equip++
return sql.db.Insert("fishState", &userInfo)
}
@@ -362,10 +352,10 @@ func (sql *fishdb) getUserEquip(uid int64) (userInfo equip, err error) {
if err != nil {
return
}
- if !sql.db.CanFind("equips", "where ID = "+strconv.FormatInt(uid, 10)) {
+ if !sql.db.CanFind("equips", "WHERE ID = ?", uid) {
return
}
- err = sql.db.Find("equips", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ err = sql.db.Find("equips", &userInfo, "WHERE ID = ?", uid)
return
}
@@ -378,7 +368,7 @@ func (sql *fishdb) updateUserEquip(userInfo equip) (err error) {
return
}
if userInfo.Durable == 0 {
- return sql.db.Del("equips", "where ID = "+strconv.FormatInt(userInfo.ID, 10))
+ return sql.db.Del("equips", "WHERE ID = ?", userInfo.ID)
}
return sql.db.Insert("equips", &userInfo)
}
@@ -400,29 +390,29 @@ func (sql *fishdb) pickFishFor(uid int64, number int) (fishNames map[string]int,
if count == 0 {
return
}
- if !sql.db.CanFind(name, "where Type is 'fish'") {
+ if !sql.db.CanFind(name, "WHERE Type = 'fish'") {
return
}
fishInfo := article{}
k := 0
- for i := number * 2; i > 0 && k < len(fishList); {
- _ = sql.db.Find(name, &fishInfo, "where Name is '"+fishList[k]+"'")
+ for i := number; i > 0 && k < len(fishList); {
+ _ = sql.db.Find(name, &fishInfo, "WHERE Name = ?", fishList[k])
if fishInfo.Number <= 0 {
k++
continue
}
if fishInfo.Number < i {
k++
- fishInfo.Number = 0
i -= fishInfo.Number
fishNames[fishInfo.Name] += fishInfo.Number
+ fishInfo.Number = 0
} else {
fishNames[fishInfo.Name] += i
fishInfo.Number -= i
i = 0
}
if fishInfo.Number <= 0 {
- err = sql.db.Del(name, "where Duration = "+strconv.FormatInt(fishInfo.Duration, 10))
+ err = sql.db.Del(name, "WHERE Duration = ?", fishInfo.Duration)
} else {
err = sql.db.Insert(name, &fishInfo)
}
@@ -477,13 +467,13 @@ func (sql *fishdb) getUserThingInfo(uid int64, thing string) (thingInfos []artic
if count == 0 {
return
}
- if !sql.db.CanFind(name, "where Name = '"+thing+"'") {
+ if !sql.db.CanFind(name, "WHERE Name = ?", thing) {
return
}
- err = sql.db.FindFor(name, &userInfo, "where Name = '"+thing+"'", func() error {
+ err = sql.db.FindFor(name, &userInfo, "WHERE Name = ?", func() error {
thingInfos = append(thingInfos, userInfo)
return nil
- })
+ }, thing)
return
}
@@ -497,7 +487,7 @@ func (sql *fishdb) updateUserThingInfo(uid int64, userInfo article) (err error)
return
}
if userInfo.Number == 0 {
- return sql.db.Del(name, "where Duration = "+strconv.FormatInt(userInfo.Duration, 10))
+ return sql.db.Del(name, "WHERE Duration = ?", userInfo.Duration)
}
return sql.db.Insert(name, &userInfo)
}
@@ -519,14 +509,14 @@ func (sql *fishdb) getNumberFor(uid int64, thing string) (number int, err error)
if count == 0 {
return
}
- if !sql.db.CanFind(name, "where Name glob '*"+thing+"*'") {
+ if !sql.db.CanFind(name, "WHERE Name glob ?", "*"+thing+"*") {
return
}
info := article{}
- err = sql.db.FindFor(name, &info, "where Name glob '*"+thing+"*'", func() error {
+ err = sql.db.FindFor(name, &info, "WHERE Name glob ?", func() error {
number += info.Number
return nil
- })
+ }, "*"+thing+"*")
return
}
@@ -540,13 +530,13 @@ func (sql *fishdb) getUserTypeInfo(uid int64, thingType string) (thingInfos []ar
if err != nil {
return
}
- if !sql.db.CanFind(name, "where Type = '"+thingType+"'") {
+ if !sql.db.CanFind(name, "WHERE Type = ?", thingType) {
return
}
- err = sql.db.FindFor(name, &userInfo, "where Type = '"+thingType+"'", func() error {
+ err = sql.db.FindFor(name, &userInfo, "WHERE Type = ?", func() error {
thingInfos = append(thingInfos, userInfo)
return nil
- })
+ }, thingType)
return
}
@@ -562,8 +552,12 @@ func (sql *fishdb) refreshStroeInfo() (ok bool, err error) {
if err != nil {
return false, err
}
+ err = sql.db.Create("store", &store{})
+ if err != nil {
+ return false, err
+ }
lastTime := storeDiscount{}
- _ = sql.db.Find("stroeDiscount", &lastTime, "where Name = 'lastTime'")
+ _ = sql.db.Find("stroeDiscount", &lastTime, "WHERE Name = 'lastTime'")
refresh := false
timeNow := time.Now().Day()
if timeNow != lastTime.Discount {
@@ -586,12 +580,18 @@ func (sql *fishdb) refreshStroeInfo() (ok bool, err error) {
Name: name,
Discount: thingDiscount,
}
+ thingInfo := store{}
+ _ = sql.db.Find("store", &thingInfo, "WHERE Name = ?", name)
+ if thingInfo.Number > 150 {
+ // 控制价格浮动区间: -10%到10%
+ thing.Discount = 90 + rand.Intn(20)
+ }
err = sql.db.Insert("stroeDiscount", &thing)
if err != nil {
return
}
default:
- _ = sql.db.Find("stroeDiscount", &thing, "where Name = '"+name+"'")
+ _ = sql.db.Find("stroeDiscount", &thing, "WHERE Name = ?", name)
}
if thing.Discount != 0 {
discountList[name] = thing.Discount
@@ -600,21 +600,17 @@ func (sql *fishdb) refreshStroeInfo() (ok bool, err error) {
}
}
thing := store{}
- oldThing := []store{}
- _ = sql.db.FindFor("stroeDiscount", &thing, "where type = 'pole'", func() error {
+ var oldThing []store
+ _ = sql.db.FindFor("stroeDiscount", &thing, "WHERE type = 'pole'", func() error {
if time.Since(time.Unix(thing.Duration, 0)) > 24 {
oldThing = append(oldThing, thing)
}
return nil
})
for _, info := range oldThing {
- _ = sql.db.Del("stroeDiscount", "where Duration = "+strconv.FormatInt(info.Duration, 10))
+ _ = sql.db.Del("stroeDiscount", "WHERE Duration = ?", info.Duration)
}
if refresh {
- err = sql.db.Create("store", &store{})
- if err != nil {
- return
- }
// 每天调控1种鱼
fish := fishList[rand.Intn(len(fishList))]
thingInfo := store{
@@ -623,21 +619,25 @@ func (sql *fishdb) refreshStroeInfo() (ok bool, err error) {
Type: "fish",
Price: priceList[fish] * discountList[fish] / 100,
}
- _ = sql.db.Find("store", &thingInfo, "where Name = '"+fish+"'")
- thingInfo.Number += (100 - discountList[fish])
+ _ = sql.db.Find("store", &thingInfo, "WHERE Name = ?", fish)
+ thingInfo.Number += 100 - discountList[fish]
if thingInfo.Number < 1 {
thingInfo.Number = 100
}
_ = sql.db.Insert("store", &thingInfo)
- // 每天上架20本净化书
+ // 每天上架1木竿
thingInfo = store{
Duration: time.Now().Unix(),
- Name: "净化书",
- Type: "article",
- Price: priceList["净化书"] * discountList["净化书"] / 100,
+ Name: "初始木竿",
+ Type: "pole",
+ Price: priceList["木竿"] + priceList["木竿"]*discountList["木竿"]/100,
+ Other: "30/0/0/0",
+ }
+ _ = sql.db.Find("store", &thingInfo, "WHERE Name = '初始木竿'")
+ thingInfo.Number++
+ if thingInfo.Number > 5 {
+ thingInfo.Number = 1
}
- _ = sql.db.Find("store", &thingInfo, "where Name = '净化书'")
- thingInfo.Number = 20
_ = sql.db.Insert("store", &thingInfo)
}
return true, nil
@@ -682,13 +682,13 @@ func (sql *fishdb) getStoreThingInfo(thing string) (thingInfos []store, err erro
if count == 0 {
return
}
- if !sql.db.CanFind("store", "where Name = '"+thing+"'") {
+ if !sql.db.CanFind("store", "WHERE Name = ?", thing) {
return
}
- err = sql.db.FindFor("store", &thingInfo, "where Name = '"+thing+"'", func() error {
+ err = sql.db.FindFor("store", &thingInfo, "WHERE Name = ?", func() error {
thingInfos = append(thingInfos, thingInfo)
return nil
- })
+ }, thing)
return
}
@@ -707,10 +707,10 @@ func (sql *fishdb) checkStoreFor(thing store, number int) (ok bool, err error) {
if count == 0 {
return false, nil
}
- if !sql.db.CanFind("store", "where Duration = "+strconv.FormatInt(thing.Duration, 10)) {
+ if !sql.db.CanFind("store", "WHERE Duration = ?", thing.Duration) {
return false, nil
}
- err = sql.db.Find("store", &thing, "where Duration = "+strconv.FormatInt(thing.Duration, 10))
+ err = sql.db.Find("store", &thing, "WHERE Duration = ?", thing.Duration)
if err != nil {
return
}
@@ -729,7 +729,7 @@ func (sql *fishdb) updateStoreInfo(thingInfo store) (err error) {
return
}
if thingInfo.Number == 0 {
- return sql.db.Del("store", "where Duration = "+strconv.FormatInt(thingInfo.Duration, 10))
+ return sql.db.Del("store", "WHERE Duration = ?", thingInfo.Duration)
}
return sql.db.Insert("store", &thingInfo)
}
@@ -743,7 +743,7 @@ func (sql *fishdb) updateBuyTimeFor(uid int64, add int) (err error) {
if err != nil {
return err
}
- _ = sql.db.Find("buff", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ _ = sql.db.Find("buff", &userInfo, "WHERE ID = ?", uid)
userInfo.BuyTimes += add
if userInfo.BuyTimes > 20 {
userInfo.BuyTimes -= 20
@@ -762,7 +762,7 @@ func (sql *fishdb) useCouponAt(uid int64, times int) (int, error) {
if err != nil {
return useTimes, err
}
- _ = sql.db.Find("buff", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ _ = sql.db.Find("buff", &userInfo, "WHERE ID = ?", uid)
if userInfo.Coupon > 0 {
useTimes = math.Min(userInfo.Coupon, times)
userInfo.Coupon -= useTimes
@@ -770,27 +770,66 @@ func (sql *fishdb) useCouponAt(uid int64, times int) (int, error) {
return useTimes, sql.db.Insert("buff", &userInfo)
}
-// 检测上限
-func (sql *fishdb) checkCanSalesFor(uid int64, sales bool) (int, error) {
- residue := 0
+// 买卖上限检测
+func (sql *fishdb) checkCanSalesFor(uid int64, saleName string, salesNum int) (int, error) {
sql.Lock()
defer sql.Unlock()
userInfo := buffInfo{ID: uid}
err := sql.db.Create("buff", &userInfo)
if err != nil {
- return residue, err
+ return salesNum, err
}
- _ = sql.db.Find("buff", &userInfo, "where ID = "+strconv.FormatInt(uid, 10))
+ _ = sql.db.Find("buff", &userInfo, "WHERE ID = ?", uid)
if time.Now().Day() != time.Unix(userInfo.Duration, 0).Day() {
+ userInfo.Duration = time.Now().Unix()
userInfo.SalesPole = 0
userInfo.BuyTing = 0
+ err := sql.db.Insert("buff", &userInfo)
+ if err != nil {
+ return salesNum, err
+ }
}
- if sales && userInfo.SalesPole < 5 {
- residue = 5 - userInfo.SalesPole
- userInfo.SalesPole++
- } else if userInfo.BuyTing < 15 {
- residue = 15 - userInfo.SalesPole
+ if strings.Contains(saleName, "竿") {
+ if userInfo.SalesPole >= 10 {
+ salesNum = -1
+ }
+ } else if !checkIsWaste(saleName) {
+ maxSales := 30 - userInfo.BuyTing
+ if maxSales < 0 {
+ salesNum = 0
+ }
+ if salesNum > maxSales {
+ salesNum = maxSales
+ }
+ }
+
+ return salesNum, err
+}
+
+// 更新买卖鱼上限,假定sales变量已经在 checkCanSalesFor 进行了防护
+func (sql *fishdb) updateCanSalesFor(uid int64, saleName string, sales int) error {
+ sql.Lock()
+ defer sql.Unlock()
+ userInfo := buffInfo{ID: uid}
+ err := sql.db.Create("buff", &userInfo)
+ if err != nil {
+ return err
+ }
+ _ = sql.db.Find("buff", &userInfo, "WHERE ID = ?", uid)
+ if strings.Contains(saleName, "竿") {
userInfo.SalesPole++
+ } else if !checkIsWaste(saleName) {
+ userInfo.BuyTing += sales
+ }
+ return sql.db.Insert("buff", &userInfo)
+}
+
+// 检测物品是否是垃圾
+func checkIsWaste(thing string) bool {
+ for _, v := range wasteList {
+ if v == thing {
+ return true
+ }
}
- return residue, sql.db.Insert("buff", &userInfo)
+ return false
}
diff --git a/plugin/mcfish/pack.go b/plugin/mcfish/pack.go
index c873e391f3..61a0108bf4 100644
--- a/plugin/mcfish/pack.go
+++ b/plugin/mcfish/pack.go
@@ -42,9 +42,9 @@ func init() {
}
ctx.SendChain(message.ImageBytes(pic))
})
- engine.OnRegex(`^消除绑定诅咒(\d*)$`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ engine.OnRegex(`^消除(绑定|宝藏)诅咒(\d*)$`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
uid := ctx.Event.UserID
- number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[1])
+ number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[2])
if number == 0 {
number = 1
}
@@ -171,6 +171,31 @@ func init() {
msg = append(msg, message.Text("-----------"))
ctx.Send(msg)
})
+ engine.OnFullMatch("查看钓鱼规则", getdb).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
+ msg := "一款钓鱼模拟器\n----------指令----------\n" +
+ "- 钓鱼看板/钓鱼商店\n- 购买xxx\n- 购买xxx [数量]\n- 出售xxx\n- 出售xxx [数量]\n- 出售所有垃圾\n" +
+ "- 钓鱼背包\n- 装备[xx竿|三叉戟|美西螈]\n- 附魔[诱钓|海之眷顾]\n- 修复鱼竿\n- 合成[xx竿|三叉戟]\n- 消除[绑定|宝藏]诅咒\n- 消除[绑定|宝藏]诅咒 [数量]\n" +
+ "- 进行钓鱼\n- 进行n次钓鱼\n- " +
+ "当前装备概率明细\n" +
+ "规则V" + version + ":\n" +
+ "1.每日的商店价格是波动的!!如何最大化收益自己考虑一下喔\n" +
+ "2.装备信息:\n-> 木竿 : 耐久上限:30 均价:100 上钩概率:0.7%\n-> 铁竿 : 耐久上限:50 均价:300 上钩概率:0.2%\n-> 金竿 : 耐久上限:70 均价700 上钩概率:0.06%\n" +
+ "-> 钻石竿 : 耐久上限:100 均价1500 上钩概率:0.03%\n-> 下界合金竿 : 耐久上限:150 均价3100 上钩概率:0.01%\n-> 三叉戟 : 可使1次钓鱼视为3次钓鱼. 耐久上限:300 均价4000 只能合成、修复和交易\n" +
+ "3.附魔书信息:\n-> 诱钓 : 减少上钩时间. 均价:1000, 上钩概率:0.25%\n-> 海之眷顾 : 增加宝藏上钩概率. 均价:2500, 上钩概率:0.10%\n" +
+ "4.稀有物品:\n-> 唱片 : 出售物品时使用该物品使价格翻倍. 均价:3000, 上钩概率:0.01%\n" +
+ "-> 美西螈 : 可装备,获得隐形[钓鱼佬]buff,并让钓到除鱼竿和美西螈外的物品数量变成5,无耐久上限.不可修复/附魔,每次钓鱼消耗3条鱼. 均价:3000, 上钩概率:0.01%\n" +
+ "-> 海豚 : 使空竿概率变成垃圾概率. 均价:1000, 上钩概率:0.19%\n" +
+ "-> 宝藏诅咒 : 无法交易,每一层就会增加购买时10%价格和减少出售时10%价格(超过10层会变为倒贴钱). 上钩概率:0.25%\n-> 净化书 : 用于消除宝藏诅咒. 均价:5000, 上钩概率:0.19%\n" +
+ "5.鱼类信息:\n-> 鳕鱼 : 均价:10 上钩概率:0.69%\n-> 鲑鱼 : 均价:50 上钩概率:0.2%\n-> 热带鱼 : 均价:100 上钩概率:0.06%\n-> 河豚 : 均价:300 上钩概率:0.03%\n-> 鹦鹉螺 : 均价:500 上钩概率:0.01%\n-> 墨鱼 : 均价:500 上钩概率:0.01%\n" +
+ "6.垃圾:\n-> 均价:10 上钩概率:30%\n" +
+ "7.物品BUFF:\n-> 钓鱼佬 : 当背包名字含有'鱼'的物品数量超过100时激活,钓到物品概率提高至90%\n-> 修复大师 : 当背包鱼竿数量超过10时激活,修复物品时耐久百分百继承\n" +
+ "8.合成:\n-> 铁竿 : 3x木竿\n-> 金竿 : 3x铁竿\n-> 钻石竿 : 3x金竿\n-> 下界合金竿 : 3x钻石竿\n-> 三叉戟 : 3x下界合金竿\n注:合成成功率90%(包括梭哈),合成鱼竿的附魔等级=(附魔等级合/合成鱼竿数量)\n" +
+ "9.杂项:\n-> 无装备的情况下,每人最多可以购买3次100块钱的鱼竿,商店每日会上架1木竿\n-> 默认状态钓鱼上钩概率为60%(理论值!!!)\n-> 附魔的鱼竿会因附魔变得昂贵,每个附魔最高3级\n-> 三叉戟不算鱼竿,修复时可直接满耐久\n" +
+ "-> 鱼竿数量大于50的不能买东西;\n 鱼竿数量大于30的不能钓鱼;\n 每购/售10次鱼竿获得1层宝藏诅咒;\n 每购买20次物品将获得3次价格减半福利;\n 每钓鱼75次获得1本净化书;\n" +
+ " 每天可交易鱼竿10个,购买物品30件(垃圾除外)."
+
+ ctx.Send(msg)
+ })
}
func drawPackImage(uid int64, equipInfo equip, articles []article) (imagePicByte []byte, err error) {
diff --git a/plugin/mcfish/pole.go b/plugin/mcfish/pole.go
index c1ca4692a5..cad6938975 100644
--- a/plugin/mcfish/pole.go
+++ b/plugin/mcfish/pole.go
@@ -63,7 +63,10 @@ func init() {
msg = append(msg, message.Text("[", i, "] ", info.Equip, " : 耐", info.Durable, "/修", info.Maintenance,
"/诱", enchantLevel[info.Induce], "/眷顾", enchantLevel[info.Favor], "\n"))
}
- msg = append(msg, message.Text("————————\n输入对应序号进行装备,或回复“取消”取消"))
+ msg = append(msg, message.Text("————————\n"))
+ msg = append(msg, message.Text("- 输入对应序号进行装备\n"))
+ msg = append(msg, message.Text("- 输入“取消”终止本次操作\n"))
+ msg = append(msg, message.Text("- 鱼竿数量请使用钓鱼背包查看"))
ctx.Send(msg)
// 等待用户下一步选择
recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(取消|\d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
@@ -316,13 +319,15 @@ func init() {
case "诱钓":
equipInfo.Induce++
if equipInfo.Induce > 3 {
- equipInfo.Induce = 3
+ ctx.SendChain(message.Text("诱钓等级已达到上限,你浪费了一本附魔书"))
+ return
}
number = equipInfo.Induce
case "海之眷顾":
equipInfo.Favor++
if equipInfo.Favor > 3 {
- equipInfo.Favor = 3
+ ctx.SendChain(message.Text("海之眷顾等级已达到上限,你浪费了一本附魔书"))
+ return
}
number = equipInfo.Favor
default:
@@ -356,12 +361,12 @@ func init() {
ctx.SendChain(message.Text("[ERROR at pole.go.10]:", err))
return
}
- max := len(articles)
- if max < 3 {
+ maxCount := len(articles)
+ if maxCount < 3 {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("你的合成材料不足"))
return
}
- poles := make([]equip, 0, max)
+ poles := make([]equip, 0, maxCount)
for _, info := range articles {
poleInfo := strings.Split(info.Other, "/")
durable, _ := strconv.Atoi(poleInfo[0])
@@ -386,10 +391,13 @@ func init() {
msg = append(msg, message.Text("[", i, "] ", info.Equip, " : 耐", info.Durable, "/修", info.Maintenance,
"/诱", enchantLevel[info.Induce], "/眷顾", enchantLevel[info.Favor], "\n"))
}
- msg = append(msg, message.Text("————————\n输入3个序号进行合成(用空格分割),或回复“取消”取消"))
+ msg = append(msg, message.Text("————————\n"))
+ msg = append(msg, message.Text("- 输入3个序号进行合成(用空格分割)\n"))
+ msg = append(msg, message.Text("- 输入“取消”,终止本次合成\n"))
+ msg = append(msg, message.Text("- 输入“梭哈“,合成所有鱼竿"))
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, msg...))
// 等待用户下一步选择
- recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(取消|\d+ \d+ \d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(梭哈|取消|\d+ \d+ \d+)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
defer cancel()
for {
select {
@@ -410,6 +418,14 @@ func init() {
)
return
}
+ if nextcmd == "梭哈" {
+ // len(list)取3的倍数,表示能够用于合成鱼竿的最大数量,note:此处未对article.Number>1的情况做处理
+ for i := 3; i < (len(articles)/3)*3; i++ {
+ list = append(list, i)
+ }
+ check = true
+ break
+ }
chooseList := strings.Split(nextcmd, " ")
first, err := strconv.Atoi(chooseList[0])
if err != nil {
@@ -431,8 +447,8 @@ func init() {
ctx.SendChain(message.At(ctx.Event.UserID), message.Text("[0]请输入正确的序号\n", list))
continue
}
- if first > max || second > max || third > max {
- ctx.SendChain(message.At(ctx.Event.UserID), message.Text("[", max, "]请输入正确的序号\n", list))
+ if first >= maxCount || second >= maxCount || third >= maxCount {
+ ctx.SendChain(message.At(ctx.Event.UserID), message.Text("[", maxCount, "]请输入正确的序号\n", list))
continue
}
check = true
@@ -442,6 +458,7 @@ func init() {
}
}
}
+ upgradeNum := len(list)
favorLevel := 0
induceLevel := 0
for _, index := range list {
@@ -463,7 +480,7 @@ func init() {
)
return
}
- attribute := strconv.Itoa(durationList[thingName]) + "/0/" + strconv.Itoa(induceLevel/3) + "/" + strconv.Itoa(favorLevel/3)
+ attribute := strconv.Itoa(durationList[thingName]) + "/0/" + strconv.Itoa(induceLevel/upgradeNum) + "/" + strconv.Itoa(favorLevel/upgradeNum)
newthing := article{
Duration: time.Now().Unix(),
Type: "pole",
@@ -471,14 +488,19 @@ func init() {
Number: 1,
Other: attribute,
}
- err = dbdata.updateUserThingInfo(uid, newthing)
- if err != nil {
- ctx.SendChain(message.Text("[ERROR at pole.go.12]:", err))
- return
+ // 代码未对article.Number>1的情况做处理,直接生成多个Number=1的鱼竿
+ for i := 0; i < upgradeNum/3; i++ {
+ // 使用时间戳作为主键,增加固定值避免主键冲突
+ newthing.Duration += int64(i * 10)
+ err = dbdata.updateUserThingInfo(uid, newthing)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR at pole.go.12]:", err))
+ return
+ }
}
ctx.Send(
message.ReplyWithMessage(ctx.Event.MessageID,
- message.Text(thingName, "合成成功", list, "\n属性: ", attribute),
+ message.Text("成功合成:", upgradeNum/3, "个", thingName, "\n属性: ", attribute),
),
)
})
diff --git a/plugin/mcfish/store.go b/plugin/mcfish/store.go
index 85e90dc2a6..102d9477fe 100644
--- a/plugin/mcfish/store.go
+++ b/plugin/mcfish/store.go
@@ -70,21 +70,24 @@ func init() {
engine.OnRegex(`^出售(`+strings.Join(thingList, "|")+`)\s*(\d*)$`, getdb, refreshFish).SetBlock(true).Limit(limitSet).Handle(func(ctx *zero.Ctx) {
uid := ctx.Event.UserID
thingName := ctx.State["regex_matched"].([]string)[1]
+ number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[2])
+ if number == 0 || strings.Contains(thingName, "竿") {
+ number = 1
+ }
+
+ // 检测物品交易次数
if strings.Contains(thingName, "竿") {
- times, err := dbdata.checkCanSalesFor(uid, true)
+ number, err := dbdata.checkCanSalesFor(uid, thingName, number)
if err != nil {
- ctx.SendChain(message.Text("[ERROR at store.go.75]:", err))
+ ctx.SendChain(message.Text("[ERROR,查询购买资质失败]:", err))
return
}
- if times <= 0 {
- ctx.SendChain(message.Text("出售次数已达到上限,明天再来售卖吧"))
+ if number <= 0 {
+ ctx.SendChain(message.Text("一天只能交易10把鱼竿,明天再来售卖吧"))
return
}
}
- number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[2])
- if number == 0 || strings.Contains(thingName, "竿") {
- number = 1
- }
+
articles, err := dbdata.getUserThingInfo(uid, thingName)
if err != nil {
ctx.SendChain(message.Text("[ERROR at store.go.5]:", err))
@@ -107,7 +110,7 @@ func init() {
"[", i, "]", info.Name, " 数量: ", info.Number, "\n"))
}
}
- msg = append(msg, message.Text("————————\n输入对应序号进行装备,或回复“取消”取消"))
+ msg = append(msg, message.Text("————————\n输入对应序号进行出售,或回复“取消”取消"))
ctx.Send(msg)
// 等待用户下一步选择
sell := false
@@ -157,7 +160,9 @@ func init() {
maintenance, _ := strconv.Atoi(poleInfo[1])
induceLevel, _ := strconv.Atoi(poleInfo[2])
favorLevel, _ := strconv.Atoi(poleInfo[3])
- pice = (priceList[thingName] - (durationList[thingName] - durable) - maintenance*2 + induceLevel*600 + favorLevel*1800) * discountList[thingName] / 100
+ pice = (priceList[thingName] - (durationList[thingName] - durable) - maintenance*2 +
+ induceLevel*600*discountList["诱钓"]/100 +
+ favorLevel*1800*discountList["海之眷顾"]/100) * discountList[thingName] / 100
} else {
pice = priceList[thingName] * discountList[thingName] / 100
}
@@ -169,7 +174,7 @@ func init() {
for {
select {
case <-time.After(time.Second * 60):
- ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消钓鱼")))
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消出售")))
return
case e := <-recv:
nextcmd := e.Event.Message.String()
@@ -304,7 +309,13 @@ func init() {
logrus.Warnln(err)
}
}
- ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("出售成功,你赚到了", pice*number, msg)))
+ // 更新交易限制
+ err = dbdata.updateCanSalesFor(uid, thingName, number)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR,记录鱼类交易数量失败,此次交易不记录]:", err))
+ }
+
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("成功出售", thingName, ":", number, "个", ",你赚到了", pice*number, msg)))
})
engine.OnRegex(`^出售所有垃圾`, getdb, refreshFish).SetBlock(true).Limit(limitSet).Handle(func(ctx *zero.Ctx) {
uid := ctx.Event.UserID
@@ -333,7 +344,7 @@ func init() {
pice += (priceList[info.Name] * discountList[info.Name] / 100) * info.Number * 8 / 10
}
- ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("是否接受商店将以", pice, "收购全部垃圾", "?\n回答\"是\"或\"否\"")))
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("是否接受回收站将以", pice, "收购全部垃圾", "?\n回答\"是\"或\"否\"")))
// 等待用户下一步选择
recv, cancel1 := zero.NewFutureEvent("message", 999, false, zero.RegexRule(`^(是|否)$`), zero.CheckUser(ctx.Event.UserID)).Repeat()
defer cancel1()
@@ -341,7 +352,7 @@ func init() {
for {
select {
case <-time.After(time.Second * 60):
- ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消钓鱼")))
+ ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("等待超时,取消出售垃圾")))
return
case e := <-recv:
nextcmd := e.Event.Message.String()
@@ -375,10 +386,21 @@ func init() {
return
}
}
+ err = wallet.InsertWalletOf(uid, pice)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR,出售垃圾失败,回收站卷款跑路了]:", err))
+ return
+ }
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("出售成功,你赚到了", pice, msg)))
})
- engine.OnRegex(`^购买(`+strings.Join(thingList, "|")+`)\s*(\d*)$`, getdb, refreshFish).SetBlock(true).Limit(limitSet).Handle(func(ctx *zero.Ctx) {
+ engine.OnRegex(`^购买(`+strings.Join(thingList, "|")+`|初始木竿)\s*(\d*)$`, getdb, refreshFish).SetBlock(true).Limit(limitSet).Handle(func(ctx *zero.Ctx) {
uid := ctx.Event.UserID
+ thingName := ctx.State["regex_matched"].([]string)[1]
+ number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[2])
+ if number == 0 || strings.Contains(thingName, "竿") {
+ number = 1
+ }
+
numberOfPole, err := dbdata.getNumberFor(uid, "竿")
if err != nil {
ctx.SendChain(message.Text("[ERROR at store.go.9.3]:", err))
@@ -388,20 +410,24 @@ func init() {
ctx.SendChain(message.Text("你有", numberOfPole, "支鱼竿,大于50支的玩家不允许购买东西"))
return
}
- buytimes, err := dbdata.checkCanSalesFor(uid, false)
+
+ // 检测物品交易次数
+ number, err = dbdata.checkCanSalesFor(uid, thingName, number)
if err != nil {
ctx.SendChain(message.Text("[ERROR at store.go.75]:", err))
return
}
- if buytimes <= 0 {
- ctx.SendChain(message.Text("出售次数已达到上限,明天再来购买吧"))
+ if number <= 0 {
+ var msg string
+ if strings.Contains(thingName, "竿") {
+ msg = "一天只能交易10把鱼竿,明天再来购买吧"
+ } else {
+ msg = "一天只能购买30次物品,明天再来吧~"
+ }
+ ctx.SendChain(message.Text(msg))
return
}
- thingName := ctx.State["regex_matched"].([]string)[1]
- number, _ := strconv.Atoi(ctx.State["regex_matched"].([]string)[2])
- if number == 0 {
- number = 1
- }
+
thingInfos, err := dbdata.getStoreThingInfo(thingName)
if err != nil {
ctx.SendChain(message.Text("[ERROR at store.go.11]:", err))
@@ -443,7 +469,12 @@ func init() {
maintenance, _ := strconv.Atoi(poleInfo[1])
induceLevel, _ := strconv.Atoi(poleInfo[2])
favorLevel, _ := strconv.Atoi(poleInfo[3])
- thingPice := (priceList[info.Name] - (durationList[info.Name] - durable) - maintenance*2 + induceLevel*600 + favorLevel*1800) * discountList[info.Name] / 100
+ thingPice := (priceList[info.Name] - (durationList[info.Name] - durable) - maintenance*2 +
+ induceLevel*600*discountList["诱钓"]/100 +
+ favorLevel*1800*discountList["海之眷顾"]/100) * discountList[info.Name] / 100
+ if strings.Contains(thingName, "初始木竿") {
+ thingPice = priceList["木竿"] + priceList["木竿"]*discountList["木竿"]/100
+ }
pice = append(pice, thingPice)
} else {
thingPice := priceList[info.Name] * discountList[info.Name] / 100
@@ -462,7 +493,7 @@ func init() {
"[", i, "]", info.Name, " 数量:", info.Number, " 价格:", pice[i], "\n"))
}
}
- msg = append(msg, message.Text("————————\n输入对应序号进行装备,或回复“取消”取消"))
+ msg = append(msg, message.Text("————————\n输入对应序号进行购买,或回复“取消”取消"))
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, msg...))
// 等待用户下一步选择
sell := false
@@ -590,6 +621,9 @@ func init() {
Number: 1,
Other: thing.Other,
}
+ if thingName == "初始木竿" {
+ newCommodity.Name = "木竿"
+ }
} else {
things, err1 := dbdata.getUserThingInfo(uid, thingName)
if err1 != nil {
@@ -617,6 +651,11 @@ func init() {
logrus.Warnln(err)
}
}
+ // 更新交易限制
+ err = dbdata.updateCanSalesFor(uid, thingName, number)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR,记录鱼类交易数量失败,此次交易不记录]:", err))
+ }
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("你用", price, "购买了", number, thingName)))
})
}
@@ -761,6 +800,9 @@ func drawStroeInfoImage(stroeInfo []store) (picImage image.Image, err error) {
induceLevel, _ := strconv.Atoi(poleInfo[2])
favorLevel, _ := strconv.Atoi(poleInfo[3])
pice = (priceList[info.Name] - (durationList[info.Name] - durable) - maintenance*2 + induceLevel*600 + favorLevel*1800) * discountList[info.Name] / 100
+ if strings.Contains(name, "初始木竿") {
+ pice = priceList["木竿"] + priceList["木竿"]*discountList["木竿"]/100
+ }
} else {
pice = priceList[info.Name] * discountList[info.Name] / 100
}
diff --git a/plugin/midicreate/midicreate.go b/plugin/midicreate/midicreate.go
index 73cf1acf2c..c18e1013d2 100644
--- a/plugin/midicreate/midicreate.go
+++ b/plugin/midicreate/midicreate.go
@@ -229,7 +229,7 @@ func init() {
return path.Ext(ctx.Event.File.Name) == ".mid"
}).SetBlock(false).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
- fileURL := ctx.GetThisGroupFileUrl(ctx.Event.File.BusID, ctx.Event.File.ID)
+ fileURL := ctx.GetThisGroupFileURL(ctx.Event.File.BusID, ctx.Event.File.ID)
data, err := web.GetData(fileURL)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
@@ -242,12 +242,12 @@ func init() {
}
for i := 0; i < int(s.NumTracks()); i++ {
midStr := mid2txt(data, i)
+ fileName := strings.ReplaceAll(cachePath+"/"+ctx.Event.File.Name, ".mid", fmt.Sprintf("-%d.txt", i))
+ err := os.WriteFile(fileName, binary.StringToBytes(midStr), 0666)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
- fileName := strings.ReplaceAll(cachePath+"/"+ctx.Event.File.Name, ".mid", fmt.Sprintf("-%d.txt", i))
- _ = os.WriteFile(fileName, binary.StringToBytes(midStr), 0666)
ctx.UploadThisGroupFile(file.BOTPATH+"/"+fileName, filepath.Base(fileName), "")
}
})
@@ -255,7 +255,7 @@ func init() {
return path.Ext(ctx.Event.File.Name) == ".txt" && strings.Contains(ctx.Event.File.Name, "midi制作")
}).SetBlock(false).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
- fileURL := ctx.GetThisGroupFileUrl(ctx.Event.File.BusID, ctx.Event.File.ID)
+ fileURL := ctx.GetThisGroupFileURL(ctx.Event.File.BusID, ctx.Event.File.ID)
data, err := web.GetData(fileURL)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
diff --git a/plugin/minecraftobserver/minecraftobserver.go b/plugin/minecraftobserver/minecraftobserver.go
new file mode 100644
index 0000000000..65b80b5dba
--- /dev/null
+++ b/plugin/minecraftobserver/minecraftobserver.go
@@ -0,0 +1,301 @@
+// Package minecraftobserver 通过mc服务器地址获取服务器状态信息并绘制图片发送到QQ群
+package minecraftobserver
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ zbpCtxExt "github.com/FloatTech/zbputils/ctxext"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+const (
+ name = "minecraftobserver"
+)
+
+var (
+ // 注册插件
+ engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ // 默认不启动
+ DisableOnDefault: false,
+ Brief: "Minecraft服务器状态查询/订阅",
+ // 详细帮助
+ Help: "- mc服务器状态 [服务器IP/URI]\n" +
+ "- mc服务器添加订阅 [服务器IP/URI]\n" +
+ "- mc服务器取消订阅 [服务器IP/URI]\n" +
+ "- mc服务器订阅拉取 (需要插件定时任务配合使用,全局只需要设置一个)" +
+ "-----------------------\n" +
+ "使用job插件设置定时, 例:" +
+ "记录在\"@every 1m\"触发的指令\n" +
+ "(机器人回答:您的下一条指令将被记录,在@@every 1m时触发)" +
+ "mc服务器订阅拉取",
+ // 插件数据存储路径
+ PrivateDataFolder: name,
+ }).ApplySingle(zbpCtxExt.DefaultSingle)
+)
+
+func init() {
+ // 状态查询
+ engine.OnRegex("^[mM][cC]服务器状态 (.+)$").SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ // 关键词查找
+ addr := ctx.State["regex_matched"].([]string)[1]
+ resp, err := getMinecraftServerStatus(addr)
+ if err != nil {
+ ctx.Send(message.Text("服务器状态获取失败... 错误信息: ", err))
+ return
+ }
+ status := resp.genServerSubscribeSchema(addr, 0)
+ textMsg, iconBase64 := status.generateServerStatusMsg()
+ var msg message.Message
+ if iconBase64 != "" {
+ msg = append(msg, message.Image(iconBase64))
+ }
+ msg = append(msg, message.Text(textMsg))
+ if id := ctx.Send(msg); id.ID() == 0 {
+ // logrus.Errorln(logPrefix + "Send failed")
+ return
+ }
+ })
+ // 添加订阅
+ engine.OnRegex(`^[mM][cC]服务器添加订阅\s*(.+)$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ // 关键词查找
+ addr := ctx.State["regex_matched"].([]string)[1]
+ status, err := getMinecraftServerStatus(addr)
+ if err != nil {
+ ctx.Send(message.Text("服务器信息初始化失败,请检查服务器是否可用!\n错误信息: ", err))
+ return
+ }
+ targetID, targetType := warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID)
+ err = dbInstance.newSubscribe(addr, targetID, targetType)
+ if err != nil {
+ ctx.Send(message.Text("订阅添加失败... 错误信息: ", err))
+ return
+ }
+ // 插入数据库(首条,需要更新状态)
+ err = dbInstance.updateServerStatus(status.genServerSubscribeSchema(addr, 0))
+ if err != nil {
+ ctx.Send(message.Text("服务器状态更新失败... 错误信息: ", err))
+ return
+ }
+ if sid := ctx.Send(message.Text(fmt.Sprintf("服务器 %s 订阅添加成功", addr))); sid.ID() == 0 {
+ // logrus.Errorln(logPrefix + "Send failed")
+ return
+ }
+ // 成功后立即发送一次状态
+ textMsg, iconBase64 := status.genServerSubscribeSchema(addr, 0).generateServerStatusMsg()
+ var msg message.Message
+ if iconBase64 != "" {
+ msg = append(msg, message.Image(iconBase64))
+ }
+ msg = append(msg, message.Text(textMsg))
+ if id := ctx.Send(msg); id.ID() == 0 {
+ // logrus.Errorln(logPrefix + "Send failed")
+ return
+ }
+ })
+ // 删除
+ engine.OnRegex(`^[mM][cC]服务器取消订阅\s*(.+)$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ addr := ctx.State["regex_matched"].([]string)[1]
+ // 通过群组id和服务器地址获取服务器状态
+ targetID, targetType := warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID)
+ err := dbInstance.deleteSubscribe(addr, targetID, targetType)
+ if err != nil {
+ ctx.Send(message.Text("取消订阅失败...", fmt.Sprintf("错误信息: %v", err)))
+ return
+ }
+ ctx.Send(message.Text("取消订阅成功"))
+ })
+ // 查看当前渠道的所有订阅
+ engine.OnRegex(`^[mM][cC]服务器订阅列表$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ subList, err := dbInstance.getSubscribesByTarget(warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID))
+ if err != nil {
+ ctx.Send(message.Text("获取订阅列表失败... 错误信息: ", err))
+ return
+ }
+ if len(subList) == 0 {
+ ctx.Send(message.Text("当前没有订阅哦"))
+ return
+ }
+ stringBuilder := strings.Builder{}
+ stringBuilder.WriteString("[订阅列表]\n")
+ for _, v := range subList {
+ stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", v.ServerAddr))
+ }
+ if sid := ctx.Send(message.Text(stringBuilder.String())); sid.ID() == 0 {
+ // logrus.Errorln(logPrefix + "Send failed")
+ return
+ }
+ })
+ // 查看全局订阅情况(仅限管理员私聊可用)
+ engine.OnRegex(`^[mM][cC]服务器全局订阅列表$`, zero.OnlyPrivate, zero.SuperUserPermission, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ subList, err := dbInstance.getAllSubscribes()
+ if err != nil {
+ ctx.Send(message.Text("获取全局订阅列表失败... 错误信息: ", err))
+ return
+ }
+ if len(subList) == 0 {
+ ctx.Send(message.Text("当前一个订阅都没有哦"))
+ return
+ }
+ userID := ctx.Event.UserID
+ userName := ctx.CardOrNickName(userID)
+ msg := make(message.Message, 0)
+
+ // 按照群组or用户分组来定
+ groupSubMap := make(map[int64][]serverSubscribe)
+ userSubMap := make(map[int64][]serverSubscribe)
+ for _, v := range subList {
+ switch v.TargetType {
+ case targetTypeGroup:
+ groupSubMap[v.TargetID] = append(groupSubMap[v.TargetID], v)
+ case targetTypeUser:
+ userSubMap[v.TargetID] = append(userSubMap[v.TargetID], v)
+ default:
+ }
+ }
+
+ // 群
+ for k, v := range groupSubMap {
+ stringBuilder := strings.Builder{}
+ stringBuilder.WriteString(fmt.Sprintf("[群 %d]存在以下订阅:\n", k))
+ for _, sub := range v {
+ stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", sub.ServerAddr))
+ }
+ msg = append(msg, message.CustomNode(userName, userID, stringBuilder.String()))
+ }
+ // 个人
+ for k, v := range userSubMap {
+ stringBuilder := strings.Builder{}
+ stringBuilder.WriteString(fmt.Sprintf("[用户 %d]存在以下订阅:\n", k))
+ for _, sub := range v {
+ stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", sub.ServerAddr))
+ }
+ msg = append(msg, message.CustomNode(userName, userID, stringBuilder.String()))
+ }
+ // 合并发送
+ ctx.SendPrivateForwardMessage(ctx.Event.UserID, msg)
+ })
+ // 状态变更通知,全局触发,逐个服务器检查,检查到变更则逐个发送通知
+ engine.OnRegex(`^[mM][cC]服务器订阅拉取$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ serverList, err := dbInstance.getAllSubscribes()
+ if err != nil {
+ su := zero.BotConfig.SuperUsers[0]
+ // 如果订阅列表获取失败,通知管理员
+ ctx.SendPrivateMessage(su, message.Text(logPrefix, "获取订阅列表失败..."))
+ return
+ }
+ // logrus.Debugln(logPrefix+"global get ", len(serverList), " subscribe(s)")
+ serverMap := make(map[string][]serverSubscribe)
+ for _, v := range serverList {
+ serverMap[v.ServerAddr] = append(serverMap[v.ServerAddr], v)
+ }
+ changedCount := 0
+ for subAddr, oneServerSubList := range serverMap {
+ // 查询当前存储的状态
+ storedStatus, sErr := dbInstance.getServerStatus(subAddr)
+ if sErr != nil {
+ // logrus.Errorln(logPrefix+fmt.Sprintf("getServerStatus ServerAddr(%s) error: ", subAddr), sErr)
+ continue
+ }
+ isChanged, changedNotifyMsg, sErr := singleServerScan(storedStatus)
+ if sErr != nil {
+ // logrus.Errorln(logPrefix+"singleServerScan error: ", sErr)
+ continue
+ }
+ if !isChanged {
+ continue
+ }
+ changedCount++
+ // 发送变化信息
+ for _, subInfo := range oneServerSubList {
+ time.Sleep(100 * time.Millisecond)
+ if subInfo.TargetType == targetTypeUser {
+ ctx.SendPrivateMessage(subInfo.TargetID, changedNotifyMsg)
+ } else if subInfo.TargetType == targetTypeGroup {
+ m, ok := control.Lookup(name)
+ if !ok {
+ continue
+ }
+ if !m.IsEnabledIn(subInfo.TargetID) {
+ continue
+ }
+ ctx.SendGroupMessage(subInfo.TargetID, changedNotifyMsg)
+ }
+ }
+ }
+ })
+}
+
+// singleServerScan 单个服务器状态扫描
+func singleServerScan(oldSubStatus *serverStatus) (changed bool, notifyMsg message.Message, err error) {
+ notifyMsg = make(message.Message, 0)
+ newSubStatus := &serverStatus{}
+ // 获取服务器状态 & 检查是否需要更新
+ rawServerStatus, err := getMinecraftServerStatus(oldSubStatus.ServerAddr)
+ if err != nil {
+ // logrus.Warnln(logPrefix+"getMinecraftServerStatus error: ", err)
+ err = nil
+ // 计数器没有超限,增加计数器并跳过
+ if cnt, ts := addPingServerUnreachableCounter(oldSubStatus.ServerAddr, time.Now()); cnt < pingServerUnreachableCounterThreshold &&
+ time.Since(ts) < pingServerUnreachableCounterTimeThreshold {
+ // logrus.Warnln(logPrefix+"server ", oldSubStatus.ServerAddr, " unreachable, counter: ", cnt, " ts:", ts)
+ return
+ }
+ // 不可达计数器已经超限,则更新服务器状态
+ // 深拷贝,设置PingDelay为不可达
+ newSubStatus = oldSubStatus.deepCopy()
+ newSubStatus.PingDelay = pingDelayUnreachable
+ } else {
+ newSubStatus = rawServerStatus.genServerSubscribeSchema(oldSubStatus.ServerAddr, oldSubStatus.ID)
+ }
+ if newSubStatus == nil {
+ // logrus.Errorln(logPrefix + "newSubStatus is nil")
+ return
+ }
+ // 检查是否有订阅信息变化
+ if oldSubStatus.isServerStatusSpecChanged(newSubStatus) {
+ // logrus.Warnf(logPrefix+"server subscribe spec changed: (%+v) -> (%+v)", oldSubStatus, newSubStatus)
+ changed = true
+ // 更新数据库
+ err = dbInstance.updateServerStatus(newSubStatus)
+ if err != nil {
+ // logrus.Errorln(logPrefix+"updateServerSubscribeStatus error: ", err)
+ return
+ }
+ // 纯文本信息
+ notifyMsg = append(notifyMsg, message.Text(formatSubStatusChangeText(oldSubStatus, newSubStatus)))
+ // 如果有图标变更
+ if oldSubStatus.FaviconMD5 != newSubStatus.FaviconMD5 {
+ // 有图标变更
+ notifyMsg = append(notifyMsg, message.Text("\n-----[图标变更]-----\n"))
+ // 旧图标
+ notifyMsg = append(notifyMsg, message.Text("[旧]\n"))
+ if oldSubStatus.FaviconRaw != "" {
+ notifyMsg = append(notifyMsg, message.Image(oldSubStatus.FaviconRaw.toBase64String()))
+ } else {
+ notifyMsg = append(notifyMsg, message.Text("(空)\n"))
+ }
+ // 新图标
+ notifyMsg = append(notifyMsg, message.Text("[新]\n"))
+ if newSubStatus.FaviconRaw != "" {
+ notifyMsg = append(notifyMsg, message.Image(newSubStatus.FaviconRaw.toBase64String()))
+ } else {
+ notifyMsg = append(notifyMsg, message.Text("(空)\n"))
+ }
+ }
+ notifyMsg = append(notifyMsg, message.Text("\n-------最新状态-------\n"))
+ // 服务状态
+ textMsg, iconBase64 := newSubStatus.generateServerStatusMsg()
+ if iconBase64 != "" {
+ notifyMsg = append(notifyMsg, message.Image(iconBase64))
+ }
+ notifyMsg = append(notifyMsg, message.Text(textMsg))
+ }
+ // 逻辑到达这里,说明状态已经变更 or 无变更且服务器可达,重置不可达计数器
+ resetPingServerUnreachableCounter(oldSubStatus.ServerAddr)
+ return
+}
diff --git a/plugin/minecraftobserver/minecraftobserver_test.go b/plugin/minecraftobserver/minecraftobserver_test.go
new file mode 100644
index 0000000000..aa7babc968
--- /dev/null
+++ b/plugin/minecraftobserver/minecraftobserver_test.go
@@ -0,0 +1,127 @@
+package minecraftobserver
+
+import (
+ "fmt"
+ "github.com/wdvxdr1123/ZeroBot/message"
+ "testing"
+)
+
+func Test_singleServerScan(t *testing.T) {
+ initErr := initializeDB("data/minecraftobserver/" + dbPath)
+ if initErr != nil {
+ t.Fatalf("initializeDB() error = %v", initErr)
+ }
+ if dbInstance == nil {
+ t.Fatalf("initializeDB() got = %v, want not nil", dbInstance)
+ }
+ t.Run("状态变更", func(t *testing.T) {
+ cleanTestData(t)
+ newSS1 := &serverStatus{
+ ServerAddr: "cn.nekoland.top",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "",
+ }
+ err := dbInstance.updateServerStatus(newSS1)
+ if err != nil {
+ t.Fatalf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("cn.nekoland.top", 123456, 1)
+ if err != nil {
+ t.Fatalf("getServerSubscribeByTargetGroupAndAddr() error = %v", err)
+ }
+ changed, msg, err := singleServerScan(newSS1)
+ if err != nil {
+ t.Fatalf("singleServerScan() error = %v", err)
+ }
+ if !changed {
+ t.Fatalf("singleServerScan() got = %v, want true", changed)
+ }
+ if len(msg) == 0 {
+ t.Fatalf("singleServerScan() got = %v, want not empty", msg)
+ }
+ fmt.Printf("msg: %v\n", msg)
+ })
+
+ t.Run("可达 -> 不可达", func(t *testing.T) {
+ cleanTestData(t)
+ newSS1 := &serverStatus{
+ ServerAddr: "dx.123213213123123.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "",
+ PingDelay: 123,
+ }
+ err := dbInstance.updateServerStatus(newSS1)
+ if err != nil {
+ t.Fatalf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("dx.123213213123123.net", 123456, 1)
+ if err != nil {
+ t.Fatalf("getServerSubscribeByTargetGroupAndAddr() error = %v", err)
+ }
+ var msg message.Message
+ changed, _, err := singleServerScan(newSS1)
+ if err != nil {
+ t.Fatalf("singleServerScan() error = %v", err)
+ }
+ if changed {
+ t.Fatalf("singleServerScan() got = %v, want false", changed)
+ }
+ // 第二次
+ changed, _, err = singleServerScan(newSS1)
+ if err != nil {
+ t.Fatalf("singleServerScan() error = %v", err)
+ }
+ if changed {
+ t.Fatalf("singleServerScan() got = %v, want false", changed)
+ }
+ // 第三次
+ changed, msg, err = singleServerScan(newSS1)
+ if err != nil {
+ t.Fatalf("singleServerScan() error = %v", err)
+ }
+ if !changed {
+ t.Fatalf("singleServerScan() got = %v, want true", changed)
+ }
+ if len(msg) == 0 {
+ t.Fatalf("singleServerScan() got = %v, want not empty", msg)
+ }
+ fmt.Printf("msg: %v\n", msg)
+
+ })
+
+ t.Run("不可达 -> 可达", func(t *testing.T) {
+ cleanTestData(t)
+ newSS1 := &serverStatus{
+ ServerAddr: "cn.nekoland.top",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "",
+ PingDelay: pingDelayUnreachable,
+ }
+ err := dbInstance.updateServerStatus(newSS1)
+ if err != nil {
+ t.Fatalf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("cn.nekoland.top", 123456, 1)
+ if err != nil {
+ t.Fatalf("newSubscribe() error = %v", err)
+ }
+ changed, msg, err := singleServerScan(newSS1)
+ if err != nil {
+ t.Fatalf("singleServerScan() error = %v", err)
+ }
+ if !changed {
+ t.Fatalf("singleServerScan() got = %v, want true", changed)
+ }
+ if len(msg) == 0 {
+ t.Fatalf("singleServerScan() got = %v, want not empty", msg)
+ }
+ fmt.Printf("msg: %v\n", msg)
+ })
+
+}
diff --git a/plugin/minecraftobserver/model.go b/plugin/minecraftobserver/model.go
new file mode 100644
index 0000000000..f4a937cf67
--- /dev/null
+++ b/plugin/minecraftobserver/model.go
@@ -0,0 +1,253 @@
+package minecraftobserver
+
+import (
+ "crypto/md5"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/Tnze/go-mc/chat"
+ "github.com/google/uuid"
+ "github.com/wdvxdr1123/ZeroBot/utils/helper"
+)
+
+// ====================
+// DB Schema
+
+// serverStatus 服务器状态
+type serverStatus struct {
+ // ID 主键
+ ID int64 `json:"id" gorm:"column:id;primary_key:pk_id;auto_increment;default:0"`
+ // 服务器地址
+ ServerAddr string `json:"server_addr" gorm:"column:server_addr;default:'';unique_index:udx_server_addr"`
+ // 服务器描述
+ Description string `json:"description" gorm:"column:description;default:null;type:CLOB"`
+ // 在线玩家
+ Players string `json:"players" gorm:"column:players;default:''"`
+ // 版本
+ Version string `json:"version" gorm:"column:version;default:''"`
+ // FaviconMD5 Favicon MD5
+ FaviconMD5 string `json:"favicon_md5" gorm:"column:favicon_md5;default:''"`
+ // FaviconRaw 原始数据
+ FaviconRaw icon `json:"favicon_raw" gorm:"column:favicon_raw;default:null;type:CLOB"`
+ // 延迟,不可达时为-1
+ PingDelay int64 `json:"ping_delay" gorm:"column:ping_delay;default:-1"`
+ // 更新时间
+ LastUpdate int64 `json:"last_update" gorm:"column:last_update;default:0"`
+}
+
+// serverSubscribe 订阅信息
+type serverSubscribe struct {
+ // ID 主键
+ ID int64 `json:"id" gorm:"column:id;primary_key:pk_id;auto_increment;default:0"`
+ // 服务器地址
+ ServerAddr string `json:"server_addr" gorm:"column:server_addr;default:'';unique_index:udx_ait"`
+ // 推送目标id
+ TargetID int64 `json:"target_id" gorm:"column:target_id;default:0;unique_index:udx_ait"`
+ // 类型 1:群组 2:个人
+ TargetType int64 `json:"target_type" gorm:"column:target_type;default:0;unique_index:udx_ait"`
+ // 更新时间
+ LastUpdate int64 `json:"last_update" gorm:"column:last_update;default:0"`
+}
+
+const (
+ // pingDelayUnreachable 不可达
+ pingDelayUnreachable = -1
+)
+
+// isServerStatusSpecChanged 检查是否有状态变化
+func (ss *serverStatus) isServerStatusSpecChanged(newStatus *serverStatus) (res bool) {
+ res = false
+ if ss == nil || newStatus == nil {
+ res = false
+ return
+ }
+ // 描述变化、版本变化、Favicon变化
+ if ss.Description != newStatus.Description || ss.Version != newStatus.Version || ss.FaviconMD5 != newStatus.FaviconMD5 {
+ res = true
+ return
+ }
+ // 状态由不可达变为可达 or 反之
+ if (ss.PingDelay == pingDelayUnreachable && newStatus.PingDelay != pingDelayUnreachable) ||
+ (ss.PingDelay != pingDelayUnreachable && newStatus.PingDelay == pingDelayUnreachable) {
+ res = true
+ return
+ }
+ return
+}
+
+// deepCopy 深拷贝
+func (ss *serverStatus) deepCopy() (dst *serverStatus) {
+ if ss == nil {
+ return
+ }
+ dst = &serverStatus{}
+ *dst = *ss
+ return
+}
+
+// generateServerStatusMsg 生成服务器状态消息
+func (ss *serverStatus) generateServerStatusMsg() (msg string, iconBase64 string) {
+ var msgBuilder strings.Builder
+ if ss == nil {
+ return
+ }
+ msgBuilder.WriteString(ss.Description)
+ msgBuilder.WriteString("\n")
+ msgBuilder.WriteString("服务器地址:")
+ msgBuilder.WriteString(ss.ServerAddr)
+ msgBuilder.WriteString("\n")
+ // 版本
+ msgBuilder.WriteString("版本:")
+ msgBuilder.WriteString(ss.Version)
+ msgBuilder.WriteString("\n")
+ // Ping
+ if ss.PingDelay < 0 {
+ msgBuilder.WriteString("Ping延迟:超时\n")
+ } else {
+ msgBuilder.WriteString("Ping延迟:")
+ msgBuilder.WriteString(fmt.Sprintf("%d 毫秒\n", ss.PingDelay))
+ msgBuilder.WriteString("在线人数:")
+ msgBuilder.WriteString(ss.Players)
+ }
+ // 图标
+ if ss.FaviconRaw != "" && ss.FaviconRaw.checkPNG() {
+ iconBase64 = ss.FaviconRaw.toBase64String()
+ }
+ msg = msgBuilder.String()
+ return
+}
+
+// DB Schema End
+
+// ====================
+// Ping & List Response DTO
+
+// serverPingAndListResp 服务器状态数据传输对象 From mc server response
+type serverPingAndListResp struct {
+ Description chat.Message
+ Players struct {
+ Max int
+ Online int
+ Sample []struct {
+ ID uuid.UUID
+ Name string
+ }
+ }
+ Version struct {
+ Name string
+ Protocol int
+ }
+ Favicon icon
+ Delay time.Duration
+}
+
+// icon should be a PNG image that is Base64 encoded
+// (without newlines: \n, new lines no longer work since 1.13)
+// and prepended with "data:image/png;base64,".
+type icon string
+
+// func (i icon) toImage() (icon image.Image, err error) {
+// const prefix = "data:image/png;base64,"
+// if !strings.HasPrefix(string(i), prefix) {
+// return nil, errors.Errorf("server icon should prepended with %s", prefix)
+// }
+// base64png := strings.TrimPrefix(string(i), prefix)
+// r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64png))
+// icon, err = png.Decode(r)
+// return
+//}
+
+// checkPNG 检查是否为PNG
+func (i icon) checkPNG() bool {
+ const prefix = "data:image/png;base64,"
+ return strings.HasPrefix(string(i), prefix)
+}
+
+// toBase64String 转换为base64字符串
+func (i icon) toBase64String() string {
+ return "base64://" + strings.TrimPrefix(string(i), "data:image/png;base64,")
+}
+
+// genServerSubscribeSchema 将DTO转换为DB Schema
+func (dto *serverPingAndListResp) genServerSubscribeSchema(addr string, id int64) *serverStatus {
+ if dto == nil {
+ return nil
+ }
+ faviconMD5 := md5.Sum(helper.StringToBytes(string(dto.Favicon)))
+ return &serverStatus{
+ ID: id,
+ ServerAddr: addr,
+ Description: dto.Description.ClearString(),
+ Version: dto.Version.Name,
+ Players: fmt.Sprintf("%d/%d", dto.Players.Online, dto.Players.Max),
+ FaviconMD5: hex.EncodeToString(faviconMD5[:]),
+ FaviconRaw: dto.Favicon,
+ PingDelay: dto.Delay.Milliseconds(),
+ LastUpdate: time.Now().Unix(),
+ }
+}
+
+// Ping & List Response DTO End
+// ====================
+
+// ====================
+// Biz Model
+const (
+ logPrefix = "[minecraft observer] "
+)
+
+// warpTargetIDAndType 转换消息信息到订阅的目标ID和类型
+func warpTargetIDAndType(groupID, userID int64) (int64, int64) {
+ // 订阅
+ var targetID int64
+ var targetType int64
+ if groupID == 0 {
+ targetType = targetTypeUser
+ targetID = userID
+ } else {
+ targetType = targetTypeGroup
+ targetID = groupID
+ }
+ return targetID, targetType
+}
+
+// formatSubStatusChangeText 格式化状态变更文本
+func formatSubStatusChangeText(oldStatus, newStatus *serverStatus) string {
+ var msgBuilder strings.Builder
+ if oldStatus == nil || newStatus == nil {
+ return ""
+ }
+ // 变更通知
+ msgBuilder.WriteString("[Minecraft服务器状态变更通知]\n")
+ // 地址
+ msgBuilder.WriteString(fmt.Sprintf("服务器地址: %v\n", oldStatus.ServerAddr))
+ // 描述
+ if oldStatus.Description != newStatus.Description {
+ msgBuilder.WriteString("\n-----[描述变更]-----\n")
+ msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v\n", oldStatus.Description))
+ msgBuilder.WriteString(fmt.Sprintf("[新]\n%v\n", newStatus.Description))
+ }
+ // 版本
+ if oldStatus.Version != newStatus.Version {
+ msgBuilder.WriteString("\n-----[版本变更]-----\n")
+ msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v\n", oldStatus.Version))
+ msgBuilder.WriteString(fmt.Sprintf("[新]\n%v\n", newStatus.Version))
+ }
+ // 状态由不可达变为可达,反之
+ if oldStatus.PingDelay == pingDelayUnreachable && newStatus.PingDelay != pingDelayUnreachable {
+ msgBuilder.WriteString("\n-----[Ping延迟]-----\n")
+ msgBuilder.WriteString("[旧]\n超时\n")
+ msgBuilder.WriteString(fmt.Sprintf("[新]\n%v毫秒\n", newStatus.PingDelay))
+ }
+ if oldStatus.PingDelay != pingDelayUnreachable && newStatus.PingDelay == pingDelayUnreachable {
+ msgBuilder.WriteString("\n-----[Ping延迟]-----\n")
+ msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v毫秒\n", oldStatus.PingDelay))
+ msgBuilder.WriteString("[新]\n超时\n")
+ }
+ return msgBuilder.String()
+}
+
+// Biz Model End
+// ====================
diff --git a/plugin/minecraftobserver/ping.go b/plugin/minecraftobserver/ping.go
new file mode 100644
index 0000000000..d691a56283
--- /dev/null
+++ b/plugin/minecraftobserver/ping.go
@@ -0,0 +1,63 @@
+package minecraftobserver
+
+import (
+ "encoding/json"
+ "time"
+
+ "github.com/RomiChan/syncx"
+ "github.com/Tnze/go-mc/bot"
+)
+
+var (
+ // pingServerUnreachableCounter Ping服务器不可达计数器,防止bot本体网络抖动导致误报
+ pingServerUnreachableCounter = syncx.Map[string, pingServerUnreachableCounterDef]{}
+ // 计数器阈值
+ pingServerUnreachableCounterThreshold = int64(3)
+ // 时间阈值
+ pingServerUnreachableCounterTimeThreshold = time.Minute * 30
+)
+
+type pingServerUnreachableCounterDef struct {
+ count int64
+ firstUnreachableTime time.Time
+}
+
+func addPingServerUnreachableCounter(addr string, ts time.Time) (int64, time.Time) {
+ key := addr
+ get, ok := pingServerUnreachableCounter.Load(key)
+ if !ok {
+ pingServerUnreachableCounter.Store(key, pingServerUnreachableCounterDef{
+ count: 1,
+ firstUnreachableTime: ts,
+ })
+ return 1, ts
+ }
+ // 存在则更新,时间戳不变
+ pingServerUnreachableCounter.Store(key, pingServerUnreachableCounterDef{
+ count: get.count + 1,
+ firstUnreachableTime: get.firstUnreachableTime,
+ })
+ return get.count + 1, get.firstUnreachableTime
+}
+
+func resetPingServerUnreachableCounter(addr string) {
+ key := addr
+ pingServerUnreachableCounter.Delete(key)
+}
+
+// getMinecraftServerStatus 获取Minecraft服务器状态
+func getMinecraftServerStatus(addr string) (*serverPingAndListResp, error) {
+ var s serverPingAndListResp
+ resp, delay, err := bot.PingAndListTimeout(addr, time.Second*5)
+ if err != nil {
+ // logrus.Errorln(logPrefix+"PingAndList error: ", err)
+ return nil, err
+ }
+ err = json.Unmarshal(resp, &s)
+ if err != nil {
+ // logrus.Errorln(logPrefix+"Parse json response fail: ", err)
+ return nil, err
+ }
+ s.Delay = delay
+ return &s, nil
+}
diff --git a/plugin/minecraftobserver/ping_test.go b/plugin/minecraftobserver/ping_test.go
new file mode 100644
index 0000000000..8f44078935
--- /dev/null
+++ b/plugin/minecraftobserver/ping_test.go
@@ -0,0 +1,27 @@
+package minecraftobserver
+
+import (
+ "fmt"
+ "testing"
+)
+
+func Test_PingListInfo(t *testing.T) {
+ t.Run("normal", func(t *testing.T) {
+ resp, err := getMinecraftServerStatus("cn.nekoland.top")
+ if err != nil {
+ t.Fatalf("getMinecraftServerStatus() error = %v", err)
+ }
+ msg, iconBase64 := resp.genServerSubscribeSchema("cn.nekoland.top", 123456).generateServerStatusMsg()
+ fmt.Printf("msg: %v\n", msg)
+ fmt.Printf("iconBase64: %v\n", iconBase64)
+ })
+ t.Run("不可达", func(t *testing.T) {
+ ss, err := getMinecraftServerStatus("dx.123213213123123.net")
+ if err == nil {
+ t.Fatalf("getMinecraftServerStatus() error = %v", err)
+ }
+ if ss != nil {
+ t.Fatalf("getMinecraftServerStatus() got = %v, want nil", ss)
+ }
+ })
+}
diff --git a/plugin/minecraftobserver/store.go b/plugin/minecraftobserver/store.go
new file mode 100644
index 0000000000..f48cca1c9d
--- /dev/null
+++ b/plugin/minecraftobserver/store.go
@@ -0,0 +1,220 @@
+package minecraftobserver
+
+import (
+ "errors"
+ "os"
+ "sync"
+ "time"
+
+ fcext "github.com/FloatTech/floatbox/ctxext"
+ "github.com/jinzhu/gorm"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+const (
+ dbPath = "minecraft_observer"
+
+ targetTypeGroup = 1
+ targetTypeUser = 2
+)
+
+var (
+ // 数据库连接失败
+ errDBConn = errors.New("数据库连接失败")
+ // 参数错误
+ errParam = errors.New("参数错误")
+)
+
+type db struct {
+ sdb *gorm.DB
+ statusLock sync.RWMutex
+ subscribeLock sync.RWMutex
+}
+
+// initializeDB 初始化数据库
+func initializeDB(dbpath string) error {
+ if _, err := os.Stat(dbpath); err != nil || os.IsNotExist(err) {
+ // 生成文件
+ f, err := os.Create(dbpath)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ }
+ gdb, err := gorm.Open("sqlite3", dbpath)
+ if err != nil {
+ // logrus.Errorln(logPrefix+"initializeDB ERROR: ", err)
+ return err
+ }
+ gdb.AutoMigrate(&serverStatus{}, &serverSubscribe{})
+ dbInstance = &db{
+ sdb: gdb,
+ statusLock: sync.RWMutex{},
+ subscribeLock: sync.RWMutex{},
+ }
+ return nil
+}
+
+var (
+ // dbInstance 数据库实例
+ dbInstance *db
+ // 开启并检查数据库链接
+ getDB = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
+ var err = initializeDB(engine.DataFolder() + dbPath)
+ if err != nil {
+ // logrus.Errorln(logPrefix+"initializeDB ERROR: ", err)
+ ctx.SendChain(message.Text("[mc-ob] ERROR: ", err))
+ return false
+ }
+ return true
+ })
+)
+
+// 通过群组id和服务器地址获取状态
+func (d *db) getServerStatus(addr string) (*serverStatus, error) {
+ if d == nil {
+ return nil, errDBConn
+ }
+ if addr == "" {
+ return nil, errParam
+ }
+ var ss serverStatus
+ if err := d.sdb.Model(&ss).Where("server_addr = ?", addr).First(&ss).Error; err != nil {
+ // logrus.Errorln(logPrefix+"getServerStatus ERROR: ", err)
+ return nil, err
+ }
+ return &ss, nil
+}
+
+// 更新服务器状态
+func (d *db) updateServerStatus(ss *serverStatus) (err error) {
+ if d == nil {
+ return errDBConn
+ }
+ d.statusLock.Lock()
+ defer d.statusLock.Unlock()
+ if ss == nil || ss.ServerAddr == "" {
+ return errParam
+ }
+ ss.LastUpdate = time.Now().Unix()
+ ss2 := ss.deepCopy()
+ if err = d.sdb.Where(&serverStatus{ServerAddr: ss.ServerAddr}).Assign(ss2).FirstOrCreate(ss).Debug().Error; err != nil {
+ // logrus.Errorln(logPrefix, fmt.Sprintf("updateServerStatus %v ERROR: %v", ss, err))
+ return
+ }
+ return
+}
+
+func (d *db) delServerStatus(addr string) (err error) {
+ if d == nil {
+ return errDBConn
+ }
+ if addr == "" {
+ return errParam
+ }
+ d.statusLock.Lock()
+ defer d.statusLock.Unlock()
+ if err = d.sdb.Where("server_addr = ?", addr).Delete(&serverStatus{}).Error; err != nil {
+ // logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
+ return
+ }
+ return
+}
+
+// 新增订阅
+func (d *db) newSubscribe(addr string, targetID, targetType int64) (err error) {
+ if d == nil {
+ return errDBConn
+ }
+ if targetID == 0 || (targetType != 1 && targetType != 2) {
+ // logrus.Errorln(logPrefix+"newSubscribe ERROR: 参数错误 ", targetID, " ", targetType)
+ return errParam
+ }
+ d.subscribeLock.Lock()
+ defer d.subscribeLock.Unlock()
+ // 如果已经存在,需要报错
+ existedRec := &serverSubscribe{}
+ err = d.sdb.Model(&serverSubscribe{}).Where("server_addr = ? and target_id = ? and target_type = ?", addr, targetID, targetType).First(existedRec).Error
+ if err != nil && !gorm.IsRecordNotFoundError(err) {
+ // logrus.Errorln(logPrefix+"newSubscribe ERROR: ", err)
+ return
+ }
+ if existedRec.ID != 0 {
+ return errors.New("已经存在的订阅")
+ }
+ ss := &serverSubscribe{
+ ServerAddr: addr,
+ TargetID: targetID,
+ TargetType: targetType,
+ LastUpdate: time.Now().Unix(),
+ }
+ if err = d.sdb.Model(&ss).Create(ss).Error; err != nil {
+ // logrus.Errorln(logPrefix+"newSubscribe ERROR: ", err)
+ return
+ }
+ return
+}
+
+// 删除订阅
+func (d *db) deleteSubscribe(addr string, targetID int64, targetType int64) (err error) {
+ if d == nil {
+ return errDBConn
+ }
+ if addr == "" || targetID == 0 || targetType == 0 {
+ return errParam
+ }
+ d.subscribeLock.Lock()
+ defer d.subscribeLock.Unlock()
+ // 检查是否存在
+ if err = d.sdb.Model(&serverSubscribe{}).Where("server_addr = ? and target_id = ? and target_type = ?", addr, targetID, targetType).First(&serverSubscribe{}).Error; err != nil {
+ if gorm.IsRecordNotFoundError(err) {
+ return errors.New("未找到订阅")
+ }
+ // logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
+ return
+ }
+
+ if err = d.sdb.Where("server_addr = ? and target_id = ? and target_type = ?", addr, targetID, targetType).Delete(&serverSubscribe{}).Error; err != nil {
+ // logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
+ return
+ }
+
+ // 扫描是否还有订阅,如果没有则删除服务器状态
+ var cnt int
+ err = d.sdb.Model(&serverSubscribe{}).Where("server_addr = ?", addr).Count(&cnt).Error
+ if err != nil {
+ // logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
+ return
+ }
+ if cnt == 0 {
+ _ = d.delServerStatus(addr)
+ }
+ return
+}
+
+// 获取所有订阅
+func (d *db) getAllSubscribes() (subs []serverSubscribe, err error) {
+ if d == nil {
+ return nil, errDBConn
+ }
+ subs = []serverSubscribe{}
+ if err = d.sdb.Find(&subs).Error; err != nil {
+ // logrus.Errorln(logPrefix+"getAllSubscribes ERROR: ", err)
+ return
+ }
+ return
+}
+
+// 获取渠道对应的订阅列表
+func (d *db) getSubscribesByTarget(targetID, targetType int64) (subs []serverSubscribe, err error) {
+ if d == nil {
+ return nil, errDBConn
+ }
+ subs = []serverSubscribe{}
+ if err = d.sdb.Model(&serverSubscribe{}).Where("target_id = ? and target_type = ?", targetID, targetType).Find(&subs).Error; err != nil {
+ // logrus.Errorln(logPrefix+"getSubscribesByTarget ERROR: ", err)
+ return
+ }
+ return
+}
diff --git a/plugin/minecraftobserver/store_test.go b/plugin/minecraftobserver/store_test.go
new file mode 100644
index 0000000000..d96c82d132
--- /dev/null
+++ b/plugin/minecraftobserver/store_test.go
@@ -0,0 +1,317 @@
+package minecraftobserver
+
+import (
+ "errors"
+ "fmt"
+ "github.com/jinzhu/gorm"
+ "testing"
+)
+
+func cleanTestData(t *testing.T) {
+ err := dbInstance.sdb.Delete(&serverStatus{}).Where("id > 0").Error
+ if err != nil {
+ t.Fatalf("cleanTestData() error = %v", err)
+ }
+ err = dbInstance.sdb.Delete(&serverSubscribe{}).Where("id > 0").Error
+ if err != nil {
+ t.Fatalf("cleanTestData() error = %v", err)
+ }
+}
+
+func Test_DAO(t *testing.T) {
+ initErr := initializeDB("data/minecraftobserver/" + dbPath)
+ if initErr != nil {
+ t.Fatalf("initializeDB() error = %v", initErr)
+ }
+ if dbInstance == nil {
+ t.Fatalf("initializeDB() got = %v, want not nil", dbInstance)
+ }
+ t.Run("insert", func(t *testing.T) {
+ cleanTestData(t)
+ newSS1 := &serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ }
+ newSS2 := &serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.8",
+ FaviconMD5: "1234567",
+ }
+ err := dbInstance.updateServerStatus(newSS1)
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.updateServerStatus(newSS2)
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+
+ // check insert
+ queryResult, err := dbInstance.getServerStatus("dx.zhaomc.net")
+ if err != nil {
+ t.Fatalf("getServerStatus() error = %v", err)
+ }
+ if queryResult == nil {
+ t.Fatalf("getServerStatus() got = %v, want not nil", queryResult)
+ }
+ if queryResult.Version != "1.16.8" {
+ t.Fatalf("getServerStatus() got = %v, want 1.16.8", queryResult.Version)
+ }
+
+ err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeUser)
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ // check insert
+ res, err := dbInstance.getAllSubscribes()
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ if len(res) != 2 {
+ t.Fatalf("getAllServer() got = %v, want 2", len(res))
+ }
+ // 检查是否符合预期
+ if res[0].ServerAddr != "dx.zhaomc.net" {
+ t.Fatalf("getAllServer() got = %v, want dx.zhaomc.net", res[0].ServerAddr)
+ }
+ if res[0].TargetType != targetTypeGroup {
+ t.Fatalf("getAllServer() got = %v, want %v", res[0].TargetType, targetTypeGroup)
+ }
+ if res[1].ServerAddr != "dx.zhaomc.net" {
+ t.Fatalf("getAllServer() got = %v, want dx.zhaomc.net", res[1].ServerAddr)
+ }
+ if res[1].TargetType != targetTypeUser {
+ t.Fatalf("getAllServer() got = %v, want %v", res[1].TargetType, targetTypeUser)
+ }
+
+ // 顺带验证一下 byTarget
+ res2, err := dbInstance.getSubscribesByTarget(123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("getSubscribesByTarget() error = %v", err)
+ }
+ if len(res2) != 1 {
+ t.Fatalf("getSubscribesByTarget() got = %v, want 1", len(res2))
+ }
+
+ })
+ // 重复添加订阅
+ t.Run("insert dup", func(t *testing.T) {
+ cleanTestData(t)
+ newSS := &serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ }
+ err := dbInstance.updateServerStatus(newSS)
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err == nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ fmt.Printf("insert dup error: %+v", err)
+ })
+
+ t.Run("update", func(t *testing.T) {
+ cleanTestData(t)
+ newSS := &serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ }
+ err := dbInstance.updateServerStatus(newSS)
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.updateServerStatus(&serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "更新测试",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ })
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+ // check update
+ queryResult2, err := dbInstance.getServerStatus("dx.zhaomc.net")
+ if err != nil {
+ t.Errorf("getAllServer() error = %v", err)
+ }
+ if queryResult2.Description != "更新测试" {
+ t.Errorf("getAllServer() got = %v, want 更新测试", queryResult2.Description)
+ }
+ })
+ t.Run("delete status", func(t *testing.T) {
+ cleanTestData(t)
+ newSS := &serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ }
+ err := dbInstance.updateServerStatus(newSS)
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+ // check insert
+ queryResult, err := dbInstance.getServerStatus("dx.zhaomc.net")
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ if queryResult == nil {
+ t.Fatalf("getAllServer() got = %v, want not nil", queryResult)
+ }
+ err = dbInstance.delServerStatus("dx.zhaomc.net")
+ if err != nil {
+ t.Fatalf("deleteServerStatus() error = %v", err)
+ }
+ // check delete
+ _, err = dbInstance.getServerStatus("dx.zhaomc.net")
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+
+ })
+
+ // 删除订阅
+ t.Run("delete subscribe", func(t *testing.T) {
+ cleanTestData(t)
+ newSS := &serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ }
+ err := dbInstance.updateServerStatus(newSS)
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ err = dbInstance.deleteSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("deleteSubscribe() error = %v", err)
+ }
+ // check delete
+ _, err = dbInstance.getServerStatus("dx.zhaomc.net")
+ if !errors.Is(err, gorm.ErrRecordNotFound) {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ })
+
+ // 重复删除订阅
+ t.Run("delete subscribe dup", func(t *testing.T) {
+ cleanTestData(t)
+ err := dbInstance.updateServerStatus(&serverStatus{
+ ServerAddr: "dx.zhaomc.net",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ })
+ if err != nil {
+ t.Errorf("upsertServerStatus() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("newSubscribe() error = %v", err)
+ }
+
+ err = dbInstance.newSubscribe("dx.zhaomc.net123", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("newSubscribe() error = %v", err)
+ }
+ err = dbInstance.updateServerStatus(&serverStatus{
+ ServerAddr: "dx.zhaomc.net123",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ })
+ if err != nil {
+ t.Fatalf("updateServerStatus() error = %v", err)
+ }
+ err = dbInstance.newSubscribe("dx.zhaomc.net4567", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("newSubscribe() error = %v", err)
+ }
+ err = dbInstance.updateServerStatus(&serverStatus{
+ ServerAddr: "dx.zhaomc.net4567",
+ Description: "测试服务器",
+ Players: "1/20",
+ Version: "1.16.5",
+ FaviconMD5: "1234567",
+ })
+ if err != nil {
+ t.Fatalf("updateServerStatus() error = %v", err)
+ }
+
+ // 检查是不是3个
+ allSub, err := dbInstance.getAllSubscribes()
+ if err != nil {
+ t.Fatalf("getAllSubscribes() error = %v", err)
+ }
+ if len(allSub) != 3 {
+ t.Fatalf("getAllSubscribes() got = %v, want 3", len(allSub))
+ }
+ err = dbInstance.deleteSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err != nil {
+ t.Fatalf("deleteSubscribe() error = %v", err)
+ }
+ err = dbInstance.deleteSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
+ if err == nil {
+ t.Fatalf("deleteSubscribe() error = %v", err)
+ }
+ fmt.Println("delete dup error: ", err)
+
+ // 检查其他的没有被删
+ allSub, err = dbInstance.getAllSubscribes()
+ if err != nil {
+ t.Fatalf("getAllSubscribes() error = %v", err)
+ }
+ // 检查是否符合预期
+ if len(allSub) != 2 {
+ t.Fatalf("getAllSubscribes() got = %v, want 2", len(allSub))
+ }
+ // 状态
+ _, err = dbInstance.getServerStatus("dx.zhaomc.net")
+ if !gorm.IsRecordNotFoundError(err) {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ status1, err := dbInstance.getServerStatus("dx.zhaomc.net123")
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ status2, err := dbInstance.getServerStatus("dx.zhaomc.net4567")
+ if err != nil {
+ t.Fatalf("getAllServer() error = %v", err)
+ }
+ if status1 == nil || status2 == nil {
+ t.Fatalf("getAllServer() want not nil")
+ }
+
+ })
+}
diff --git a/plugin/moegoe/main.go b/plugin/moegoe/main.go
deleted file mode 100644
index fa8038edc1..0000000000
--- a/plugin/moegoe/main.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// Package moegoe 日韩中 VITS 模型拟声
-package moegoe
-
-import (
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "net/url"
-
- zero "github.com/wdvxdr1123/ZeroBot"
- "github.com/wdvxdr1123/ZeroBot/message"
-
- "github.com/FloatTech/AnimeAPI/tts/genshin"
- "github.com/FloatTech/floatbox/binary"
- "github.com/FloatTech/floatbox/file"
- ctrl "github.com/FloatTech/zbpctrl"
- "github.com/FloatTech/zbputils/control"
- "github.com/FloatTech/zbputils/ctxext"
-)
-
-var 原 = newapikeystore("./data/tts/o.txt")
-
-func init() {
- en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
- DisableOnDefault: false,
- Brief: "日韩中 VITS 模型拟声",
- Help: "- 让[空|荧|派蒙|纳西妲|阿贝多|温迪|枫原万叶|钟离|荒泷一斗|八重神子|艾尔海森|提纳里|迪希雅|卡维|宵宫|莱依拉|赛诺|诺艾尔|托马|凝光|莫娜|北斗|神里绫华|雷电将军|芭芭拉|鹿野院平藏|五郎|迪奥娜|凯亚|安柏|班尼特|琴|柯莱|夜兰|妮露|辛焱|珐露珊|魈|香菱|达达利亚|砂糖|早柚|云堇|刻晴|丽莎|迪卢克|烟绯|重云|珊瑚宫心海|胡桃|可莉|流浪者|久岐忍|神里绫人|甘雨|戴因斯雷布|优菈|菲谢尔|行秋|白术|九条裟罗|雷泽|申鹤|迪娜泽黛|凯瑟琳|多莉|坎蒂丝|萍姥姥|罗莎莉亚|留云借风真君|绮良良|瑶瑶|七七|奥兹|米卡|夏洛蒂|埃洛伊|博士|女士|大慈树王|三月七|娜塔莎|希露瓦|虎克|克拉拉|丹恒|希儿|布洛妮娅|瓦尔特|杰帕德|佩拉|姬子|艾丝妲|白露|星|穹|桑博|伦纳德|停云|罗刹|卡芙卡|彦卿|史瓦罗|螺丝咕姆|阿兰|银狼|素裳|丹枢|黑塔|景元|帕姆|可可利亚|半夏|符玄|公输师傅|奥列格|青雀|大毫|青镞|费斯曼|绿芙蓉|镜流|信使|丽塔|失落迷迭|缭乱星棘|伊甸|伏特加女孩|狂热蓝调|莉莉娅|萝莎莉娅|八重樱|八重霞|卡莲|第六夜想曲|卡萝尔|姬子|极地战刃|布洛妮娅|次生银翼|理之律者|真理之律者|迷城骇兔|希儿|魇夜星渊|黑希儿|帕朵菲莉丝|天元骑英|幽兰黛尔|德丽莎|月下初拥|朔夜观星|暮光骑士|明日香|李素裳|格蕾修|梅比乌斯|渡鸦|人之律者|爱莉希雅|爱衣|天穹游侠|琪亚娜|空之律者|终焉之律者|薪炎之律者|云墨丹心|符华|识之律者|维尔薇|始源之律者|芽衣|雷之律者|苏莎娜|阿波尼亚|陆景和|莫弈|夏彦|左然|标贝]说(中文)",
- }).ApplySingle(ctxext.DefaultSingle)
- en.OnRegex("^让(空|荧|派蒙|纳西妲|阿贝多|温迪|枫原万叶|钟离|荒泷一斗|八重神子|艾尔海森|提纳里|迪希雅|卡维|宵宫|莱依拉|赛诺|诺艾尔|托马|凝光|莫娜|北斗|神里绫华|雷电将军|芭芭拉|鹿野院平藏|五郎|迪奥娜|凯亚|安柏|班尼特|琴|柯莱|夜兰|妮露|辛焱|珐露珊|魈|香菱|达达利亚|砂糖|早柚|云堇|刻晴|丽莎|迪卢克|烟绯|重云|珊瑚宫心海|胡桃|可莉|流浪者|久岐忍|神里绫人|甘雨|戴因斯雷布|优菈|菲谢尔|行秋|白术|九条裟罗|雷泽|申鹤|迪娜泽黛|凯瑟琳|多莉|坎蒂丝|萍姥姥|罗莎莉亚|留云借风真君|绮良良|瑶瑶|七七|奥兹|米卡|夏洛蒂|埃洛伊|博士|女士|大慈树王|三月七|娜塔莎|希露瓦|虎克|克拉拉|丹恒|希儿|布洛妮娅|瓦尔特|杰帕德|佩拉|姬子|艾丝妲|白露|星|穹|桑博|伦纳德|停云|罗刹|卡芙卡|彦卿|史瓦罗|螺丝咕姆|阿兰|银狼|素裳|丹枢|黑塔|景元|帕姆|可可利亚|半夏|符玄|公输师傅|奥列格|青雀|大毫|青镞|费斯曼|绿芙蓉|镜流|信使|丽塔|失落迷迭|缭乱星棘|伊甸|伏特加女孩|狂热蓝调|莉莉娅|萝莎莉娅|八重樱|八重霞|卡莲|第六夜想曲|卡萝尔|姬子|极地战刃|布洛妮娅|次生银翼|理之律者|真理之律者|迷城骇兔|希儿|魇夜星渊|黑希儿|帕朵菲莉丝|天元骑英|幽兰黛尔|德丽莎|月下初拥|朔夜观星|暮光骑士|明日香|李素裳|格蕾修|梅比乌斯|渡鸦|人之律者|爱莉希雅|爱衣|天穹游侠|琪亚娜|空之律者|终焉之律者|薪炎之律者|云墨丹心|符华|识之律者|维尔薇|始源之律者|芽衣|雷之律者|苏莎娜|阿波尼亚|陆景和|莫弈|夏彦|左然|标贝)说([\\s\u4e00-\u9fa5\\pP]+)$").Limit(ctxext.LimitByGroup).SetBlock(true).
- Handle(func(ctx *zero.Ctx) {
- if 原.k == "" {
- return
- }
- text := ctx.State["regex_matched"].([]string)[2]
- name := ctx.State["regex_matched"].([]string)[1]
- rec := fmt.Sprintf(genshin.CNAPI, url.QueryEscape(name), url.QueryEscape(text), url.QueryEscape(原.k))
- b := md5.Sum(binary.StringToBytes(rec))
- fn := hex.EncodeToString(b[:])
- fp := "data/tts/" + fn
- if file.IsNotExist(fp) {
- if file.DownloadTo(rec, fp) != nil {
- return
- }
- }
- rec = "file:///" + file.BOTPATH + "/" + fp
- ctx.SendChain(message.Record(rec))
- })
-}
diff --git a/plugin/moegoe/model.go b/plugin/moegoe/model.go
deleted file mode 100644
index cd09b72acd..0000000000
--- a/plugin/moegoe/model.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package moegoe
-
-import (
- "os"
-
- "github.com/FloatTech/floatbox/binary"
- "github.com/FloatTech/floatbox/file"
-)
-
-type apikeystore struct {
- k string
- p string
-}
-
-func newapikeystore(p string) (s apikeystore) {
- s.p = p
- if file.IsExist(p) {
- data, err := os.ReadFile(p)
- if err == nil {
- s.k = binary.BytesToString(data)
- }
- }
- return
-}
diff --git a/plugin/movies/main.go b/plugin/movies/main.go
new file mode 100644
index 0000000000..b17cbb4631
--- /dev/null
+++ b/plugin/movies/main.go
@@ -0,0 +1,435 @@
+// Package movies 电影查询
+package movies
+
+import (
+ "encoding/json"
+ "image"
+ "net/http"
+ "os"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/FloatTech/floatbox/file"
+ "github.com/FloatTech/floatbox/web"
+ "github.com/FloatTech/gg"
+ "github.com/FloatTech/imgfactory"
+ "github.com/FloatTech/rendercard"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/img/text"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+const (
+ apiURL = "https://m.maoyan.com/ajax/"
+ ua = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36"
+)
+
+var (
+ mu sync.RWMutex
+ todayPic = make([][]byte, 2)
+ lasttime time.Time
+ en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "电影查询",
+ Help: "- 今日电影\n" +
+ "- 预售电影",
+ PrivateDataFolder: "movies",
+ })
+)
+
+func init() {
+ en.OnFullMatch("今日电影").SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ if todayPic != nil && time.Since(lasttime) < 12*time.Hour {
+ ctx.SendChain(message.ImageBytes(todayPic[0]))
+ return
+ }
+ lasttime = time.Now()
+ movieComingList, err := getMovieList("今日电影")
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:", err))
+ return
+ }
+ if len(movieComingList) == 0 {
+ ctx.SendChain(message.Text("没有今日电影"))
+ return
+ }
+ pic, err := drawOnListPic(movieComingList)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:", err))
+ return
+ }
+ todayPic[0] = pic
+ ctx.SendChain(message.ImageBytes(pic))
+ })
+ en.OnFullMatch("预售电影").SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ if todayPic[1] != nil && time.Since(lasttime) < 12*time.Hour {
+ ctx.SendChain(message.ImageBytes(todayPic[1]))
+ return
+ }
+ lasttime = time.Now()
+ movieComingList, err := getMovieList("预售电影")
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:", err))
+ return
+ }
+ if len(movieComingList) == 0 {
+ ctx.SendChain(message.Text("没有预售电影"))
+ return
+ }
+ pic, err := drawComListPic(movieComingList)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:", err))
+ return
+ }
+ todayPic[1] = pic
+ ctx.SendChain(message.ImageBytes(pic))
+ })
+}
+
+type movieInfo struct {
+ ID int64 `json:"id"` // 电影ID
+ Img string `json:"img"` // 海报
+
+ Nm string `json:"nm"` // 名称
+
+ Dir string `json:"dir"` // 导演
+ Star string `json:"star"` // 演员
+
+ OriLang string `json:"oriLang"` // 原语言
+ Cat string `json:"cat"` // 类型
+
+ Version string `json:"version"` // 电影格式
+ Rt string `json:"rt"` // 上映时间
+
+ ShowInfo string `json:"showInfo"` // 今日上映信息
+ ComingTitle string `json:"comingTitle"` // 预售信息
+
+ Sc float64 `json:"sc"` // 评分
+ Wish int64 `json:"wish"` // 观看人数
+ Watched int64 `json:"watched"` // 观看数
+}
+type movieOnList struct {
+ MovieList []movieInfo `json:"movieList"`
+}
+type comingList struct {
+ MovieList []movieInfo `json:"coming"`
+}
+type movieShow struct {
+ MovieInfo movieInfo `json:"detailMovie"`
+}
+
+type cardInfo struct {
+ Avatar image.Image
+ TopLeftText string
+ BottomLeftText []string
+ RightText string
+ Rank string
+}
+
+func getMovieList(mode string) (movieList []movieInfo, err error) {
+ var data []byte
+ if mode == "今日电影" {
+ data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"movieOnInfoList", "", "GET", ua, nil)
+ if err != nil {
+ return
+ }
+ var parsed movieOnList
+ err = json.Unmarshal(data, &parsed)
+ if err != nil {
+ return
+ }
+ movieList = parsed.MovieList
+ } else {
+ data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"comingList?token=", "", "GET", ua, nil)
+ if err != nil {
+ return
+ }
+ var parsed comingList
+ err = json.Unmarshal(data, &parsed)
+ if err != nil {
+ return
+ }
+ movieList = parsed.MovieList
+ }
+ if len(movieList) == 0 {
+ return
+ }
+ for i, info := range movieList {
+ movieID := strconv.FormatInt(info.ID, 10)
+ data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"detailmovie?movieId="+movieID, "", "GET", ua, nil)
+ if err != nil {
+ return
+ }
+ var movieInfo movieShow
+ err = json.Unmarshal(data, &movieInfo)
+ if err != nil {
+ return
+ }
+ if mode != "今日电影" {
+ movieInfo.MovieInfo.ComingTitle = movieList[i].ComingTitle
+ }
+ movieList[i] = movieInfo.MovieInfo
+ }
+ // 整理数据,进行排序
+ sort.Slice(movieList, func(i, j int) bool {
+ if movieList[i].Sc != movieList[j].Sc {
+ return movieList[i].Sc > movieList[j].Sc
+ }
+ if mode == "今日电影" {
+ return movieList[i].Watched > movieList[j].Watched
+ }
+ return movieList[i].Wish > movieList[j].Wish
+ })
+ return movieList, nil
+}
+func drawOnListPic(lits []movieInfo) (data []byte, err error) {
+ rankinfo := make([]*cardInfo, len(lits))
+
+ wg := &sync.WaitGroup{}
+ wg.Add(len(lits))
+ for i := 0; i < len(lits); i++ {
+ go func(i int) {
+ info := lits[i]
+ defer wg.Done()
+ img, err := avatar(&info)
+ if err != nil {
+ return
+ }
+ movieType := "2D"
+ if info.Version != "" {
+ movieType = info.Version
+ }
+ watched := ""
+ switch {
+ case info.Watched > 100000000:
+ watched = strconv.FormatFloat(float64(info.Watched)/100000000, 'f', 2, 64) + "亿"
+ case info.Watched > 10000:
+ watched = strconv.FormatFloat(float64(info.Watched)/10000, 'f', 2, 64) + "万"
+ default:
+ watched = strconv.FormatInt(info.Watched, 10)
+ }
+ rankinfo[i] = &cardInfo{
+ TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")",
+ BottomLeftText: []string{
+ "导演:" + info.Dir,
+ "演员:" + info.Star,
+ "标签:" + info.Cat,
+ "语言: " + info.OriLang + " 类型: " + movieType,
+ "上映时间: " + info.Rt,
+ },
+ RightText: watched + "人已看",
+ Avatar: img,
+ Rank: strconv.FormatFloat(info.Sc, 'f', 1, 64),
+ }
+ }(i)
+ }
+ wg.Wait()
+ fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
+ if err != nil {
+ return
+ }
+ img, err := drawRankingCard(fontbyte, "今日电影", rankinfo)
+ if err != nil {
+ return
+ }
+ data, err = imgfactory.ToBytes(img)
+ return
+}
+
+func drawComListPic(lits []movieInfo) (data []byte, err error) {
+ rankinfo := make([]*cardInfo, len(lits))
+
+ wg := &sync.WaitGroup{}
+ wg.Add(len(lits))
+ for i := 0; i < len(lits); i++ {
+ go func(i int) {
+ info := lits[i]
+ defer wg.Done()
+ img, err := avatar(&info)
+ if err != nil {
+ return
+ }
+ movieType := "2D"
+ if info.Version != "" {
+ movieType = info.Version
+ }
+ wish := ""
+ switch {
+ case info.Wish > 100000000:
+ wish = strconv.FormatFloat(float64(info.Wish)/100000000, 'f', 2, 64) + "亿"
+ case info.Wish > 10000:
+ wish = strconv.FormatFloat(float64(info.Wish)/10000, 'f', 2, 64) + "万"
+ default:
+ wish = strconv.FormatInt(info.Wish, 10)
+ }
+ rankinfo[i] = &cardInfo{
+ TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")",
+ BottomLeftText: []string{
+ "导演:" + info.Dir,
+ "演员:" + info.Star,
+ "标签:" + info.Cat,
+ "语言: " + info.OriLang + " 类型: " + movieType,
+ "上映时间: " + info.Rt + " 播放时间: " + info.ComingTitle,
+ },
+ RightText: wish + "人期待",
+ Avatar: img,
+ Rank: strconv.Itoa(i + 1),
+ }
+ }(i)
+ }
+ wg.Wait()
+ fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
+ if err != nil {
+ return
+ }
+ img, err := drawRankingCard(fontbyte, "预售电影", rankinfo)
+ if err != nil {
+ return
+ }
+ data, err = imgfactory.ToBytes(img)
+ return
+}
+
+func drawRankingCard(fontdata []byte, title string, rankinfo []*cardInfo) (img image.Image, err error) {
+ line := len(rankinfo)
+ const lineh = 130
+ const w = 800
+ h := 64 + (lineh+14)*line + 20 - 14
+ canvas := gg.NewContext(w, h)
+ canvas.SetRGBA255(255, 255, 255, 255)
+ canvas.Clear()
+
+ cardh, cardw := lineh, 770
+ cardspac := 14
+ hspac, wspac := 64.0, 16.0
+ r := 16.0
+
+ wg := &sync.WaitGroup{}
+ wg.Add(line)
+ cardimgs := make([]image.Image, line)
+ for i := 0; i < line; i++ {
+ go func(i int) {
+ defer wg.Done()
+ card := gg.NewContext(w, cardh)
+
+ card.NewSubPath()
+
+ card.MoveTo(wspac+float64(cardh)/2, 0)
+
+ card.LineTo(wspac+float64(cardw)-r, 0)
+ card.DrawArc(wspac+float64(cardw)-r, r, r, gg.Radians(-90), gg.Radians(0))
+ card.LineTo(wspac+float64(cardw), float64(cardh)-r)
+ card.DrawArc(wspac+float64(cardw)-r, float64(cardh)-r, r, gg.Radians(0), gg.Radians(90))
+ card.LineTo(wspac+float64(cardh)/2, float64(cardh))
+ card.DrawArc(wspac+r, float64(cardh)-r, r, gg.Radians(90), gg.Radians(180))
+ card.LineTo(wspac, r)
+ card.DrawArc(wspac+r, r, r, gg.Radians(180), gg.Radians(270))
+
+ card.ClosePath()
+
+ card.ClipPreserve()
+
+ avatar := rankinfo[i].Avatar
+
+ PicH := cardh - 20
+ picW := int(float64(avatar.Bounds().Dx()) * float64(PicH) / float64(avatar.Bounds().Dy()))
+ card.DrawImageAnchored(imgfactory.Size(avatar, picW, PicH).Image(), int(wspac)+10+picW/2, cardh/2, 0.5, 0.5)
+
+ card.ResetClip()
+ card.SetRGBA255(0, 0, 0, 127)
+ card.Stroke()
+
+ card.SetRGBA255(240, 210, 140, 200)
+ card.DrawRoundedRectangle(wspac+float64(cardw-8-250), (float64(cardh)-50)/2, 250, 50, 25)
+ card.Fill()
+ card.SetRGB255(rendercard.RandJPColor())
+ card.DrawRoundedRectangle(wspac+float64(cardw-8-60), (float64(cardh)-50)/2, 60, 50, 25)
+ card.Fill()
+ cardimgs[i] = card.Image()
+ }(i)
+ }
+
+ canvas.SetRGBA255(0, 0, 0, 255)
+ err = canvas.ParseFontFace(fontdata, 32)
+ if err != nil {
+ return
+ }
+ canvas.DrawStringAnchored(title, w/2, 64/2, 0.5, 0.5)
+
+ err = canvas.ParseFontFace(fontdata, 22)
+ if err != nil {
+ return
+ }
+ wg.Wait()
+ for i := 0; i < line; i++ {
+ canvas.DrawImageAnchored(cardimgs[i], w/2, int(hspac)+((cardh+cardspac)*i), 0.5, 0)
+ canvas.DrawStringAnchored(rankinfo[i].TopLeftText, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*3/16), 0, 0.5)
+ }
+
+ // canvas.SetRGBA255(63, 63, 63, 255)
+ err = canvas.ParseFontFace(fontdata, 14)
+ if err != nil {
+ return
+ }
+ for i := 0; i < line; i++ {
+ for j, text := range rankinfo[i].BottomLeftText {
+ canvas.DrawStringAnchored(text, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*6/16)+float64(j*16), 0, 0.5)
+ }
+ }
+ canvas.SetRGBA255(0, 0, 0, 255)
+ err = canvas.ParseFontFace(fontdata, 20)
+ if err != nil {
+ return
+ }
+ for i := 0; i < line; i++ {
+ canvas.DrawStringAnchored(rankinfo[i].RightText, w-wspac-8-60-8, hspac+float64((cardspac+cardh)*i+cardh/2), 1, 0.5)
+ }
+
+ canvas.SetRGBA255(255, 255, 255, 255)
+ err = canvas.ParseFontFace(fontdata, 28)
+ if err != nil {
+ return
+ }
+ for i := 0; i < line; i++ {
+ canvas.DrawStringAnchored(rankinfo[i].Rank, w-wspac-8-30, hspac+float64((cardspac+cardh)*i+cardh/2), 0.5, 0.5)
+ }
+
+ img = canvas.Image()
+ return
+}
+
+// avatar 获取电影海报,图片大且多,存本地增加响应速度
+func avatar(movieInfo *movieInfo) (pic image.Image, err error) {
+ mu.Lock()
+ defer mu.Unlock()
+
+ aimgfile := filepath.Join(en.DataFolder(), movieInfo.Nm+"("+strconv.FormatInt(movieInfo.ID, 10)+").jpg")
+ if file.IsNotExist(aimgfile) {
+ err = file.DownloadTo(movieInfo.Img, aimgfile)
+ if err != nil {
+ return urlToImg(movieInfo.Img)
+ }
+ }
+ f, err := os.Open(filepath.Join(file.BOTPATH, aimgfile))
+ if err != nil {
+ return urlToImg(movieInfo.Img)
+ }
+ defer f.Close()
+ pic, _, err = image.Decode(f)
+ return
+}
+
+func urlToImg(url string) (img image.Image, err error) {
+ resp, err := http.Get(url)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+ img, _, err = image.Decode(resp.Body)
+ return
+}
diff --git a/plugin/music/selecter.go b/plugin/music/selecter.go
index d4b40a933a..5f43844404 100644
--- a/plugin/music/selecter.go
+++ b/plugin/music/selecter.go
@@ -21,6 +21,10 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
)
+var (
+ longZhuURL = "https://www.hhlqilongzhu.cn/api/joox/juhe_music.php?msg=%v"
+)
+
func init() {
control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
@@ -29,7 +33,8 @@ func init() {
"- 网易点歌[xxx]\n" +
"- 酷我点歌[xxx]\n" +
"- 酷狗点歌[xxx]\n" +
- "- 咪咕点歌[xxx]",
+ "- 咪咕点歌[xxx]\n" +
+ "- qq点歌[xxx]\n",
}).OnRegex(`^(.{0,2})点歌\s?(.{1,25})$`).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
// switch 平台
@@ -42,14 +47,39 @@ func init() {
ctx.SendChain(kugou(ctx.State["regex_matched"].([]string)[2]))
case "网易":
ctx.SendChain(cloud163(ctx.State["regex_matched"].([]string)[2]))
- default: // 默认 QQ音乐
+ case "qq":
ctx.SendChain(qqmusic(ctx.State["regex_matched"].([]string)[2]))
+ default: // 默认聚合点歌
+ ctx.SendChain(longzhu(ctx.State["regex_matched"].([]string)[2]))
}
})
}
+// longzhu 聚合平台
+func longzhu(keyword string) message.Segment {
+ data, _ := web.GetData(fmt.Sprintf(longZhuURL, url.QueryEscape(keyword)))
+ // 假设 data 是包含整个 JSON 数组的字节切片
+ results := gjson.ParseBytes(data).Array()
+ for _, result := range results {
+ if strings.Contains(strings.ToLower(result.Get("title").String()), strings.ToLower(keyword)) {
+ if musicURL := result.Get("full_track").String(); musicURL != "" {
+ return message.Record(musicURL)
+ }
+ }
+ }
+
+ results = gjson.GetBytes(data, "#.full_track").Array()
+ if len(results) > 0 {
+ if musicURL := results[0].String(); musicURL != "" {
+ return message.Record(musicURL)
+ }
+ }
+
+ return message.Text("点歌失败, 找不到 ", keyword, " 的相关结果")
+}
+
// migu 返回咪咕音乐卡片
-func migu(keyword string) message.MessageSegment {
+func migu(keyword string) message.Segment {
headers := http.Header{
"Cookie": []string{"audioplayer_exist=1; audioplayer_open=0; migu_cn_cookie_id=3ad476db-f021-4bda-ab91-c485ac3d56a0; Hm_lvt_ec5a5474d9d871cb3d82b846d861979d=1671119573; Hm_lpvt_ec5a5474d9d871cb3d82b846d861979d=1671119573; WT_FPC=id=279ef92eaf314cbb8d01671116477485:lv=1671119583092:ss=1671116477485"},
"csrf": []string{"LWKACV45JSQ"},
@@ -75,7 +105,7 @@ func migu(keyword string) message.MessageSegment {
}
// kuwo 返回酷我音乐卡片
-func kuwo(keyword string) message.MessageSegment {
+func kuwo(keyword string) message.Segment {
headers := http.Header{
"Cookie": []string{"Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1610284708,1610699237; _ga=GA1.2.1289529848.1591618534; kw_token=LWKACV45JSQ; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1610699468; _gid=GA1.2.1868980507.1610699238; _gat=1"},
"csrf": []string{"LWKACV45JSQ"},
@@ -109,7 +139,7 @@ func kuwo(keyword string) message.MessageSegment {
}
// kugou 返回酷狗音乐卡片
-func kugou(keyword string) message.MessageSegment {
+func kugou(keyword string) message.Segment {
stamp := time.Now().UnixNano() / 1e6
hash := md5str(
fmt.Sprintf(
@@ -163,7 +193,7 @@ func kugou(keyword string) message.MessageSegment {
}
// cloud163 返回网易云音乐卡片
-func cloud163(keyword string) (msg message.MessageSegment) {
+func cloud163(keyword string) (msg message.Segment) {
requestURL := "http://music.163.com/api/search/get/web?type=1&limit=1&s=" + url.QueryEscape(keyword)
data, err := web.GetData(requestURL)
if err != nil {
@@ -175,7 +205,7 @@ func cloud163(keyword string) (msg message.MessageSegment) {
}
// qqmusic 返回QQ音乐卡片
-func qqmusic(keyword string) (msg message.MessageSegment) {
+func qqmusic(keyword string) (msg message.Segment) {
requestURL := "https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg?platform=yqq.json&key=" + url.QueryEscape(keyword)
data, err := web.RequestDataWith(web.NewDefaultClient(), requestURL, "GET", "", web.RandUA(), nil)
if err != nil {
diff --git a/plugin/nativesetu/data.go b/plugin/nativesetu/data.go
index e9a37c66e4..f1d50482d9 100644
--- a/plugin/nativesetu/data.go
+++ b/plugin/nativesetu/data.go
@@ -24,15 +24,15 @@ type setuclass struct {
Path string `db:"path"` // Path 图片路径
}
-var ns = &nsetu{db: &sql.Sqlite{}}
+var ns nsetu
type nsetu struct {
- db *sql.Sqlite
+ db sql.Sqlite
mu sync.RWMutex
}
func (n *nsetu) List() (l []string) {
- if file.IsExist(n.db.DBPath) {
+ if file.IsExist(dbpath) {
var err error
l, err = n.db.ListTables()
if err != nil {
@@ -46,7 +46,7 @@ func (n *nsetu) scanall(path string) error {
model := &setuclass{}
root := os.DirFS(path)
_ = n.db.Close()
- _ = os.Remove(n.db.DBPath)
+ _ = os.Remove(dbpath)
err := n.db.Open(time.Hour)
if err != nil {
return err
diff --git a/plugin/nativesetu/main.go b/plugin/nativesetu/main.go
index 3f98341b48..c791d8d568 100644
--- a/plugin/nativesetu/main.go
+++ b/plugin/nativesetu/main.go
@@ -13,6 +13,7 @@ import (
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
@@ -20,6 +21,7 @@ import (
var (
setupath = "/tmp" // 绝对路径,图片根目录
+ dbpath = ""
)
func init() {
@@ -34,7 +36,8 @@ func init() {
PrivateDataFolder: "nsetu",
})
- ns.db.DBPath = engine.DataFolder() + "data.db"
+ dbpath = engine.DataFolder() + "data.db"
+ ns.db = sql.New(dbpath)
cfgfile := engine.DataFolder() + "setupath.txt"
if file.IsExist(cfgfile) {
b, err := os.ReadFile(cfgfile)
@@ -48,7 +51,7 @@ func init() {
panic(err)
}
- engine.OnRegex(`^本地(.*)$`, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, ns)).SetBlock(true).
+ engine.OnRegex(`^本地(.*)$`, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, &ns)).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
imgtype := ctx.State["regex_matched"].([]string)[1]
sc := new(setuclass)
@@ -69,7 +72,7 @@ func init() {
ctx.SendChain(message.Text(imgtype, ": ", sc.Name, "\n"), message.Image(p))
}
})
- engine.OnRegex(`^刷新本地(.*)$`, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, ns), zero.SuperUserPermission).SetBlock(true).
+ engine.OnRegex(`^刷新本地(.*)$`, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, &ns), zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
imgtype := ctx.State["regex_matched"].([]string)[1]
err := ns.scanclass(os.DirFS(setupath), imgtype, imgtype)
diff --git a/plugin/nihongo/model.go b/plugin/nihongo/model.go
index d843ea778f..910a3c2ecb 100644
--- a/plugin/nihongo/model.go
+++ b/plugin/nihongo/model.go
@@ -22,14 +22,14 @@ func (g *grammar) string() string {
return fmt.Sprintf("ID:\n%d\n\n标签:\n%s\n\n语法名:\n%s\n\n发音:\n%s\n\n用法:\n%s\n\n意思:\n%s\n\n解说:\n%s\n\n示例:\n%s", g.ID, g.Tag, g.Name, g.Pronunciation, g.Usage, g.Meaning, g.Explanation, g.Example)
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
func getRandomGrammarByTag(tag string) (g grammar) {
- _ = db.Find("grammar", &g, "WHERE tag LIKE '%"+tag+"%' ORDER BY RANDOM() limit 1")
+ _ = db.Find("grammar", &g, "WHERE tag LIKE ? ORDER BY RANDOM() limit 1", "%"+tag+"%")
return
}
func getRandomGrammarByKeyword(keyword string) (g grammar) {
- _ = db.Find("grammar", &g, "WHERE (name LIKE '%"+keyword+"%' or pronunciation LIKE '%"+keyword+"%') ORDER BY RANDOM() limit 1")
+ _ = db.Find("grammar", &g, "WHERE (name LIKE ? OR pronunciation LIKE ?) ORDER BY RANDOM() limit 1", "%"+keyword+"%", "%"+keyword+"%")
return
}
diff --git a/plugin/nihongo/nihongo.go b/plugin/nihongo/nihongo.go
index 453262515e..6829a9f730 100644
--- a/plugin/nihongo/nihongo.go
+++ b/plugin/nihongo/nihongo.go
@@ -6,6 +6,7 @@ import (
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
@@ -24,7 +25,7 @@ func init() {
})
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- db.DBPath = engine.DataFolder() + "nihongo.db"
+ db = sql.New(engine.DataFolder() + "nihongo.db")
_, err := engine.GetLazyData("nihongo.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
diff --git a/plugin/niuniu/draw.go b/plugin/niuniu/draw.go
new file mode 100644
index 0000000000..0fe284a38d
--- /dev/null
+++ b/plugin/niuniu/draw.go
@@ -0,0 +1,54 @@
+package niuniu
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "image/png"
+ "net/http"
+
+ "github.com/FloatTech/AnimeAPI/niu"
+ "github.com/FloatTech/floatbox/file"
+ "github.com/FloatTech/rendercard"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/img/text"
+ zero "github.com/wdvxdr1123/ZeroBot"
+)
+
+func processRankingImg(allUsers niu.BaseInfos, ctx *zero.Ctx, t bool) ([]byte, error) {
+ fontByte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
+ if err != nil {
+ return nil, err
+ }
+ s := "牛牛长度"
+ title := "牛牛长度排行"
+ if !t {
+ s = "牛牛深度"
+ title = "牛牛深度排行"
+ }
+ ri := make([]*rendercard.RankInfo, len(allUsers))
+ for i, user := range allUsers {
+ resp, err := http.Get(fmt.Sprintf("https://q1.qlogo.cn/g?b=qq&nk=%d&s=100", user.UID))
+ if err != nil {
+ return nil, err
+ }
+ decode, _, err := image.Decode(resp.Body)
+ _ = resp.Body.Close()
+ if err != nil {
+ return nil, err
+ }
+ ri[i] = &rendercard.RankInfo{
+ Avatar: decode,
+ TopLeftText: ctx.CardOrNickName(user.UID),
+ BottomLeftText: fmt.Sprintf("QQ:%d", user.UID),
+ RightText: fmt.Sprintf("%s:%.2fcm", s, user.Length),
+ }
+ }
+ img, err := rendercard.DrawRankingCard(fontByte, title, ri)
+ if err != nil {
+ return nil, err
+ }
+ var buf bytes.Buffer
+ err = png.Encode(&buf, img)
+ return buf.Bytes(), err
+}
diff --git a/plugin/niuniu/main.go b/plugin/niuniu/main.go
new file mode 100644
index 0000000000..d49b5a062f
--- /dev/null
+++ b/plugin/niuniu/main.go
@@ -0,0 +1,430 @@
+// Package niuniu 牛牛大作战
+package niuniu
+
+import (
+ "errors"
+ "fmt"
+ "math/rand"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/FloatTech/AnimeAPI/niu"
+ "github.com/FloatTech/AnimeAPI/wallet"
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ "github.com/FloatTech/zbputils/ctxext"
+ "github.com/RomiChan/syncx"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/extension/rate"
+ "github.com/wdvxdr1123/ZeroBot/message"
+)
+
+var (
+ en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
+ DisableOnDefault: false,
+ Brief: "牛牛大作战",
+ Help: "- 打胶\n" +
+ "- 使用[道具名称]打胶\n" +
+ "- jj@xxx\n" +
+ "- 使用[道具名称]jj@xxx\n" +
+ "- 注册牛牛\n" +
+ "- 赎牛牛(cd:60分钟)\n" +
+ "- 出售牛牛\n" +
+ "- 牛牛拍卖行\n" +
+ "- 牛牛商店\n" +
+ "- 牛牛背包\n" +
+ "- 注销牛牛\n" +
+ "- 查看我的牛牛\n" +
+ "- 牛子长度排行\n" +
+ "- 牛子深度排行\n" +
+ "\n ps : 出售后的牛牛都会进入牛牛拍卖行哦",
+ PrivateDataFolder: "niuniu",
+ })
+ dajiaoLimiter = rate.NewManager[string](time.Second*90, 1)
+ jjLimiter = rate.NewManager[string](time.Second*150, 1)
+ jjCount = syncx.Map[string, *niu.PKRecord]{}
+ register = syncx.Map[string, *niu.PKRecord]{}
+)
+
+func init() {
+ en.OnFullMatch("牛牛拍卖行", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ uid := ctx.Event.UserID
+ auction, err := niu.ShowAuction(gid)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR:", err))
+ return
+ }
+
+ var messages message.Message
+ messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text("牛牛拍卖行有以下牛牛")))
+ for _, info := range auction {
+ msg := fmt.Sprintf("商品序号: %d\n牛牛原所属: %d\n牛牛价格: %d%s\n牛牛大小: %.2fcm",
+ info.ID, info.UserID, info.Money, wallet.GetWalletName(), info.Length)
+ messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text(msg)))
+ }
+ if id := ctx.Send(messages).ID(); id == 0 {
+ ctx.Send(message.Text("发送拍卖行失败"))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.Message), message.Text("请输入对应序号进行购买"))
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.CheckUser(uid), zero.CheckGroup(gid), zero.RegexRule(`^(\d+)$`)).Repeat()
+ defer cancel()
+ timer := time.NewTimer(120 * time.Second)
+ answer := ""
+ defer timer.Stop()
+ for {
+ select {
+ case <-timer.C:
+ ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消"))
+ return
+ case r := <-recv:
+ answer = r.Event.Message.String()
+ n, err := strconv.Atoi(answer)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ msg, err := niu.Auction(gid, uid, n)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR:", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.Message), message.Text(msg))
+ return
+ }
+ }
+ })
+ en.OnFullMatch("出售牛牛", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ uid := ctx.Event.UserID
+ key := fmt.Sprintf("%d_%d", gid, uid)
+ sell, err := niu.Sell(gid, uid)
+ if errors.Is(err, niu.ErrCanceled) || errors.Is(err, niu.ErrNoNiuNiu) {
+ ctx.SendChain(message.Text(err))
+ jjCount.Delete(key)
+ return
+ } else if err != nil {
+ ctx.SendChain(message.Text("ERROR:", err))
+ return
+ }
+
+ // 数据库操作成功之后,及时删除残留的缓存
+ if _, ok := jjCount.Load(key); ok {
+ jjCount.Delete(key)
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(sell))
+ })
+ en.OnFullMatch("牛牛背包", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ uid := ctx.Event.UserID
+ bag, err := niu.Bag(gid, uid)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR:", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(bag))
+ })
+ en.OnFullMatch("牛牛商店", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ uid := ctx.Event.UserID
+
+ if _, err := niu.GetWordNiuNiu(gid, uid); err != nil {
+ ctx.SendChain(message.Text(niu.ErrNoNiuNiu))
+ return
+ }
+
+ propMap := map[int]struct {
+ name string
+ cost int
+ scope string
+ description string
+ }{
+ 1: {"伟哥", 100, "打胶", "可以让你打胶每次都增长"},
+ 2: {"媚药", 100, "打胶", "可以让你打胶每次都减少"},
+ 3: {"击剑神器", 300, "jj", "可以让你每次击剑都立于不败之地"},
+ 4: {"击剑神稽", 300, "jj", "可以让你每次击剑都失败"},
+ }
+
+ var messages message.Message
+ messages = append(messages, ctxext.FakeSenderForwardNode(ctx,
+ message.Text("输入对应序号进行购买商品"),
+ message.Text(
+ "使用说明:\n"+
+ "商品id-商品数量\n"+
+ "如想购买10个伟哥\n"+
+ "即:1-10")))
+ messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text("牛牛商店当前售卖的物品如下")))
+ for id := 1; id <= len(propMap); id++ {
+ product := propMap[id]
+ productInfo := fmt.Sprintf("商品%d\n商品名: %s\n商品价格: %dATRI币\n商品作用域: %s\n商品描述: %s",
+ id, product.name, product.cost, product.scope, product.description)
+ messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text(productInfo)))
+ }
+ if id := ctx.Send(messages).ID(); id == 0 {
+ ctx.Send(message.Text("发送商店失败"))
+ return
+ }
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.CheckUser(uid), zero.CheckGroup(gid), zero.RegexRule(`^(\d+)-(\d+)$`)).Repeat()
+ defer cancel()
+ timer := time.NewTimer(120 * time.Second)
+ answer := ""
+ defer timer.Stop()
+ for {
+ select {
+ case <-timer.C:
+ ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消"))
+ return
+ case r := <-recv:
+ answer = r.Event.Message.String()
+
+ // 解析输入的商品ID和数量
+ parts := strings.Split(answer, "-")
+ productID, _ := strconv.Atoi(parts[0])
+ quantity, _ := strconv.Atoi(parts[1])
+
+ if err := niu.Store(gid, uid, productID, quantity); err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+
+ ctx.SendChain(message.Text("购买成功!"))
+ return
+ }
+ }
+ })
+ en.OnFullMatch("赎牛牛", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ uid := ctx.Event.UserID
+ last, ok := jjCount.Load(fmt.Sprintf("%d_%d", gid, uid))
+
+ if !ok {
+ ctx.SendChain(message.Text("你还没有被厥呢"))
+ return
+ }
+
+ if time.Since(last.TimeLimit) > time.Hour {
+ ctx.SendChain(message.Text("时间已经过期了,牛牛已被收回!"))
+ jjCount.Delete(fmt.Sprintf("%d_%d", gid, uid))
+ return
+ }
+
+ if last.Count < 4 {
+ ctx.SendChain(message.Text("你还没有被厥够4次呢,不能赎牛牛"))
+ return
+ }
+ ctx.SendChain(message.Text("再次确认一下哦,这次赎牛牛,牛牛长度将会变成", last.Length, "cm\n还需要嘛【是|否】"))
+ recv, cancel := zero.NewFutureEvent("message", 999, false, zero.CheckUser(uid), zero.CheckGroup(gid), zero.RegexRule(`^(是|否)$`)).Repeat()
+ defer cancel()
+ timer := time.NewTimer(2 * time.Minute)
+ defer timer.Stop()
+ for {
+ select {
+ case <-timer.C:
+ ctx.SendChain(message.Text("操作超时,已自动取消"))
+ return
+ case c := <-recv:
+ answer := c.Event.Message.String()
+ if answer == "否" {
+ ctx.SendChain(message.Text("取消成功!"))
+ return
+ }
+
+ if err := niu.Redeem(gid, uid, *last); err != nil {
+ ctx.SendChain(message.Text("ERROR:", err))
+ return
+ }
+ // 成功赎回,删除残留的缓存。
+ jjCount.Delete(fmt.Sprintf("%d_%d", gid, uid))
+
+ ctx.SendChain(message.At(uid), message.Text(fmt.Sprintf("恭喜你!成功赎回牛牛,当前长度为:%.2fcm", last.Length)))
+ return
+ }
+ }
+ })
+ en.OnFullMatch("牛子长度排行", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ infos, err := niu.GetRankingInfo(gid, true)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ img, err := processRankingImg(infos, ctx, true)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.SendChain(message.ImageBytes(img))
+ })
+ en.OnFullMatch("牛子深度排行", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ infos, err := niu.GetRankingInfo(gid, false)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ img, err := processRankingImg(infos, ctx, false)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.SendChain(message.ImageBytes(img))
+ })
+ en.OnFullMatch("查看我的牛牛", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ gid := ctx.Event.GroupID
+ view, err := niu.View(gid, uid, ctx.CardOrNickName(uid))
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(view))
+ })
+ en.OnRegex(`^(?:.*使用(.*))??打胶$`, zero.OnlyGroup).SetBlock(true).Limit(func(ctx *zero.Ctx) *rate.Limiter {
+ lt := dajiaoLimiter.Load(fmt.Sprintf("%d_%d", ctx.Event.GroupID, ctx.Event.UserID))
+ ctx.State["dajiao_last_touch"] = lt.LastTouch()
+ return lt
+ }, func(ctx *zero.Ctx) {
+ timePass := int(time.Since(time.Unix(ctx.State["dajiao_last_touch"].(int64), 0)).Seconds())
+ ctx.SendChain(message.Text(randomChoice([]string{
+ fmt.Sprintf("才过去了%ds时间,你就又要打🦶了,身体受得住吗", timePass),
+ fmt.Sprintf("不行不行,你的身体会受不了的,歇%ds再来吧", 90-timePass),
+ fmt.Sprintf("休息一下吧,会炸膛的!%ds后再来吧", 90-timePass),
+ fmt.Sprintf("打咩哟,你的牛牛会爆炸的,休息%ds再来吧", 90-timePass),
+ })))
+ }).Handle(func(ctx *zero.Ctx) {
+ // 获取群号和用户ID
+ gid := ctx.Event.GroupID
+ uid := ctx.Event.UserID
+ fiancee := ctx.State["regex_matched"].([]string)
+
+ msg, err := niu.HitGlue(gid, uid, fiancee[1])
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ dajiaoLimiter.Delete(fmt.Sprintf("%d_%d", ctx.Event.GroupID, ctx.Event.UserID))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(msg))
+ })
+ en.OnFullMatch("注册牛牛", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ gid := ctx.Event.GroupID
+ uid := ctx.Event.UserID
+ msg, err := niu.Register(gid, uid)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(msg))
+ })
+ en.OnMessage(zero.NewPattern(nil).Text(`^(?:.*使用(.*))??jj`).At().AsRule(),
+ zero.OnlyGroup).SetBlock(true).Limit(func(ctx *zero.Ctx) *rate.Limiter {
+ lt := jjLimiter.Load(fmt.Sprintf("%d_%d", ctx.Event.GroupID, ctx.Event.UserID))
+ ctx.State["jj_last_touch"] = lt.LastTouch()
+ return lt
+ }, func(ctx *zero.Ctx) {
+ timePass := int(time.Since(time.Unix(ctx.State["jj_last_touch"].(int64), 0)).Seconds())
+ ctx.SendChain(message.Text(randomChoice([]string{
+ fmt.Sprintf("才过去了%ds时间,你就又要击剑了,真是饥渴难耐啊", timePass),
+ fmt.Sprintf("不行不行,你的身体会受不了的,歇%ds再来吧", 150-timePass),
+ fmt.Sprintf("你这种男同就应该被送去集中营!等待%ds再来吧", 150-timePass),
+ fmt.Sprintf("打咩哟!你的牛牛会炸的,休息%ds再来吧", 150-timePass),
+ })))
+ },
+ ).Handle(func(ctx *zero.Ctx) {
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ adduser, err := strconv.ParseInt(patternParsed[1].At(), 10, 64)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ jjLimiter.Delete(fmt.Sprintf("%d_%d", ctx.Event.GroupID, ctx.Event.UserID))
+ return
+ }
+ uid := ctx.Event.UserID
+ gid := ctx.Event.GroupID
+ msg, length, niuID, err := niu.JJ(gid, uid, adduser, patternParsed[0].Text()[1])
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ jjLimiter.Delete(fmt.Sprintf("%d_%d", ctx.Event.GroupID, ctx.Event.UserID))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(msg))
+ j := fmt.Sprintf("%d_%d", gid, adduser)
+ count, ok := jjCount.Load(j)
+ var c niu.PKRecord
+ // 按照最后一次被 jj 时的时间计算,超过60分钟则重置
+ if !ok {
+ // 第一次被 jj
+ c = niu.PKRecord{
+ NiuID: niuID,
+ TimeLimit: time.Now(),
+ Count: 1,
+ Length: length,
+ }
+ } else {
+ c = niu.PKRecord{
+ NiuID: niuID,
+ TimeLimit: time.Now(),
+ Count: count.Count + 1,
+ Length: count.Length,
+ }
+ // 超时了,重置
+ if time.Since(c.TimeLimit) > time.Hour {
+ c = niu.PKRecord{
+ NiuID: niuID,
+ TimeLimit: time.Now(),
+ Count: 1,
+ Length: length,
+ }
+ }
+ }
+
+ jjCount.Store(j, &c)
+ if c.Count > 2 {
+ ctx.SendChain(message.Text(randomChoice([]string{
+ fmt.Sprintf("你们太厉害了,对方已经被你们打了%d次了,你们可以继续找他🤺", c.Count),
+ "你们不要再找ta🤺啦!"},
+ )))
+
+ if c.Count >= 4 {
+ if c.Count == 6 {
+ return
+ }
+ id := ctx.SendPrivateMessage(adduser,
+ message.Text(fmt.Sprintf("你在%d群里已经被厥冒烟了,快去群里赎回你原本的牛牛!\n发送:`赎牛牛`即可!", gid)))
+ if id == 0 {
+ ctx.SendChain(message.At(adduser), message.Text("快发送`赎牛牛`来赎回你原本的牛牛!"))
+ }
+ }
+ }
+ })
+ en.OnFullMatch("注销牛牛", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ uid := ctx.Event.UserID
+ gid := ctx.Event.GroupID
+ key := fmt.Sprintf("%d_%d", gid, uid)
+ data, ok := register.Load(key)
+ switch {
+ case !ok || time.Since(data.TimeLimit) > time.Hour*24:
+ data = &niu.PKRecord{
+ TimeLimit: time.Now(),
+ Count: 1,
+ }
+ default:
+ if err := wallet.InsertWalletOf(uid, -data.Count*50); err != nil {
+ ctx.SendChain(message.Text("你的钱不够你注销牛牛了,这次注销需要", data.Count*50, wallet.GetWalletName()))
+ return
+ }
+ data.Count++
+ }
+ register.Store(key, data)
+ msg, err := niu.Cancel(gid, uid)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(msg))
+ })
+}
+
+func randomChoice(options []string) string {
+ return options[rand.Intn(len(options))]
+}
diff --git a/plugin/omikuji/model.go b/plugin/omikuji/model.go
index 2f204acf53..847f004e08 100644
--- a/plugin/omikuji/model.go
+++ b/plugin/omikuji/model.go
@@ -1,8 +1,6 @@
package omikuji
import (
- "strconv"
-
sql "github.com/FloatTech/sqlite"
)
@@ -11,12 +9,12 @@ type kuji struct {
Text string `db:"text"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
// 返回一个解签
func getKujiByBango(id uint8) string {
var s kuji
- err := db.Find("kuji", &s, "where id = "+strconv.Itoa(int(id)))
+ err := db.Find("kuji", &s, "WHERE id = ?", id)
if err != nil {
return err.Error()
}
diff --git a/plugin/omikuji/sensou.go b/plugin/omikuji/sensou.go
index 5b3e754ee7..11d95ebe73 100644
--- a/plugin/omikuji/sensou.go
+++ b/plugin/omikuji/sensou.go
@@ -11,6 +11,7 @@ import (
"github.com/wdvxdr1123/ZeroBot/utils/helper"
fcext "github.com/FloatTech/floatbox/ctxext"
+ sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
@@ -48,7 +49,7 @@ func init() { // 插件主体
})
engine.OnFullMatch("解签", fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
- db.DBPath = engine.DataFolder() + "kuji.db"
+ db = sql.New(engine.DataFolder() + "kuji.db")
_, err := engine.GetLazyData("kuji.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
diff --git a/plugin/poker/poker.go b/plugin/poker/poker.go
index 32def3c247..161827c37f 100644
--- a/plugin/poker/poker.go
+++ b/plugin/poker/poker.go
@@ -28,12 +28,12 @@ func init() {
getImg := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
data, err := engine.GetLazyData("imgdata.json", true)
if err != nil {
- ctx.SendChain(message.Text("ERROR:", err))
+ ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = json.Unmarshal(data, &cardImgPathList)
if err != nil {
- ctx.SendChain(message.Text("ERROR:", err))
+ ctx.SendChain(message.Text("ERROR: ", err))
return false
}
return true
diff --git a/plugin/qqwife/command.go b/plugin/qqwife/command.go
index 6915272b22..9f548bd08b 100644
--- a/plugin/qqwife/command.go
+++ b/plugin/qqwife/command.go
@@ -5,7 +5,6 @@ import (
"math/rand"
"sort"
"strconv"
- "strings"
"sync"
"time"
@@ -29,8 +28,8 @@ import (
)
type 婚姻登记 struct {
- db *sql.Sqlite
sync.RWMutex
+ db sql.Sqlite
}
// 群设置
@@ -53,9 +52,7 @@ type userinfo struct {
}
var (
- 民政局 = &婚姻登记{
- db: &sql.Sqlite{},
- }
+ 民政局 婚姻登记
engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "一群一天一夫一妻制群老婆",
@@ -67,7 +64,7 @@ var (
"- 买礼物给[对方Q号|@对方QQ]\n使用小熊饼干获取好感度\n" +
"- 做媒 @攻方QQ @受方QQ\n身为管理, 群友的xing福是要搭把手的(攻受双方好感度越高成功率越高,保底30%概率)\n" +
"--------------------------------\n好感度规则\n--------------------------------\n" +
- "\"娶群友\"指令好感度随机增加1~5。\n\"A牛B的C\"会导致C恨A, 好感度-5;\nB为了报复A, 好感度+5(什么柜子play)\nA为BC做媒,成功B、C对A好感度+1反之-1\n做媒成功BC好感度+1" +
+ "\"娶群友\"&\"(娶|嫁)@对方QQ\"指令好感度随机增加1~5。\n\"A牛B的C\"会导致C恨A, 好感度-5;\nB为了报复A, 好感度+5(什么柜子play)\nA为BC做媒,成功B、C对A好感度+1反之-1\n做媒成功BC好感度+1" +
"\nTips: 群老婆列表过0点刷新",
PrivateDataFolder: "qqwife",
}).ApplySingle(single.New(
@@ -81,7 +78,7 @@ var (
}),
))
getdb = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- 民政局.db.DBPath = engine.DataFolder() + "结婚登记表.db"
+ 民政局.db = sql.New(engine.DataFolder() + "结婚登记表.db")
err := 民政局.db.Open(time.Hour)
if err == nil {
// 创建群配置表
@@ -128,7 +125,7 @@ func init() {
ctx.SendChain(
message.At(uid),
message.Text("\n今天你在", userInfo.Updatetime, "娶了群友"),
- message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(userInfo.Target, 10)+"&s=640").Add("cache", 0),
+ message.Image("https://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(userInfo.Target, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", userInfo.Targetname, "]",
@@ -140,7 +137,7 @@ func init() {
ctx.SendChain(
message.At(uid),
message.Text("\n今天你在", userInfo.Updatetime, "被群友"),
- message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(userInfo.User, 10)+"&s=640").Add("cache", 0),
+ message.Image("https://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(userInfo.User, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", userInfo.Username, "]",
@@ -200,7 +197,7 @@ func init() {
ctx.SendChain(
message.At(uid),
message.Text("今天你的群老婆是"),
- message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
+ message.Image("https://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
@@ -281,7 +278,7 @@ func init() {
ctx.SendChain(message.Text("该功能只能在群组使用或者指定群组"))
return
}
- err = 民政局.清理花名册("group" + strconv.FormatInt(ctx.Event.GroupID, 10))
+ err = 民政局.清理花名册(ctx.Event.GroupID)
default:
cmd := ctx.State["regex_matched"].([]string)[1]
gid, _ := strconv.ParseInt(cmd, 10, 64) // 判断是否为群号
@@ -289,7 +286,7 @@ func init() {
ctx.SendChain(message.Text("请输入正确的群号"))
return
}
- err = 民政局.清理花名册("group" + cmd)
+ err = 民政局.清理花名册(gid)
}
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
@@ -307,7 +304,7 @@ func (sql *婚姻登记) 查看设置(gid int64) (dbinfo updateinfo, err error)
if err != nil {
return
}
- if !sql.db.CanFind("updateinfo", "where gid is "+strconv.FormatInt(gid, 10)) {
+ if !sql.db.CanFind("updateinfo", "WHERE gid = ?", gid) {
// 没有记录
return updateinfo{
GID: gid,
@@ -316,7 +313,7 @@ func (sql *婚姻登记) 查看设置(gid int64) (dbinfo updateinfo, err error)
CDtime: 12,
}, nil
}
- _ = sql.db.Find("updateinfo", &dbinfo, "where gid is "+strconv.FormatInt(gid, 10))
+ _ = sql.db.Find("updateinfo", &dbinfo, "WHERE gid = ?", gid)
return
}
@@ -334,7 +331,7 @@ func (sql *婚姻登记) 开门时间(gid int64) error {
sql.Lock()
defer sql.Unlock()
dbinfo := updateinfo{}
- _ = sql.db.Find("updateinfo", &dbinfo, "where gid is "+strconv.FormatInt(gid, 10))
+ _ = sql.db.Find("updateinfo", &dbinfo, "WHERE gid = ?", gid)
if time.Now().Format("2006/01/02") != dbinfo.Updatetime {
// 如果跨天了就删除
_ = sql.db.Drop("group" + strconv.FormatInt(gid, 10))
@@ -355,10 +352,9 @@ func (sql *婚姻登记) 查户口(gid, uid int64) (info userinfo, err error) {
if err != nil {
return
}
- uidstr := strconv.FormatInt(uid, 10)
- err = sql.db.Find(gidstr, &info, "where user = "+uidstr)
+ err = sql.db.Find(gidstr, &info, "WHERE user = ?", uid)
if err != nil {
- err = sql.db.Find(gidstr, &info, "where target = "+uidstr)
+ err = sql.db.Find(gidstr, &info, "WHERE target = ?", uid)
}
return
}
@@ -423,7 +419,7 @@ func slicename(name string, canvas *gg.Context) (resultname string) {
return
}
-func (sql *婚姻登记) 清理花名册(gid ...string) error {
+func (sql *婚姻登记) 清理花名册(gid ...int64) error {
sql.Lock()
defer sql.Unlock()
switch gid {
@@ -439,9 +435,9 @@ func (sql *婚姻登记) 清理花名册(gid ...string) error {
}
return err
default:
- err := sql.db.Drop(gid[0])
+ err := sql.db.Drop("group" + strconv.FormatInt(gid[0], 10))
if err == nil {
- _ = sql.db.Del("cdsheet", "where GroupID is "+strings.ReplaceAll(gid[0], "group", ""))
+ _ = sql.db.Del("cdsheet", "WHERE GroupID = ?", gid[0])
}
return err
}
diff --git a/plugin/qqwife/favorSystem.go b/plugin/qqwife/favorSystem.go
index 6e66c3d954..af370c1f57 100644
--- a/plugin/qqwife/favorSystem.go
+++ b/plugin/qqwife/favorSystem.go
@@ -9,6 +9,7 @@ import (
"github.com/FloatTech/floatbox/math"
"github.com/FloatTech/imgfactory"
+ sql "github.com/FloatTech/sqlite"
control "github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
zero "github.com/wdvxdr1123/ZeroBot"
@@ -31,9 +32,10 @@ type favorability struct {
func init() {
// 好感度系统
- engine.OnRegex(`^查好感度\s*(\[CQ:at,qq=)?(\d+)`, zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
+ engine.OnMessage(zero.NewPattern(nil).Text(`^查好感度`).At().AsRule(), zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
- fiancee, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ fiancee, _ := strconv.ParseInt(patternParsed[1].At(), 10, 64)
uid := ctx.Event.UserID
favor, err := 民政局.查好感度(uid, fiancee)
if err != nil {
@@ -47,12 +49,12 @@ func init() {
)
})
// 礼物系统
- engine.OnRegex(`^买礼物给\s?(\[CQ:at,qq=(\d+)\]|(\d+))`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
+ engine.OnMessage(zero.NewPattern(nil).Text(`^买礼物给`).At().AsRule(), zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
- fiancee := ctx.State["regex_matched"].([]string)
- gay, _ := strconv.ParseInt(fiancee[2]+fiancee[3], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ gay, _ := strconv.ParseInt(patternParsed[1].At(), 10, 64)
if gay == uid {
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.At(uid), message.Text("你想给自己买什么礼物呢?")))
return
@@ -117,9 +119,9 @@ func init() {
}
// 输出结果
if mood == 0 {
- ctx.SendChain(message.Text("你花了", moneyToFavor, "ATRI币买了一件女装送给了ta,ta很不喜欢,你们的好感度降低至", lastfavor))
+ ctx.SendChain(message.Text("你花了", moneyToFavor, wallet.GetWalletName(), "买了一件女装送给了ta,ta很不喜欢,你们的好感度降低至", lastfavor))
} else {
- ctx.SendChain(message.Text("你花了", moneyToFavor, "ATRI币买了一件女装送给了ta,ta很喜欢,你们的好感度升至", lastfavor))
+ ctx.SendChain(message.Text("你花了", moneyToFavor, wallet.GetWalletName(), "买了一件女装送给了ta,ta很喜欢,你们的好感度升至", lastfavor))
}
})
engine.OnFullMatch("好感度列表", zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
@@ -214,7 +216,7 @@ func init() {
favor := favorability{}
delInfo := make([]string, 0, count*2)
favorInfo := make(map[string]int, count*2)
- _ = 民政局.db.FindFor("favorability", &favor, "group by Userinfo", func() error {
+ _ = 民政局.db.FindFor("favorability", &favor, "GROUP BY Userinfo", func() error {
delInfo = append(delInfo, favor.Userinfo)
// 解析旧数据
userList := strings.Split(favor.Userinfo, "+")
@@ -236,15 +238,11 @@ func init() {
}
return nil
})
- for _, updateinfo := range delInfo {
- // 删除旧数据
- err = 民政局.db.Del("favorability", "where Userinfo = '"+updateinfo+"'")
- if err != nil {
- userList := strings.Split(favor.Userinfo, "+")
- uid1, _ := strconv.ParseInt(userList[0], 10, 64)
- uid2, _ := strconv.ParseInt(userList[1], 10, 64)
- ctx.SendChain(message.Text("[ERROR]: 删除", ctx.CardOrNickName(uid1), "和", ctx.CardOrNickName(uid2), "的好感度时发生了错误。\n错误信息:", err))
- }
+ // 删除旧数据
+ q, s := sql.QuerySet("WHERE Userinfo", "IN", delInfo)
+ err = 民政局.db.Del("favorability", q, s...)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]: 删除好感度时发生了错误。\n错误信息:", err))
}
for userInfo, favor := range favorInfo {
favorInfo := favorability{
@@ -273,15 +271,15 @@ func (sql *婚姻登记) 查好感度(uid, target int64) (int, error) {
info := favorability{}
if uid > target {
userinfo := strconv.FormatInt(uid, 10) + "+" + strconv.FormatInt(target, 10)
- err = sql.db.Find("favorability", &info, "where Userinfo is '"+userinfo+"'")
+ err = sql.db.Find("favorability", &info, "WHERE Userinfo = ?", userinfo)
if err != nil {
- _ = sql.db.Find("favorability", &info, "where Userinfo glob '*"+userinfo+"*'")
+ _ = sql.db.Find("favorability", &info, "WHERE Userinfo glob ?", "*"+userinfo+"*")
}
} else {
userinfo := strconv.FormatInt(target, 10) + "+" + strconv.FormatInt(uid, 10)
- err = sql.db.Find("favorability", &info, "where Userinfo is '"+userinfo+"'")
+ err = sql.db.Find("favorability", &info, "WHERE Userinfo = ?", userinfo)
if err != nil {
- _ = sql.db.Find("favorability", &info, "where Userinfo glob '*"+userinfo+"*'")
+ _ = sql.db.Find("favorability", &info, "WHERE Userinfo glob ?", "*"+userinfo+"*")
}
}
return info.Favor, nil
@@ -304,7 +302,7 @@ func (sql *婚姻登记) getGroupFavorability(uid int64) (list favorList, err er
sql.RLock()
defer sql.RUnlock()
info := favorability{}
- err = sql.db.FindFor("favorability", &info, "where Userinfo glob '*"+uidStr+"*'", func() error {
+ err = sql.db.FindFor("favorability", &info, "WHERE Userinfo glob ?", func() error {
var target string
userList := strings.Split(info.Userinfo, "+")
switch {
@@ -320,7 +318,7 @@ func (sql *婚姻登记) getGroupFavorability(uid int64) (list favorList, err er
Favor: info.Favor,
})
return nil
- })
+ }, "*"+uidStr+"*")
sort.Sort(list)
return
}
@@ -338,15 +336,15 @@ func (sql *婚姻登记) 更新好感度(uid, target int64, score int) (favor in
targstr := strconv.FormatInt(target, 10)
if uid > target {
info.Userinfo = uidstr + "+" + targstr
- err = sql.db.Find("favorability", &info, "where Userinfo is '"+info.Userinfo+"'")
+ err = sql.db.Find("favorability", &info, "WHERE Userinfo = ?", info.Userinfo)
} else {
info.Userinfo = targstr + "+" + uidstr
- err = sql.db.Find("favorability", &info, "where Userinfo is '"+info.Userinfo+"'")
+ err = sql.db.Find("favorability", &info, "WHERE Userinfo = ?", info.Userinfo)
}
if err != nil {
- err = sql.db.Find("favorability", &info, "where Userinfo glob '*"+targstr+"+"+uidstr+"*'")
+ err = sql.db.Find("favorability", &info, "WHERE Userinfo glob ?", "*"+targstr+"+"+uidstr+"*")
if err == nil { // 如果旧数据存在就删除旧数据
- err = 民政局.db.Del("favorability", "where Userinfo = '"+info.Userinfo+"'")
+ err = 民政局.db.Del("favorability", "WHERE Userinfo = ?", info.Userinfo)
}
}
info.Favor += score
diff --git a/plugin/qqwife/function.go b/plugin/qqwife/function.go
index 73582c89f2..dd9a339288 100644
--- a/plugin/qqwife/function.go
+++ b/plugin/qqwife/function.go
@@ -93,12 +93,13 @@ func init() {
ctx.SendChain(message.Text("设置成功"))
})
// 单身技能
- engine.OnRegex(`^(娶|嫁)\[CQ:at,qq=(\d+)\]`, zero.OnlyGroup, getdb, checkSingleDog).SetBlock(true).Limit(ctxext.LimitByUser).
+ engine.OnMessage(zero.NewPattern(nil).Text(`^(娶|嫁)`).At().AsRule(), zero.OnlyGroup, getdb, checkSingleDog).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
- choice := ctx.State["regex_matched"].([]string)[1]
- fiancee, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ choice := patternParsed[0].Text()[0]
+ fiancee, _ := strconv.ParseInt(patternParsed[1].At(), 10, 64)
// 写入CD
err := 民政局.记录CD(gid, uid, "嫁娶")
if err != nil {
@@ -148,26 +149,31 @@ func init() {
}
choicetext = "\n今天你的群老公是"
}
+ favor, err = 民政局.更新好感度(uid, fiancee, 1+rand.Intn(5))
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:", err))
+ }
// 请大家吃席
ctx.SendChain(
message.Text(sendtext[0][rand.Intn(len(sendtext[0]))]),
message.At(uid),
message.Text(choicetext),
- message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
+ message.Image("https://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
"(", fiancee, ")哒",
+ "(", fiancee, ")哒\n当前你们好感度为", favor,
),
)
})
// NTR技能
- engine.OnRegex(`^当(\[CQ:at,qq=(\d+)\]\s?|(\d+))的小三`, zero.OnlyGroup, getdb, checkMistress).SetBlock(true).Limit(ctxext.LimitByUser).
+ engine.OnMessage(zero.NewPattern(nil).Text(`^当`).At().Text(`的小三`).AsRule(), zero.OnlyGroup, getdb, checkMistress).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
- fid := ctx.State["regex_matched"].([]string)
- fiancee, _ := strconv.ParseInt(fid[2]+fid[3], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ fiancee, _ := strconv.ParseInt(patternParsed[1].At(), 10, 64)
// 写入CD
err := 民政局.记录CD(gid, uid, "NTR")
if err != nil {
@@ -239,7 +245,7 @@ func init() {
message.Text(sendtext[2][rand.Intn(len(sendtext[2]))]),
message.At(uid),
message.Text("今天你的群"+choicetext+"是"),
- message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
+ message.Image("https://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
@@ -248,12 +254,13 @@ func init() {
)
})
// 做媒技能
- engine.OnRegex(`^做媒\s?\[CQ:at,qq=(\d+)\]\s?\[CQ:at,qq=(\d+)\]`, zero.OnlyGroup, zero.AdminPermission, getdb, checkMatchmaker).SetBlock(true).Limit(ctxext.LimitByUser).
+ engine.OnMessage(zero.NewPattern(nil).Text(`做媒`).At().At().AsRule(), zero.OnlyGroup, zero.AdminPermission, getdb, checkMatchmaker).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
- gayOne, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
- gayZero, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ gayOne, _ := strconv.ParseInt(patternParsed[1].At(), 10, 64)
+ gayZero, _ := strconv.ParseInt(patternParsed[2].At(), 10, 64)
// 写入CD
err := 民政局.记录CD(gid, uid, "做媒")
if err != nil {
@@ -303,7 +310,7 @@ func init() {
message.Text("恭喜你成功撮合了一对CP\n\n"),
message.At(gayOne),
message.Text("今天你的群老婆是"),
- message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(gayZero, 10)+"&s=640").Add("cache", 0),
+ message.Image("https://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(gayZero, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(gayZero), "]",
@@ -367,18 +374,16 @@ func (sql *婚姻登记) 判断CD(gid, uid int64, model string, cdtime float64)
if err != nil {
return false, err
}
- limitID := "where GroupID is " + strconv.FormatInt(gid, 10) +
- " and UserID is " + strconv.FormatInt(uid, 10) +
- " and ModeID is '" + model + "'"
- if !sql.db.CanFind("cdsheet", limitID) {
+ limitID := "WHERE GroupID = ? AND UserID = ? AND ModeID = ?"
+ if !sql.db.CanFind("cdsheet", limitID, gid, uid, model) {
// 没有记录即不用比较
return true, nil
}
cdinfo := cdsheet{}
- _ = sql.db.Find("cdsheet", &cdinfo, limitID)
+ _ = sql.db.Find("cdsheet", &cdinfo, limitID, gid, uid, model)
if time.Since(time.Unix(cdinfo.Time, 0)).Hours() > cdtime {
// 如果CD已过就删除
- err = sql.db.Del("cdsheet", limitID)
+ err = sql.db.Del("cdsheet", limitID, gid, uid, model)
return true, err
}
return false, nil
@@ -399,23 +404,22 @@ func (sql *婚姻登记) 离婚休妻(gid, wife int64) error {
sql.Lock()
defer sql.Unlock()
gidstr := "group" + strconv.FormatInt(gid, 10)
- wifestr := strconv.FormatInt(wife, 10)
- return sql.db.Del(gidstr, "where target = "+wifestr)
+ return sql.db.Del(gidstr, "WHERE target = ?", wife)
}
func (sql *婚姻登记) 离婚休夫(gid, husband int64) error {
sql.Lock()
defer sql.Unlock()
gidstr := "group" + strconv.FormatInt(gid, 10)
- husbandstr := strconv.FormatInt(husband, 10)
- return sql.db.Del(gidstr, "where user = "+husbandstr)
+ return sql.db.Del(gidstr, "WHERE user = ?", husband)
}
// 注入判断 是否单身条件
func checkSingleDog(ctx *zero.Ctx) bool {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
- fiancee, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ fiancee, err := strconv.ParseInt(patternParsed[1].At(), 10, 64)
if err != nil {
ctx.SendChain(message.Text("额,你的target好像不存在?"))
return false
@@ -481,7 +485,8 @@ func checkSingleDog(ctx *zero.Ctx) bool {
func checkMistress(ctx *zero.Ctx) bool {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
- fiancee, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ fiancee, err := strconv.ParseInt(patternParsed[1].At(), 10, 64)
if err != nil {
ctx.SendChain(message.Text("额,你的target好像不存在?"))
return false
@@ -577,12 +582,13 @@ func checkDivorce(ctx *zero.Ctx) bool {
func checkMatchmaker(ctx *zero.Ctx) bool {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
- gayOne, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
+ patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
+ gayOne, err := strconv.ParseInt(patternParsed[1].At(), 10, 64)
if err != nil {
ctx.SendChain(message.Text("额,攻方好像不存在?"))
return false
}
- gayZero, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
+ gayZero, err := strconv.ParseInt(patternParsed[2].At(), 10, 64)
if err != nil {
ctx.SendChain(message.Text("额,受方好像不存在?"))
return false
diff --git a/plugin/qzone/qzone.go b/plugin/qzone/qzone.go
index 166ebd5636..fdfcf22bdd 100644
--- a/plugin/qzone/qzone.go
+++ b/plugin/qzone/qzone.go
@@ -31,7 +31,7 @@ const (
agreeStatus
disagreeStatus
loveTag = "表白"
- faceURL = "http://q4.qlogo.cn/g?b=qq&nk=%v&s=640"
+ faceURL = "https://q4.qlogo.cn/g?b=qq&nk=%v&s=640"
anonymousURL = "https://gitcode.net/anto_july/avatar/-/raw/master/%v.png"
)
diff --git a/plugin/robbery/robbery.go b/plugin/robbery/robbery.go
index 0db5a28fbd..e393161ba0 100644
--- a/plugin/robbery/robbery.go
+++ b/plugin/robbery/robbery.go
@@ -21,8 +21,8 @@ import (
)
type robberyRepo struct {
- db *sql.Sqlite
sync.RWMutex
+ db sql.Sqlite
}
type robberyRecord struct {
@@ -32,12 +32,10 @@ type robberyRecord struct {
}
func init() {
- police := &robberyRepo{
- db: &sql.Sqlite{},
- }
+ var police robberyRepo
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
- Brief: "打劫别人的ATRI币",
+ Brief: "打劫别人的钱包",
Help: "- 打劫[对方Q号|@对方QQ]\n" +
"1. 受害者钱包少于1000不能被打劫\n" +
"2. 打劫成功率 40%\n" +
@@ -58,7 +56,7 @@ func init() {
}),
))
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- police.db.DBPath = engine.DataFolder() + "robbery.db"
+ police.db = sql.New(engine.DataFolder() + "robbery.db")
err := police.db.Open(time.Hour)
if err == nil {
// 创建CD表
@@ -74,7 +72,7 @@ func init() {
})
// 打劫功能
- engine.OnRegex(`^打劫\s?(\[CQ:at,qq=(\d+)\]|(\d+))`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
+ engine.OnRegex(`^打劫\s?(\[CQ:at,(?:\S*,)?qq=(\d+)(?:,\S*)?\]|(\d+))`, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
uid := ctx.Event.UserID
fiancee := ctx.State["regex_matched"].([]string)
@@ -90,8 +88,13 @@ func init() {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
- if !ok {
- ctx.SendChain(message.Text("你已经打劫过了/对方已经被打劫过了"))
+
+ if ok == 1 {
+ ctx.SendChain(message.Text("对方今天已经被打劫了,给人家留点后路吧"))
+ return
+ }
+ if ok >= 2 {
+ ctx.SendChain(message.Text("你今天已经成功打劫过了,贪心没有好果汁吃!"))
return
}
@@ -116,16 +119,16 @@ func init() {
}
return
}
- userIncrMonry := math.Min(rand.Intn(victimWallet/20)+500, 10000)
- victimDecrMonry := userIncrMonry / (rand.Intn(4) + 1)
+ userIncrMoney := math.Min(rand.Intn(victimWallet/20)+500, 10000)
+ victimDecrMoney := userIncrMoney / (rand.Intn(4) + 1)
// 记录结果
- err = wallet.InsertWalletOf(victimID, -victimDecrMonry)
+ err = wallet.InsertWalletOf(victimID, -victimDecrMoney)
if err != nil {
ctx.SendChain(message.Text("[ERROR]:钱包坏掉力:\n", err))
return
}
- err = wallet.InsertWalletOf(uid, +userIncrMonry)
+ err = wallet.InsertWalletOf(uid, +userIncrMoney)
if err != nil {
ctx.SendChain(message.Text("[ERROR]:打劫失败,脏款掉入虚无\n", err))
return
@@ -137,33 +140,45 @@ func init() {
ctx.SendChain(message.At(uid), message.Text("[ERROR]:犯罪记录写入失败\n", err))
}
- ctx.SendChain(message.At(uid), message.Text("打劫成功,钱包增加:", userIncrMonry, "ATRI币"))
- ctx.SendChain(message.At(victimID), message.Text("保险公司对您进行了赔付,您实际损失:", victimDecrMonry, "ATRI币"))
+ ctx.SendChain(message.At(uid), message.Text("打劫成功,钱包增加:", userIncrMoney, wallet.GetWalletName()))
+ ctx.SendChain(message.At(victimID), message.Text("保险公司对您进行了赔付,您实际损失:", victimDecrMoney, wallet.GetWalletName()))
})
}
-func (sql *robberyRepo) getRecord(victimID, uid int64) (ok bool, err error) {
+// ok==0 可以打劫;ok==1 程序错误 or 受害者进入CD;ok==2 用户进入CD; ok==3 用户和受害者都进入CD;
+func (sql *robberyRepo) getRecord(victimID, uid int64) (ok int, err error) {
sql.Lock()
defer sql.Unlock()
// 创建群表格
err = sql.db.Create("criminal_record", &robberyRecord{})
if err != nil {
- return false, err
+ return 1, err
}
- limitID := "where victim_id is " + strconv.FormatInt(victimID, 10) +
- " or user_id is " + strconv.FormatInt(uid, 10)
- if !sql.db.CanFind("criminal_record", limitID) {
+ // 拼接查询SQL
+ limitID := "WHERE victim_id = ? OR user_id = ?"
+ if !sql.db.CanFind("criminal_record", limitID, victimID, uid) {
// 没有记录即不用比较
- return true, nil
- }
- cdinfo := robberyRecord{}
- _ = sql.db.Find("criminal_record", &cdinfo, limitID)
- if time.Now().Format("2006/01/02") != cdinfo.Time {
- // // 如果跨天了就删除
- err = sql.db.Del("criminal_record", limitID)
- return true, err
+ return 0, nil
}
- return false, nil
+ cdInfo := robberyRecord{}
+
+ err = sql.db.FindFor("criminal_record", &cdInfo, limitID, func() error {
+ if time.Now().Format("2006/01/02") != cdInfo.Time {
+ // // 如果跨天了就删除
+ err = sql.db.Del("criminal_record", limitID, victimID, uid)
+ return nil
+ }
+ // 俩个if是为了保证,重复打劫同一个人,ok == 3
+ if cdInfo.UserID == uid {
+ ok += 2
+ }
+ if cdInfo.VictimID == victimID {
+ // lint 不允许使用 ok += 1
+ ok++
+ }
+ return nil
+ }, victimID, uid)
+ return ok, err
}
func (sql *robberyRepo) insertRecord(vid int64, uid int64) error {
diff --git a/plugin/rsshub/domain/job.go b/plugin/rsshub/domain/job.go
new file mode 100644
index 0000000000..078523fa10
--- /dev/null
+++ b/plugin/rsshub/domain/job.go
@@ -0,0 +1,134 @@
+// Package domain rsshub领域逻辑
+package domain
+
+import (
+ "context"
+
+ "github.com/mmcdole/gofeed"
+ "github.com/sirupsen/logrus"
+)
+
+// syncRss 同步所有频道
+// 返回:更新的频道&订阅信息 map[int64]*RssClientView
+// 1. 获取所有频道
+// 2. 遍历所有频道,检查频道是否更新
+// 3. 如果更新,获取更新的内容,但是返回的数据
+func (repo *rssDomain) syncRss(ctx context.Context) (updated map[int64]*RssClientView, err error) {
+ updated = make(map[int64]*RssClientView)
+ // 获取所有频道
+ sources, err := repo.storage.GetSources(ctx)
+ if err != nil {
+ return
+ }
+ // 遍历所有源,获取每个channel对应的rss内容
+ rssView := make([]*RssClientView, len(sources))
+ for i, channel := range sources {
+ var feed *gofeed.Feed
+ // 从site获取rss内容
+ feed, err = repo.rssHubClient.FetchFeed(channel.RssHubFeedPath)
+ // 如果获取失败,则跳过
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub syncRss] fetch path(%+v) error: %v", channel.RssHubFeedPath, err)
+ continue
+ }
+ rv := convertFeedToRssView(0, channel.RssHubFeedPath, feed)
+ rssView[i] = rv
+ }
+ // 检查频道是否更新
+ for _, cv := range rssView {
+ if cv == nil {
+ continue
+ }
+ var needUpdate bool
+ needUpdate, err = repo.checkSourceNeedUpdate(ctx, cv.Source)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub syncRss] checkSourceNeedUpdate error: %v", err)
+ err = nil
+ continue
+ }
+ // 保存
+ logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %+v, need update(real): %v", cv.Source, needUpdate)
+ // 如果需要更新,更新channel 和 content
+ if needUpdate {
+ err = repo.storage.UpsertSource(ctx, cv.Source)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert source error: %v", err)
+ }
+ }
+ var updateChannelView = &RssClientView{Source: cv.Source, Contents: []*RssContent{}}
+ err = repo.processContentsUpdate(ctx, cv, updateChannelView)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub syncRss] processContentsUpdate error: %v", err)
+ continue
+ }
+ if len(updateChannelView.Contents) == 0 {
+ logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %s, no new content", cv.Source.RssHubFeedPath)
+ continue
+ }
+ updateChannelView.Sort()
+ updated[updateChannelView.Source.ID] = updateChannelView
+ logrus.WithContext(ctx).Debugf("[rsshub syncRss] cv %s, new contents: %v", cv.Source.RssHubFeedPath, len(updateChannelView.Contents))
+ }
+ return
+}
+
+// checkSourceNeedUpdate 检查频道是否需要更新
+func (repo *rssDomain) checkSourceNeedUpdate(ctx context.Context, source *RssSource) (needUpdate bool, err error) {
+ var sourceInDB *RssSource
+ sourceInDB, err = repo.storage.GetSourceByRssHubFeedLink(ctx, source.RssHubFeedPath)
+ if err != nil {
+ return
+ }
+ if sourceInDB == nil {
+ logrus.WithContext(ctx).Errorf("[rsshub syncRss] source not found: %v", source.RssHubFeedPath)
+ return
+ }
+ source.ID = sourceInDB.ID
+ // 检查是否需要更新到db
+ if sourceInDB.IfNeedUpdate(source) {
+ needUpdate = true
+ }
+ return
+}
+
+// processContentsUpdate 处理内容(s)更新
+func (repo *rssDomain) processContentsUpdate(ctx context.Context, cv *RssClientView, updateChannelView *RssClientView) error {
+ var err error
+ for _, content := range cv.Contents {
+ if content == nil {
+ continue
+ }
+ content.RssSourceID = cv.Source.ID
+ var existed bool
+ existed, err = repo.processContentItemUpdate(ctx, content)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err)
+ err = nil
+ continue
+ }
+ if !existed {
+ updateChannelView.Contents = append(updateChannelView.Contents, content)
+ logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %s, add new content: %v", cv.Source.RssHubFeedPath, content.Title)
+ }
+ }
+ return err
+}
+
+// processContentItemUpdate 处理单个内容更新
+func (repo *rssDomain) processContentItemUpdate(ctx context.Context, content *RssContent) (existed bool, err error) {
+ existed, err = repo.storage.IsContentHashIDExist(ctx, content.HashID)
+ if err != nil {
+ return
+ }
+ // 不需要更新&不需要发送
+ if existed {
+ return
+ }
+ // 保存
+ err = repo.storage.UpsertContent(ctx, content)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err)
+ return
+ }
+ return
+}
diff --git a/plugin/rsshub/domain/model.go b/plugin/rsshub/domain/model.go
new file mode 100644
index 0000000000..3e3e2cd662
--- /dev/null
+++ b/plugin/rsshub/domain/model.go
@@ -0,0 +1,118 @@
+package domain
+
+import (
+ "encoding/hex"
+ "hash/fnv"
+ "sort"
+ "time"
+)
+
+// ======== RSS ========[START]
+
+func genHashForFeedItem(link, guid string) string {
+ h := fnv.New32()
+ // 分三次写入数据:link、分隔符、guid
+ _, _ = h.Write([]byte(link))
+ _, _ = h.Write([]byte("||"))
+ _, _ = h.Write([]byte(guid))
+
+ encoded := hex.EncodeToString(h.Sum(nil))
+ return encoded
+}
+
+// RssClientView 频道视图
+type RssClientView struct {
+ Source *RssSource
+ Contents []*RssContent
+}
+
+// ======== RSS ========[END]
+
+// ======== DB ========[START]
+
+const (
+ tableNameRssSource = "rss_source"
+ tableNameRssContent = "rss_content"
+ tableNameRssSubscribe = "rss_subscribe"
+)
+
+// RssSource RSS频道
+type RssSource struct {
+ // Id 自增id
+ ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"`
+ // RssHubFeedPath 频道路由 用于区分rss_hub 不同的频道 例如: `/bangumi/tv/calendar/today`
+ RssHubFeedPath string `gorm:"column:rss_hub_feed_path;not null;unique;" json:"rss_hub_feed_path"`
+ // Title 频道标题
+ Title string `gorm:"column:title" json:"title"`
+ // ChannelDesc 频道描述
+ ChannelDesc string `gorm:"column:channel_desc" json:"channel_desc"`
+ // ImageURL 频道图片
+ ImageURL string `gorm:"column:image_url" json:"image_url"`
+ // Link 频道链接
+ Link string `gorm:"column:link" json:"link"`
+ // UpdatedParsed RSS页面更新时间
+ UpdatedParsed time.Time `gorm:"column:updated_parsed" json:"updated_parsed"`
+ // Mtime update time
+ Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"`
+}
+
+// TableName ...
+func (RssSource) TableName() string {
+ return tableNameRssSource
+}
+
+// IfNeedUpdate ...
+func (r RssSource) IfNeedUpdate(cmp *RssSource) bool {
+ if r.Link != cmp.Link {
+ return false
+ }
+ return r.UpdatedParsed.Unix() < cmp.UpdatedParsed.Unix()
+}
+
+// RssContent 订阅的RSS频道的推送信息
+type RssContent struct {
+ // Id 自增id
+ ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"`
+ HashID string `gorm:"column:hash_id;unique" json:"hash_id"`
+ RssSourceID int64 `gorm:"column:rss_source_id;not null" json:"rss_source_id"`
+ Title string `gorm:"column:title" json:"title"`
+ Description string `gorm:"column:description" json:"description"`
+ Link string `gorm:"column:link" json:"link"`
+ Date time.Time `gorm:"column:date" json:"date"`
+ Author string `gorm:"column:author" json:"author"`
+ Thumbnail string `gorm:"column:thumbnail" json:"thumbnail"`
+ Content string `gorm:"column:content" json:"content"`
+ // Mtime update time
+ Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"`
+}
+
+// TableName ...
+func (RssContent) TableName() string {
+ return tableNameRssContent
+}
+
+// Sort ... order by Date desc
+func (r *RssClientView) Sort() {
+ sort.Slice(r.Contents, func(i, j int) bool {
+ return r.Contents[i].Date.Unix() > r.Contents[j].Date.Unix()
+ })
+}
+
+// RssSubscribe 订阅关系表:群组-RSS频道
+type RssSubscribe struct {
+ // Id 自增id
+ ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"`
+ // 订阅群组
+ GroupID int64 `gorm:"column:group_id;not null;uniqueIndex:uk_sid_gid"`
+ // 订阅频道
+ RssSourceID int64 `gorm:"column:rss_source_id;not null;uniqueIndex:uk_sid_gid"`
+ // Mtime update time
+ Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"`
+}
+
+// TableName ...
+func (RssSubscribe) TableName() string {
+ return tableNameRssSubscribe
+}
+
+// ======== DB ========[END]
diff --git a/plugin/rsshub/domain/rawFeed.go b/plugin/rsshub/domain/rawFeed.go
new file mode 100644
index 0000000000..0b29fe32b6
--- /dev/null
+++ b/plugin/rsshub/domain/rawFeed.go
@@ -0,0 +1,101 @@
+package domain
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "time"
+
+ "github.com/FloatTech/floatbox/web"
+ "github.com/mmcdole/gofeed"
+ "github.com/sirupsen/logrus"
+)
+
+var (
+ // RSSHubMirrors RSSHub镜像站地址列表,第一个为默认地址
+ rssHubMirrors = []string{
+ "https://rsshub.rssforever.com",
+ "https://rss.injahow.cn",
+ }
+)
+
+// RssHubClient rss hub client (http)
+type RssHubClient struct {
+ *http.Client
+}
+
+// FetchFeed 获取rss feed信息
+func (c *RssHubClient) FetchFeed(path string) (feed *gofeed.Feed, err error) {
+ var data []byte
+ // 遍历 rssHubMirrors,直到获取成功
+ for _, mirror := range rssHubMirrors {
+ data, err = web.RequestDataWith(c.Client, mirror+path, "GET", "", web.RandUA(), nil)
+ if err == nil && len(data) > 0 {
+ break
+ }
+ }
+ if err != nil {
+ logrus.Errorf("[rsshub FetchFeed] fetch feed error: %v", err)
+ return nil, err
+ }
+ if len(data) == 0 {
+ logrus.Errorf("[rsshub FetchFeed] fetch feed error: data is empty")
+ return nil, errors.New("feed data is empty")
+ }
+ feed, err = gofeed.NewParser().Parse(bytes.NewBuffer(data))
+ if err != nil {
+ return
+ }
+ return
+}
+
+func convertFeedToRssView(channelID int64, cPath string, feed *gofeed.Feed) (view *RssClientView) {
+ var imgURL string
+ if feed.Image != nil {
+ imgURL = feed.Image.URL
+ }
+ view = &RssClientView{
+ Source: &RssSource{
+ ID: channelID,
+ RssHubFeedPath: cPath,
+ Title: feed.Title,
+ ChannelDesc: feed.Description,
+ ImageURL: imgURL,
+ Link: feed.Link,
+ UpdatedParsed: *(feed.UpdatedParsed),
+ Mtime: time.Now(),
+ },
+ // 不用定长,后面可能会过滤一些元素再append
+ Contents: []*RssContent{},
+ }
+ // convert feed items to rss content
+ for _, item := range feed.Items {
+ if item.Link == "" || item.Title == "" {
+ continue
+ }
+ var thumbnail string
+ if item.Image != nil {
+ thumbnail = item.Image.URL
+ }
+ var publishedParsed = item.PublishedParsed
+ if publishedParsed == nil {
+ publishedParsed = &time.Time{}
+ }
+ aus, _ := json.Marshal(item.Authors)
+ view.Contents = append(view.Contents, &RssContent{
+ ID: 0,
+ HashID: genHashForFeedItem(item.Link, item.GUID),
+ RssSourceID: channelID,
+ Title: item.Title,
+ Description: item.Description,
+ Link: item.Link,
+ Date: *publishedParsed,
+ Author: string(aus),
+ Thumbnail: thumbnail,
+ Content: item.Content,
+ Mtime: time.Now(),
+ })
+ }
+ return
+}
diff --git a/plugin/rsshub/domain/rssHub.go b/plugin/rsshub/domain/rssHub.go
new file mode 100644
index 0000000000..1f652032ad
--- /dev/null
+++ b/plugin/rsshub/domain/rssHub.go
@@ -0,0 +1,192 @@
+package domain
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/jinzhu/gorm"
+ "github.com/sirupsen/logrus"
+)
+
+// RssDomain RssRepo定义
+type RssDomain interface {
+ // Subscribe 订阅Rss频道
+ Subscribe(ctx context.Context, gid int64, route string) (rv *RssClientView, isChannelExisted,
+ isSubExisted bool, err error)
+ // Unsubscribe 取消订阅Rss频道
+ Unsubscribe(ctx context.Context, gid int64, route string) (err error)
+ // GetSubscribedChannelsByGroupID 获取群组订阅的Rss频道
+ GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) (rv []*RssClientView, err error)
+ // Sync 同步Rss频道
+ // 返回群组-频道推送视图 map[群组]推送内容数组
+ Sync(ctx context.Context) (groupView map[int64][]*RssClientView, err error)
+}
+
+// rssDomain RssRepo定义
+type rssDomain struct {
+ storage RepoStorage
+ rssHubClient *RssHubClient
+}
+
+// NewRssDomain 新建RssDomain,调用方保证单例模式
+func NewRssDomain(dbPath string) (RssDomain, error) {
+ return newRssDomain(dbPath)
+}
+
+func newRssDomain(dbPath string) (*rssDomain, error) {
+ if _, err := os.Stat(dbPath); err != nil || os.IsNotExist(err) {
+ // 生成文件
+ f, err := os.Create(dbPath)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ }
+ orm, err := gorm.Open("sqlite3", dbPath)
+ if err != nil {
+ logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err)
+ panic(err)
+ }
+ repo := &rssDomain{
+ storage: &repoStorage{orm: orm},
+ rssHubClient: &RssHubClient{Client: http.DefaultClient},
+ }
+ err = repo.storage.initDB()
+ if err != nil {
+ logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err)
+ panic(err)
+ }
+ return repo, nil
+}
+
+// Subscribe QQ群订阅Rss频道
+func (repo *rssDomain) Subscribe(ctx context.Context, gid int64, feedPath string) (
+ rv *RssClientView, isChannelExisted, isSubExisted bool, err error) {
+ // 验证
+ feed, err := repo.rssHubClient.FetchFeed(feedPath)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] add source error: %v", err)
+ return
+ }
+ logrus.WithContext(ctx).Infof("[rsshub Subscribe] try get source success: %v", len(feed.Title))
+ // 新建source结构体
+ rv = convertFeedToRssView(0, feedPath, feed)
+ feedChannel, err := repo.storage.GetSourceByRssHubFeedLink(ctx, feedPath)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query source by feedPath error: %v", err)
+ return
+ }
+ // 如果已经存在
+ if feedChannel != nil {
+ logrus.WithContext(ctx).Warningf("[rsshub Subscribe] source existed: %v", feedChannel)
+ isChannelExisted = true
+ } else {
+ // 不存在的情况,要把更新时间置空,保证下一次同步时能够更新
+ rv.Source.UpdatedParsed = time.Time{}
+ }
+ // 保存
+ err = repo.storage.UpsertSource(ctx, rv.Source)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] save source error: %v", err)
+ return
+ }
+ logrus.Infof("[rsshub Subscribe] save/update source success %v", rv.Source.ID)
+ // 添加群号到订阅
+ subscribe, err := repo.storage.GetSubscribeByID(ctx, gid, rv.Source.ID)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query subscribe error: %v", err)
+ return
+ }
+ logrus.WithContext(ctx).Infof("[rsshub Subscribe] query subscribe success: %v", subscribe)
+ // 如果已经存在,直接返回
+ if subscribe != nil {
+ isSubExisted = true
+ logrus.WithContext(ctx).Infof("[rsshub Subscribe] subscribe existed: %v", subscribe)
+ return
+ }
+ // 如果不存在,保存
+ err = repo.storage.CreateSubscribe(ctx, gid, rv.Source.ID)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] save subscribe error: %v", err)
+ return
+ }
+ logrus.WithContext(ctx).Infof("[rsshub Subscribe] success: %v", len(rv.Contents))
+ return
+}
+
+// Unsubscribe 群组取消订阅
+func (repo *rssDomain) Unsubscribe(ctx context.Context, gid int64, feedPath string) (err error) {
+ existedSubscribes, ifExisted, err := repo.storage.GetIfExistedSubscribe(ctx, gid, feedPath)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query sub by route error: %v", err)
+ return errors.New("数据库错误")
+ }
+ logrus.WithContext(ctx).Infof("[rsshub Subscribe] query source by route success: %v", existedSubscribes)
+ // 如果不存在订阅关系,直接返回
+ if !ifExisted || existedSubscribes == nil {
+ logrus.WithContext(ctx).Infof("[rsshub Subscribe] source existed: %v", ifExisted)
+ return errors.New("频道不存在")
+ }
+ err = repo.storage.DeleteSubscribe(ctx, existedSubscribes.ID)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] delete source error: %v", err)
+ return errors.New("删除失败")
+ }
+ // 查询是否还有群订阅这个频道
+ subscribesNeedsToDel, err := repo.storage.GetSubscribesBySource(ctx, feedPath)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query source by route error: %v", err)
+ return
+ }
+ // 没有群订阅的时候,把频道删除
+ if len(subscribesNeedsToDel) == 0 {
+ err = repo.storage.DeleteSource(ctx, existedSubscribes.RssSourceID)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Subscribe] delete source error: %v", err)
+ return errors.New("清除频道信息失败")
+ }
+ }
+ return
+}
+
+// GetSubscribedChannelsByGroupID 获取群对应的订阅的频道信息
+func (repo *rssDomain) GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) ([]*RssClientView, error) {
+ channels, err := repo.storage.GetSubscribedChannelsByGroupID(ctx, gid)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub GetSubscribedChannelsByGroupID] GetSubscribedChannelsByGroupID error: %v", err)
+ return nil, err
+ }
+ rv := make([]*RssClientView, len(channels))
+ logrus.WithContext(ctx).Infof("[rsshub GetSubscribedChannelsByGroupID] query subscribe success: %v", len(channels))
+ for i, cn := range channels {
+ rv[i] = &RssClientView{
+ Source: cn,
+ }
+ }
+ return rv, nil
+}
+
+// Sync 同步任务,按照群组订阅情况做好map切片
+func (repo *rssDomain) Sync(ctx context.Context) (groupView map[int64][]*RssClientView, err error) {
+ groupView = make(map[int64][]*RssClientView)
+ // 获取所有Rss频道
+ // 获取所有频道
+ updatedViews, err := repo.syncRss(ctx)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Sync] sync rss feed error: %v", err)
+ return
+ }
+ logrus.WithContext(ctx).Infof("[rsshub Sync] updated channels: %v", len(updatedViews))
+ subscribes, err := repo.storage.GetSubscribes(ctx)
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub Sync] get subscribes error: %v", err)
+ return
+ }
+ for _, subscribe := range subscribes {
+ groupView[subscribe.GroupID] = append(groupView[subscribe.GroupID], updatedViews[subscribe.RssSourceID])
+ }
+ return
+}
diff --git a/plugin/rsshub/domain/rssHub_test.go b/plugin/rsshub/domain/rssHub_test.go
new file mode 100644
index 0000000000..451795931d
--- /dev/null
+++ b/plugin/rsshub/domain/rssHub_test.go
@@ -0,0 +1,105 @@
+package domain
+
+import (
+ "context"
+ "encoding/json"
+ "testing"
+)
+
+func TestNewRssDomain(t *testing.T) {
+ dm, err := newRssDomain("rsshub.db")
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+ if dm == nil {
+ t.Fatal("domain is nil")
+ }
+}
+
+//var testRssHubChannelUrl = "https://rsshub.rssforever.com/bangumi/tv/calendar/today"
+
+var dm, _ = newRssDomain("rsshub.db")
+
+func TestSub(t *testing.T) {
+ testCases := []struct {
+ name string
+ feedLink string
+ gid int64
+ }{
+ {
+ name: "test1",
+ feedLink: "/bangumi/tv/calendar/today",
+ gid: 99,
+ },
+ {
+ name: "test2",
+ feedLink: "/go-weekly",
+ gid: 99,
+ },
+ {
+ name: "test3",
+ feedLink: "/go-weekly",
+ gid: 123,
+ },
+ {
+ name: "test3",
+ feedLink: "/go-weekly",
+ gid: 321,
+ },
+ {
+ name: "test3",
+ feedLink: "/go-weekly",
+ gid: 4123,
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ ctx := context.Background()
+ channel, ifExisted, ifSub, err := dm.Subscribe(ctx, tc.gid, tc.feedLink)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+ t.Logf("[TEST] add sub res: %+v,%+v,%+v\n", channel, ifExisted, ifSub)
+ res, ext, err := dm.storage.GetIfExistedSubscribe(ctx, tc.gid, tc.feedLink)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+ t.Logf("[TEST] if exist: %+v,%+v", res, ext)
+ channels, err := dm.GetSubscribedChannelsByGroupID(ctx, 2)
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+ t.Logf("[TEST] 2 channels: %+v", channels)
+ // del
+ //err = dm.Unsubscribe(ctx, tc.gid, tc.feedLink)
+ //if err != nil {
+ // t.Fatal(err)
+ // return
+ //}
+ //res, ext, err = dm.storage.GetIfExistedSubscribe(ctx, tc.gid, tc.feedLink)
+ //if err != nil {
+ // t.Fatal(err)
+ // return
+ //}
+ //t.Logf("[TEST] after del: %+v,%+v", res, ext)
+ //if res != nil || ext {
+ // t.Fatal("delete failed")
+ //}
+
+ })
+ }
+}
+
+func Test_SyncFeed(t *testing.T) {
+ feed, err := dm.Sync(context.Background())
+ if err != nil {
+ t.Fatal(err)
+ return
+ }
+ rs, _ := json.Marshal(feed)
+ t.Logf("[Test] feed: %+v", string(rs))
+}
diff --git a/plugin/rsshub/domain/storageImpl.go b/plugin/rsshub/domain/storageImpl.go
new file mode 100644
index 0000000000..842411defb
--- /dev/null
+++ b/plugin/rsshub/domain/storageImpl.go
@@ -0,0 +1,47 @@
+package domain
+
+import "context"
+
+// RepoContent RSS 推送信息存储接口
+type RepoContent interface {
+ // UpsertContent 添加一条文章
+ UpsertContent(ctx context.Context, content *RssContent) error
+ // DeleteSourceContents 删除订阅源的所有文章,返回被删除的文章数
+ DeleteSourceContents(ctx context.Context, channelID int64) (int64, error)
+ // IsContentHashIDExist hash id 对应的文章是否已存在
+ IsContentHashIDExist(ctx context.Context, hashID string) (bool, error)
+}
+
+// RepoSource RSS 订阅源存储接口
+type RepoSource interface {
+ // UpsertSource 添加一个订阅源
+ UpsertSource(ctx context.Context, rfc *RssSource) error
+ // GetSources 获取所有订阅源信息
+ GetSources(ctx context.Context) ([]RssSource, error)
+ // GetSourceByRssHubFeedLink 通过 rssHub 的 feed 链接获取订阅源信息
+ GetSourceByRssHubFeedLink(ctx context.Context, url string) (*RssSource, error)
+ // DeleteSource 删除一个订阅源
+ DeleteSource(ctx context.Context, fID int64) error
+}
+
+// RepoSubscribe RSS 订阅存储接口
+type RepoSubscribe interface {
+ // CreateSubscribe 添加一个订阅
+ CreateSubscribe(ctx context.Context, gid, rssSourceID int64) error
+ // DeleteSubscribe 删除一个订阅
+ DeleteSubscribe(ctx context.Context, subscribeID int64) error
+ // GetSubscribeByID 获取一个订阅
+ GetSubscribeByID(ctx context.Context, gid int64, subscribeID int64) (*RssSubscribe, error)
+ // GetSubscribes 获取全部订阅
+ GetSubscribes(ctx context.Context) ([]*RssSubscribe, error)
+}
+
+// RepoMultiQuery 多表查询接口
+type RepoMultiQuery interface {
+ // GetSubscribesBySource 获取一个源对应的所有订阅群组
+ GetSubscribesBySource(ctx context.Context, feedPath string) ([]*RssSubscribe, error)
+ // GetIfExistedSubscribe 判断一个群组是否已订阅了一个源
+ GetIfExistedSubscribe(ctx context.Context, gid int64, feedPath string) (*RssSubscribe, bool, error)
+ // GetSubscribedChannelsByGroupID 获取该群所有的订阅
+ GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) ([]*RssSource, error)
+}
diff --git a/plugin/rsshub/domain/storageRepo.go b/plugin/rsshub/domain/storageRepo.go
new file mode 100644
index 0000000000..8698a8d996
--- /dev/null
+++ b/plugin/rsshub/domain/storageRepo.go
@@ -0,0 +1,280 @@
+package domain
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/jinzhu/gorm"
+ "github.com/sirupsen/logrus"
+)
+
+// RepoStorage 定义RepoStorage接口
+type RepoStorage interface {
+ RepoContent
+ RepoSource
+ RepoSubscribe
+ RepoMultiQuery
+ initDB() error
+}
+
+// repoStorage db struct for rss
+type repoStorage struct {
+ orm *gorm.DB
+}
+
+// initDB ...
+func (s *repoStorage) initDB() (err error) {
+ err = s.orm.AutoMigrate(&RssSource{}, &RssContent{}, &RssSubscribe{}).Error
+ if err != nil {
+ logrus.Errorf("[rsshub initDB] error: %v", err)
+ return err
+ }
+ return nil
+ // s.orm.LogMode(true)
+}
+
+// GetSubscribesBySource Impl
+func (s *repoStorage) GetSubscribesBySource(ctx context.Context, feedPath string) ([]*RssSubscribe, error) {
+ logrus.WithContext(ctx).Infof("[rsshub GetSubscribesBySource] feedPath: %s", feedPath)
+ rs := make([]*RssSubscribe, 0)
+ err := s.orm.Model(&RssSubscribe{}).Joins(fmt.Sprintf("%s left join %s on %s.rss_source_id=%s.id", tableNameRssSubscribe, tableNameRssSource, tableNameRssSubscribe, tableNameRssSource)).
+ Where("rss_source.rss_hub_feed_path = ?", feedPath).Select("rss_subscribe.*").Find(&rs).Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, nil
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub GetSubscribesBySource] error: %v", err)
+ return nil, err
+ }
+ return rs, nil
+}
+
+// GetIfExistedSubscribe Impl
+func (s *repoStorage) GetIfExistedSubscribe(ctx context.Context, gid int64, feedPath string) (*RssSubscribe, bool, error) {
+ rs := RssSubscribe{}
+
+ err := s.orm.Table(tableNameRssSubscribe).
+ Select("rss_subscribe.id, rss_subscribe.group_id, rss_subscribe.rss_source_id, rss_subscribe.mtime").
+ Joins(fmt.Sprintf("INNER JOIN %s ON %s.rss_source_id=%s.id",
+ tableNameRssSource, tableNameRssSubscribe, tableNameRssSource)).
+ Where("rss_source.rss_hub_feed_path = ? AND rss_subscribe.group_id = ?", feedPath, gid).Scan(&rs).Error
+
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, false, nil
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub GetIfExistedSubscribe] error: %v", err)
+ return nil, false, err
+ }
+ if rs.ID == 0 {
+ return nil, false, nil
+ }
+ return &rs, true, nil
+}
+
+// ==================== RepoSource ==================== [Start]
+
+// UpsertSource Impl
+func (s *repoStorage) UpsertSource(ctx context.Context, source *RssSource) (err error) {
+ // Update columns to default value on `id` conflict
+ querySource := &RssSource{RssHubFeedPath: source.RssHubFeedPath}
+ err = s.orm.First(querySource, "rss_hub_feed_path = ?", querySource.RssHubFeedPath).Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ err = s.orm.Create(source).Omit("id").Error
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub] add source error: %v", err)
+ return
+ }
+ }
+ return
+ }
+ source.ID = querySource.ID
+ logrus.WithContext(ctx).Infof("[rsshub] update source: %+v", source.UpdatedParsed)
+ err = s.orm.Model(&source).Where(&RssSource{ID: source.ID}).
+ Updates(&RssSource{
+ Title: source.Title,
+ ChannelDesc: source.ChannelDesc,
+ ImageURL: source.ImageURL,
+ Link: source.Link,
+ UpdatedParsed: source.UpdatedParsed,
+ Mtime: time.Now(),
+ }).Error
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub] update source error: %v", err)
+ return
+ }
+ logrus.Println("[rsshub] add source success: ", source.ID)
+ return nil
+}
+
+// GetSources Impl
+func (s *repoStorage) GetSources(ctx context.Context) (sources []RssSource, err error) {
+ sources = []RssSource{}
+ err = s.orm.Find(&sources, "id > 0").Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, errors.New("source not found")
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub] get sources error: %v", err)
+ return
+ }
+ logrus.WithContext(ctx).Infof("[rsshub] get sources success: %d", len(sources))
+ return
+}
+
+// GetSourceByRssHubFeedLink Impl
+func (s *repoStorage) GetSourceByRssHubFeedLink(ctx context.Context, rssHubFeedLink string) (source *RssSource, err error) {
+ source = &RssSource{RssHubFeedPath: rssHubFeedLink}
+ err = s.orm.Take(source, source).Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, nil
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub] get source error: %v", err)
+ return
+ }
+ return
+}
+
+// DeleteSource Impl
+func (s *repoStorage) DeleteSource(ctx context.Context, fID int64) (err error) {
+ err = s.orm.Delete(&RssSource{}, "id = ?", fID).Error
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSource: %v", err)
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return errors.New("source not found")
+ }
+ return
+ }
+ return nil
+}
+
+// ==================== RepoSource ==================== [End]
+
+// ==================== RepoContent ==================== [Start]
+
+// UpsertContent Impl
+func (s *repoStorage) UpsertContent(ctx context.Context, content *RssContent) (err error) {
+ // check params
+ if content == nil {
+ err = errors.New("content is nil")
+ return
+ }
+ // check params.RssHubFeedPath and params.HashID
+ if content.RssSourceID < 0 || content.HashID == "" || content.Title == "" {
+ err = errors.New("content.RssSourceID or content.HashID or content.Title is empty")
+ return
+ }
+ err = s.orm.Create(content).Omit("id").Error
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.UpsertContent: %v", err)
+ return
+ }
+ return
+}
+
+// DeleteSourceContents Impl
+func (s *repoStorage) DeleteSourceContents(ctx context.Context, channelID int64) (rows int64, err error) {
+ err = s.orm.Delete(&RssSubscribe{}).Where(&RssSubscribe{RssSourceID: channelID}).Error
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSourceContents: %v", err)
+ return
+ }
+ return
+}
+
+// IsContentHashIDExist Impl
+func (s *repoStorage) IsContentHashIDExist(ctx context.Context, hashID string) (bool, error) {
+ wanted := &RssContent{HashID: hashID}
+ err := s.orm.Take(wanted, wanted).Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return false, nil
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.IsContentHashIDExist: %v", err)
+ return false, err
+ }
+ return true, nil
+}
+
+// ==================== RepoContent ==================== [End]
+
+// ==================== RepoSubscribe ==================== [Start]
+
+// CreateSubscribe Impl
+func (s *repoStorage) CreateSubscribe(ctx context.Context, gid, rssSourceID int64) (err error) {
+ // check subscribe
+ if rssSourceID < 0 || gid == 0 {
+ err = errors.New("gid or rssSourceID is empty")
+ return
+ }
+ err = s.orm.Create(&RssSubscribe{GroupID: gid, RssSourceID: rssSourceID}).Omit("id").Error
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.CreateSubscribe: %v", err)
+ return
+ }
+ return
+}
+
+// DeleteSubscribe Impl
+func (s *repoStorage) DeleteSubscribe(ctx context.Context, subscribeID int64) (err error) {
+ err = s.orm.Delete(&RssSubscribe{}, "id = ?", subscribeID).Error
+ if err != nil {
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSubscribe error: %v", err)
+ return
+ }
+ return
+}
+
+// GetSubscribeByID Impl
+func (s *repoStorage) GetSubscribeByID(ctx context.Context, gid int64, subscribeID int64) (res *RssSubscribe, err error) {
+ res = &RssSubscribe{}
+ err = s.orm.First(res, &RssSubscribe{GroupID: gid, RssSourceID: subscribeID}).Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ return nil, nil
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribeByID: %v", err)
+ return nil, err
+ }
+ return
+}
+
+// GetSubscribedChannelsByGroupID Impl
+func (s *repoStorage) GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) (res []*RssSource, err error) {
+ res = make([]*RssSource, 0)
+ err = s.orm.Model(&RssSource{}).
+ Joins(fmt.Sprintf("join %s on rss_source_id=%s.id", tableNameRssSubscribe, tableNameRssSource)).Where("rss_subscribe.group_id = ?", gid).
+ Select("rss_source.*").
+ Find(&res).
+ Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ err = nil
+ return
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribedChannelsByGroupID: %v", err)
+ return
+ }
+ return
+}
+
+// GetSubscribes Impl
+func (s *repoStorage) GetSubscribes(ctx context.Context) (res []*RssSubscribe, err error) {
+ res = make([]*RssSubscribe, 0)
+ err = s.orm.Find(&res).Error
+ if err != nil {
+ if errors.Is(err, gorm.ErrRecordNotFound) {
+ err = nil
+ return
+ }
+ logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribes: %v", err)
+ return
+ }
+ return
+}
+
+// ==================== RepoSubscribe ==================== [End]
diff --git a/plugin/rsshub/main.go b/plugin/rsshub/main.go
new file mode 100644
index 0000000000..ff4cce1088
--- /dev/null
+++ b/plugin/rsshub/main.go
@@ -0,0 +1,152 @@
+// Package rsshub rss_hub订阅插件
+package rsshub
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ ctrl "github.com/FloatTech/zbpctrl"
+ "github.com/FloatTech/zbputils/control"
+ zbpCtxExt "github.com/FloatTech/zbputils/ctxext"
+ "github.com/sirupsen/logrus"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub/domain"
+)
+
+// 初始化 repo
+var (
+ rssRepo domain.RssDomain
+ initErr error
+ regexpForSQL = regexp.MustCompile(`[\^<>\[\]%&\*\(\)\{\}\|\=]|(union\s+select|update\s+|delete\s+|drop\s+|truncate\s+|insert\s+|exec\s+|declare\s+)`)
+)
+
+var (
+ // 注册插件
+ engine = control.Register("rsshub", &ctrl.Options[*zero.Ctx]{
+ // 默认不启动
+ DisableOnDefault: false,
+ Brief: "RssHub订阅姬",
+ // 详细帮助
+ Help: "RssHub订阅姬desu~ \n" +
+ "支持的详细订阅列表文档可见:\n" +
+ "https://rsshub.netlify.app/ \n" +
+ "- 添加rsshub订阅-/bookfere/weekly \n" +
+ "- 删除rsshub订阅-/bookfere/weekly \n" +
+ "- 查看rsshub订阅列表 \n" +
+ "- rsshub同步 \n" +
+ "Tips: 定时刷新rsshub订阅信息需要配合job一起使用, 全局只需要设置一个, 无视响应状态推送, 下为例子\n" +
+ "记录在\"@every 10m\"触发的指令)\n" +
+ "rsshub同步",
+ // 插件数据存储路径
+ PrivateDataFolder: "rsshub",
+ OnEnable: func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text("RssHub订阅姬现在启动了哦"))
+ },
+ OnDisable: func(ctx *zero.Ctx) {
+ ctx.SendChain(message.Text("RssHub订阅姬现在关闭了哦"))
+ },
+ }).ApplySingle(zbpCtxExt.DefaultSingle)
+)
+
+// init 命令路由
+func init() {
+ rssRepo, initErr = domain.NewRssDomain(engine.DataFolder() + "rsshub.db")
+ if initErr != nil {
+ logrus.Errorln("RssHub订阅姬:初始化失败", initErr)
+ panic(initErr)
+ }
+ engine.OnFullMatch("rsshub同步", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ // 群组-频道推送视图 map[群组]推送内容数组
+ groupToFeedsMap, err := rssRepo.Sync(context.Background())
+ if err != nil {
+ logrus.Errorln("rsshub同步失败", err)
+ ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text("rsshub同步失败", err))
+ return
+ }
+ // 没有更新的[群组-频道推送视图]则不推送
+ if len(groupToFeedsMap) == 0 {
+ logrus.Info("rsshub未发现更新")
+ return
+ }
+ sendRssUpdateMsg(ctx, groupToFeedsMap)
+ })
+ // 添加订阅
+ engine.OnPrefix("添加rsshub订阅-", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ routeStr := ctx.State["args"].(string)
+ input := regexpForSQL.ReplaceAllString(routeStr, "")
+ logrus.Debugf("添加rsshub订阅:raw(%s), replaced(%s)", routeStr, input)
+ rv, _, isSubExisted, err := rssRepo.Subscribe(context.Background(), ctx.Event.GroupID, input)
+ if err != nil {
+ ctx.SendChain(message.Text("RssHub订阅姬:添加失败", err.Error()))
+ return
+ }
+ if isSubExisted {
+ ctx.SendChain(message.Text("RssHub订阅姬:已存在,更新成功"))
+ } else {
+ ctx.SendChain(message.Text("RssHub订阅姬:添加成功\n", rv.Source.Title))
+ }
+ // 添加成功,发送订阅源快照
+ msg, err := newRssDetailsMsg(ctx, rv)
+ if len(msg) == 0 || err != nil {
+ ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text("RssHub推送错误", err))
+ return
+ }
+ if id := ctx.Send(msg).ID(); id == 0 {
+ ctx.SendChain(message.Text("ERROR: 发送订阅源快照失败,可能被风控了"))
+ }
+ })
+ engine.OnPrefix("删除rsshub订阅-", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ routeStr := ctx.State["args"].(string)
+ input := regexpForSQL.ReplaceAllString(routeStr, "")
+ logrus.Debugf("删除rsshub订阅:raw(%s), replaced(%s)", routeStr, input)
+ err := rssRepo.Unsubscribe(context.Background(), ctx.Event.GroupID, input)
+ if err != nil {
+ ctx.SendChain(message.Text("RssHub订阅姬:删除失败 ", err.Error()))
+ return
+ }
+ ctx.SendChain(message.Text(fmt.Sprintf("RssHub订阅姬:删除%s成功", input)))
+ })
+ engine.OnFullMatch("查看rsshub订阅列表", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) {
+ rv, err := rssRepo.GetSubscribedChannelsByGroupID(context.Background(), ctx.Event.GroupID)
+ if err != nil {
+ ctx.SendChain(message.Text("RssHub订阅姬:查询失败 ", err.Error()))
+ return
+ }
+ // 添加成功,发送订阅源信息
+ msg, err := newRssSourcesMsg(ctx, rv)
+ if err != nil {
+ ctx.SendChain(message.Text("RssHub订阅姬:查询失败 ", err.Error()))
+ return
+ }
+ if len(msg) == 0 {
+ ctx.SendChain(message.Text("ん? 没有订阅的频道哦~"))
+ return
+ }
+ ctx.SendChain(msg...)
+ })
+}
+
+// sendRssUpdateMsg 发送Rss更新消息
+func sendRssUpdateMsg(ctx *zero.Ctx, groupToFeedsMap map[int64][]*domain.RssClientView) {
+ for groupID, views := range groupToFeedsMap {
+ logrus.Infof("RssHub插件在群 %d 触发推送检查", groupID)
+ for _, view := range views {
+ if view == nil || len(view.Contents) == 0 {
+ continue
+ }
+ msg, err := newRssDetailsMsg(ctx, view)
+ if len(msg) == 0 || err != nil {
+ ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text(rssHubPushErrMsg, err))
+ continue
+ }
+ logrus.Infof("RssHub插件在群 %d 开始推送 %s", groupID, view.Source.Title)
+ ctx.SendGroupMessage(groupID, message.Text(fmt.Sprintf("%s\n该RssHub频道下有更新了哦~", view.Source.Title)))
+ if res := ctx.SendGroupForwardMessage(groupID, msg); !res.Exists() {
+ ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text(rssHubPushErrMsg))
+ }
+ }
+ }
+}
diff --git a/plugin/rsshub/view.go b/plugin/rsshub/view.go
new file mode 100644
index 0000000000..1bc2ae7c9b
--- /dev/null
+++ b/plugin/rsshub/view.go
@@ -0,0 +1,100 @@
+package rsshub
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/FloatTech/floatbox/binary"
+ "github.com/FloatTech/zbputils/img/text"
+ "github.com/sirupsen/logrus"
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub/domain"
+)
+
+const (
+ rssHubPushErrMsg = "RssHub推送错误"
+)
+
+// formatRssViewToMessagesSlice 格式化RssClientView为消息切片
+func formatRssViewToMessagesSlice(view *domain.RssClientView) ([]message.Message, error) {
+ // 取前20条
+ cts := view.Contents
+ if len(cts) > 20 {
+ cts = cts[:20]
+ }
+ // 2n+1条消息
+ fv := make([]message.Message, len(cts)*2+1)
+ // 订阅源头图
+ toastPic, err := text.RenderToBase64(fmt.Sprintf("%s\n\n\n%s\n\n\n更新时间:%v\n\n\n",
+ view.Source.Title, view.Source.Link, view.Source.UpdatedParsed.Local().Format(time.DateTime)),
+ text.SakuraFontFile, 1200, 40)
+ if err != nil {
+ return nil, err
+ }
+ fv[0] = message.Message{message.Image("base64://" + binary.BytesToString(toastPic))}
+ // 元素信息
+ for idx, item := range cts {
+ contentStr := fmt.Sprintf("%s\n\n\n", item.Title)
+ // Date为空时不显示
+ if !item.Date.IsZero() {
+ contentStr += fmt.Sprintf("更新时间:\n%v\n", item.Date.Local().Format(time.DateTime))
+ }
+ var content []byte
+ content, err = text.RenderToBase64(contentStr, text.SakuraFontFile, 1200, 40)
+ if err != nil {
+ logrus.WithError(err).Error("RssHub订阅姬渲染图片失败")
+ continue
+ }
+ itemMessagePic := message.Message{message.Image("base64://" + binary.BytesToString(content))}
+ fv[2*idx+1] = itemMessagePic
+ fv[2*idx+2] = message.Message{message.Text(item.Link)}
+ }
+ return fv, nil
+}
+
+// newRssSourcesMsg Rss订阅源列表
+func newRssSourcesMsg(ctx *zero.Ctx, view []*domain.RssClientView) (message.Message, error) {
+ var msgSlice []message.Message
+ // 生成消息
+ for _, v := range view {
+ if v == nil {
+ continue
+ }
+ item, err := formatRssViewToMessagesSlice(v)
+ if err != nil {
+ return nil, err
+ }
+ msgSlice = append(msgSlice, item...)
+ }
+ // 伪造一个发送者为RssHub订阅姬的消息节点
+ msg := make(message.Message, len(msgSlice))
+ for i, item := range msgSlice {
+ msg[i] = fakeSenderForwardNode(ctx.Event.SelfID, item...)
+ }
+ return msg, nil
+}
+
+// newRssDetailsMsg Rss订阅源详情(包含文章信息列表)
+func newRssDetailsMsg(ctx *zero.Ctx, view *domain.RssClientView) (message.Message, error) {
+ // 生成消息
+ msgSlice, err := formatRssViewToMessagesSlice(view)
+ if err != nil {
+ return nil, err
+ }
+ // 伪造一个发送者为RssHub订阅姬的消息节点
+ msg := make(message.Message, len(msgSlice))
+ for i, item := range msgSlice {
+ msg[i] = fakeSenderForwardNode(ctx.Event.SelfID, item...)
+ }
+ return msg, nil
+}
+
+// fakeSenderForwardNode 伪造一个发送者为RssHub订阅姬的消息节点
+func fakeSenderForwardNode(userID int64, msgs ...message.Segment) message.Segment {
+ return message.CustomNode(
+ "RssHub订阅姬",
+ userID,
+ msgs)
+}
diff --git a/plugin/saucenao/searcher.go b/plugin/saucenao/searcher.go
index 9afb04bad4..4c129bcc67 100644
--- a/plugin/saucenao/searcher.go
+++ b/plugin/saucenao/searcher.go
@@ -22,7 +22,6 @@ import (
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
- "github.com/FloatTech/zbputils/img/pool"
)
const (
@@ -73,20 +72,10 @@ func init() { // 插件主体
for i := range illust.ImageUrls {
f := file.BOTPATH + "/" + illust.Path(i)
n := name + "_p" + strconv.Itoa(i)
- var m *pool.Image
if file.IsNotExist(f) {
- m, err = pool.GetImage(n)
- if err == nil {
- imgs = append(imgs, message.Image(m.String()))
- continue
- }
logrus.Debugln("[saucenao]开始下载", n)
logrus.Debugln("[saucenao]urls:", illust.ImageUrls)
err1 := illust.DownloadToCache(i)
- if err1 == nil {
- m.SetFile(f)
- _, _ = m.Push(ctxext.SendToSelf(ctx), ctxext.GetMessage(ctx))
- }
if err1 != nil {
logrus.Debugln("[saucenao]下载err:", err1)
}
diff --git a/plugin/score/draw.go b/plugin/score/draw.go
index 33cc58b451..acb9568e0a 100644
--- a/plugin/score/draw.go
+++ b/plugin/score/draw.go
@@ -11,6 +11,7 @@ import (
"sync"
"time"
+ "github.com/FloatTech/AnimeAPI/wallet"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/gg"
"github.com/FloatTech/imgfactory"
@@ -84,8 +85,8 @@ func drawScore16(a *scdata) (image.Image, error) {
return nil, err
}
canvas.DrawStringAnchored(hourWord, 350, 280, 0, 0)
- canvas.DrawStringAnchored("ATRI币 + "+strconv.Itoa(a.inc), 350, 350, 0, 0)
- canvas.DrawStringAnchored("当前ATRI币:"+strconv.Itoa(a.score), 350, 400, 0, 0)
+ canvas.DrawStringAnchored(wallet.GetWalletName()+" + "+strconv.Itoa(a.inc), 350, 350, 0, 0)
+ canvas.DrawStringAnchored("当前"+wallet.GetWalletName()+":"+strconv.Itoa(a.score), 350, 400, 0, 0)
canvas.DrawStringAnchored("LEVEL: "+strconv.Itoa(getrank(a.level)), 350, 450, 0, 0)
// draw Info(Time,etc.)
getTime := time.Now().Format("2006-01-02 15:04:05")
@@ -161,8 +162,8 @@ func drawScore15(a *scdata) (image.Image, error) {
if err = canvas.LoadFontFace(text.FontFile, float64(back.Bounds().Size().X)*0.04); err != nil {
return nil, err
}
- canvas.DrawString(a.nickname+fmt.Sprintf(" ATRI币+%d", a.inc), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.3)
- canvas.DrawString("当前ATRI币:"+strconv.FormatInt(int64(a.score), 10), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.4)
+ canvas.DrawString(a.nickname+fmt.Sprintf(" %s+%d", wallet.GetWalletName(), a.inc), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.3)
+ canvas.DrawString("当前"+wallet.GetWalletName()+":"+strconv.FormatInt(int64(a.score), 10), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.4)
canvas.DrawString("LEVEL:"+strconv.FormatInt(int64(a.rank), 10), float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.5)
canvas.DrawRectangle(float64(back.Bounds().Size().X)*0.1, float64(back.Bounds().Size().Y)*1.55, float64(back.Bounds().Size().X)*0.6, float64(back.Bounds().Size().Y)*0.1)
canvas.SetRGB255(150, 150, 150)
@@ -247,8 +248,8 @@ func drawScore17(a *scdata) (image.Image, error) {
if err = canvas.ParseFontFace(data, 20); err != nil {
return nil, err
}
- canvas.DrawStringAnchored("ATRI币 + "+strconv.Itoa(a.inc), 40, float64(imgDY-90), 0, 0)
- canvas.DrawStringAnchored("当前ATRI币:"+strconv.Itoa(a.score), 40, float64(imgDY-60), 0, 0)
+ canvas.DrawStringAnchored(wallet.GetWalletName()+" + "+strconv.Itoa(a.inc), 40, float64(imgDY-90), 0, 0)
+ canvas.DrawStringAnchored("当前"+wallet.GetWalletName()+":"+strconv.Itoa(a.score), 40, float64(imgDY-60), 0, 0)
canvas.DrawStringAnchored("LEVEL: "+strconv.Itoa(getrank(a.level)), 40, float64(imgDY-30), 0, 0)
// Draw Info(Time, etc.)
@@ -285,7 +286,6 @@ func drawScore17b2(a *scdata) (img image.Image, err error) {
if err != nil {
return
}
-
back, err := gg.LoadImage(a.picfile)
if err != nil {
return
@@ -294,30 +294,27 @@ func drawScore17b2(a *scdata) (img image.Image, err error) {
bx, by := float64(back.Bounds().Dx()), float64(back.Bounds().Dy())
sc := 1280 / bx
-
- colors := gg.TakeColor(back, 3)
+ var colors []color.RGBA
canvas := gg.NewContext(1280, 1280*int(by)/int(bx))
-
cw, ch := float64(canvas.W()), float64(canvas.H())
sch := ch * 6 / 10
var blurback, scbackimg, backshadowimg, avatarimg, avatarbackimg, avatarshadowimg, whitetext, blacktext image.Image
- var wg sync.WaitGroup
- wg.Add(8)
+ wg := &sync.WaitGroup{}
+ wg.Add(7)
+ scback := gg.NewContext(canvas.W(), canvas.H())
+
+ scback.ScaleAbout(sc, sc, cw/2, ch/2)
+ scback.DrawImageAnchored(back, canvas.W()/2, canvas.H()/2, 0.5, 0.5)
+ scback.Identity()
+ colors = gg.TakeColor(scback.Image(), 3)
go func() {
defer wg.Done()
- scback := gg.NewContext(canvas.W(), canvas.H())
- scback.ScaleAbout(sc, sc, cw/2, ch/2)
- scback.DrawImageAnchored(back, canvas.W()/2, canvas.H()/2, 0.5, 0.5)
- scback.Identity()
- go func() {
- defer wg.Done()
- blurback = imaging.Blur(scback.Image(), 20)
- }()
+ blurback = imaging.Blur(scback.Image(), 20)
scbackimg = rendercard.Fillet(scback.Image(), 12)
}()
@@ -477,7 +474,7 @@ func customtext(a *scdata, fontdata []byte, cw, ch, aw float64, textcolor color.
return
}
- canvas.DrawStringAnchored("ATRI币 + "+strconv.Itoa(a.inc), ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4+tempfh, 0, 0.5)
+ canvas.DrawStringAnchored(wallet.GetWalletName()+" + "+strconv.Itoa(a.inc), ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4+tempfh, 0, 0.5)
canvas.DrawStringAnchored("EXP + 1", ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4+tempfh+canvas.FontHeight(), 0, 1)
err = canvas.ParseFontFace(fontdata, (ch-sch)/2/4)
@@ -485,7 +482,7 @@ func customtext(a *scdata, fontdata []byte, cw, ch, aw float64, textcolor color.
return
}
- canvas.DrawStringAnchored("你有 "+strconv.Itoa(a.score)+" 枚ATRI币", ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4*3, 0, 0.5)
+ canvas.DrawStringAnchored("你有 "+strconv.Itoa(a.score)+" 枚"+wallet.GetWalletName(), ((cw-scw)-(cw/3-scw/2))/8, (ch-sch)/2+sch/4*3, 0, 0.5)
img = canvas.Image()
return
diff --git a/plugin/score/model.go b/plugin/score/model.go
index e0e21d8a4f..95eaadb57d 100644
--- a/plugin/score/model.go
+++ b/plugin/score/model.go
@@ -2,6 +2,7 @@ package score
import (
"os"
+ "sync"
"time"
"github.com/jinzhu/gorm"
@@ -11,7 +12,10 @@ import (
var sdb *scoredb
// scoredb 分数数据库
-type scoredb gorm.DB
+type scoredb struct {
+ db *gorm.DB
+ scoremu sync.Mutex
+}
// scoretable 分数结构体
type scoretable struct {
@@ -52,25 +56,31 @@ func initialize(dbpath string) *scoredb {
panic(err)
}
gdb.AutoMigrate(&scoretable{}).AutoMigrate(&signintable{})
- return (*scoredb)(gdb)
+ return &scoredb{
+ db: gdb,
+ }
}
// Close ...
func (sdb *scoredb) Close() error {
- db := (*gorm.DB)(sdb)
+ db := sdb.db
return db.Close()
}
// GetScoreByUID 取得分数
func (sdb *scoredb) GetScoreByUID(uid int64) (s scoretable) {
- db := (*gorm.DB)(sdb)
+ sdb.scoremu.Lock()
+ defer sdb.scoremu.Unlock()
+ db := sdb.db
db.Model(&scoretable{}).FirstOrCreate(&s, "uid = ? ", uid)
return s
}
// InsertOrUpdateScoreByUID 插入或更新分数
func (sdb *scoredb) InsertOrUpdateScoreByUID(uid int64, score int) (err error) {
- db := (*gorm.DB)(sdb)
+ sdb.scoremu.Lock()
+ defer sdb.scoremu.Unlock()
+ db := sdb.db
s := scoretable{
UID: uid,
Score: score,
@@ -91,14 +101,18 @@ func (sdb *scoredb) InsertOrUpdateScoreByUID(uid int64, score int) (err error) {
// GetSignInByUID 取得签到次数
func (sdb *scoredb) GetSignInByUID(uid int64) (si signintable) {
- db := (*gorm.DB)(sdb)
+ sdb.scoremu.Lock()
+ defer sdb.scoremu.Unlock()
+ db := sdb.db
db.Model(&signintable{}).FirstOrCreate(&si, "uid = ? ", uid)
return si
}
// InsertOrUpdateSignInCountByUID 插入或更新签到次数
func (sdb *scoredb) InsertOrUpdateSignInCountByUID(uid int64, count int) (err error) {
- db := (*gorm.DB)(sdb)
+ sdb.scoremu.Lock()
+ defer sdb.scoremu.Unlock()
+ db := sdb.db
si := signintable{
UID: uid,
Count: count,
@@ -118,7 +132,9 @@ func (sdb *scoredb) InsertOrUpdateSignInCountByUID(uid int64, count int) (err er
}
func (sdb *scoredb) GetScoreRankByTopN(n int) (st []scoretable, err error) {
- db := (*gorm.DB)(sdb)
+ sdb.scoremu.Lock()
+ defer sdb.scoremu.Unlock()
+ db := sdb.db
err = db.Model(&scoretable{}).Order("score desc").Limit(n).Find(&st).Error
return
}
diff --git a/plugin/score/sign_in.go b/plugin/score/sign_in.go
index fd5ef0142a..3c852d150c 100644
--- a/plugin/score/sign_in.go
+++ b/plugin/score/sign_in.go
@@ -3,6 +3,7 @@ package score
import (
"encoding/base64"
+ "errors"
"io"
"math"
"math/rand"
@@ -22,13 +23,14 @@ import (
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
"github.com/golang/freetype"
+ log "github.com/sirupsen/logrus"
"github.com/wcharczuk/go-chart/v2"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
- backgroundURL = "https://iw233.cn/api.php?sort=pc"
+ backgroundURL = "https://pic.re/image"
referer = "https://weibo.com/"
signinMax = 1
// SCOREMAX 分数上限定为1200
@@ -139,13 +141,11 @@ func init() {
// 更新钱包
rank := getrank(level)
add := 1 + rand.Intn(10) + rank*5 // 等级越高获得的钱越高
- go func() {
- err = wallet.InsertWalletOf(uid, add)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- }()
+ err = wallet.InsertWalletOf(uid, add)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
alldata := &scdata{
drawedfile: drawedFile,
picfile: picFile,
@@ -158,7 +158,7 @@ func init() {
}
drawimage, err := styles[k](alldata)
if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
+ ctx.SendChain(message.Text("签到成功,但签到图生成失败,请勿重复签到:\n", err))
return
}
// done.
@@ -192,7 +192,7 @@ func init() {
}
picFile := cachePath + uidStr + time.Now().Format("20060102") + ".png"
if file.IsNotExist(picFile) {
- ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("请先签到!"))
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("签到背景加载失败"))
return
}
trySendImage(picFile, ctx)
@@ -325,7 +325,7 @@ func getrank(count int) int {
func initPic(picFile string, uid int64) (avatar []byte, err error) {
defer process.SleepAbout1sTo2s()
- avatar, err = web.GetData("http://q4.qlogo.cn/g?b=qq&nk=" + strconv.FormatInt(uid, 10) + "&s=640")
+ avatar, err = web.GetData("https://q4.qlogo.cn/g?b=qq&nk=" + strconv.FormatInt(uid, 10) + "&s=640")
if err != nil {
return
}
@@ -333,14 +333,15 @@ func initPic(picFile string, uid int64) (avatar []byte, err error) {
return
}
url, err := bilibili.GetRealURL(backgroundURL)
- if err != nil {
- return
- }
- data, err := web.RequestDataWith(web.NewDefaultClient(), url, "", referer, "", nil)
- if err != nil {
- return
+ if err == nil {
+ data, err := web.RequestDataWith(web.NewDefaultClient(), url, "", referer, "", nil)
+ if err == nil {
+ return avatar, os.WriteFile(picFile, data, 0644)
+ }
}
- return avatar, os.WriteFile(picFile, data, 0644)
+ // 获取网络图片失败,使用本地已有的图片
+ log.Error("[score:get online img error]:", err)
+ return avatar, copyImage(picFile)
}
// 使用"file:"发送图片失败后,改用base64发送
@@ -371,3 +372,47 @@ func trySendImage(filePath string, ctx *zero.Ctx) {
return
}
}
+
+// 从已有签到背景中,复制出一张图片
+func copyImage(picFile string) (err error) {
+ // 读取目录中的文件列表,并随机挑选出一张图片
+ cachePath := engine.DataFolder() + "cache/"
+ files, err := os.ReadDir(cachePath)
+ if err != nil {
+ return err
+ }
+
+ // 随机取10次图片,取到图片就break退出
+ imgNum := len(files)
+ var validFile string
+ for i := 0; i < len(files) && i < 10; i++ {
+ imgFile := files[rand.Intn(imgNum)]
+ if !imgFile.IsDir() && strings.HasSuffix(imgFile.Name(), ".png") && !strings.HasSuffix(imgFile.Name(), "signin.png") {
+ validFile = imgFile.Name()
+ break
+ }
+ }
+ if len(validFile) == 0 {
+ return errors.New("copyImage: no local image")
+ }
+ selectedFile := cachePath + validFile
+
+ // 使用 io.Copy 复制签到背景
+ srcFile, err := os.Open(selectedFile)
+ if err != nil {
+ return err
+ }
+ defer srcFile.Close()
+
+ dstFile, err := os.Create(picFile)
+ if err != nil {
+ return err
+ }
+ defer dstFile.Close()
+ _, err = io.Copy(dstFile, srcFile)
+ if err != nil {
+ return err
+ }
+
+ return err
+}
diff --git a/plugin/setutime/setu_geter.go b/plugin/setutime/setu_geter.go
index 1e8eeeeb65..f2fbf39c59 100644
--- a/plugin/setutime/setu_geter.go
+++ b/plugin/setutime/setu_geter.go
@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"strconv"
- "strings"
"sync"
"time"
@@ -25,11 +24,11 @@ import (
// Pools 图片缓冲池
type imgpool struct {
- db *sql.Sqlite
+ db sql.Sqlite
dbmu sync.RWMutex
path string
max int
- pool map[string][]*message.MessageSegment
+ pool map[string][]*message.Segment
poolmu sync.Mutex
}
@@ -45,10 +44,9 @@ func (p *imgpool) List() (l []string) {
}
var pool = &imgpool{
- db: &sql.Sqlite{},
path: pixiv.CacheDir,
max: 10,
- pool: make(map[string][]*message.MessageSegment),
+ pool: make(map[string][]*message.Segment),
}
func init() { // 插件主体
@@ -64,7 +62,7 @@ func init() { // 插件主体
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
// 如果数据库不存在则下载
- pool.db.DBPath = engine.DataFolder() + "SetuTime.db"
+ pool.db = sql.New(engine.DataFolder() + "SetuTime.db")
_, _ = engine.GetLazyData("SetuTime.db", false)
err := pool.db.Open(time.Hour)
if err != nil {
@@ -158,34 +156,22 @@ func (p *imgpool) push(ctx *zero.Ctx, imgtype string, illust *pixiv.Illust) {
if len(illust.ImageUrls) == 0 {
return
}
- u := illust.ImageUrls[0]
- n := u[strings.LastIndex(u, "/")+1 : len(u)-4]
- m, err := imagepool.GetImage(n)
- var msg message.MessageSegment
+ var msg message.Segment
f := fileutil.BOTPATH + "/" + illust.Path(0)
- if err != nil {
- if fileutil.IsNotExist(f) {
- // 下载图片
- if err := illust.DownloadToCache(0); err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- }
- m.SetFile(f)
- _, _ = m.Push(ctxext.SendToSelf(ctx), ctxext.GetMessage(ctx))
- msg = message.Image("file:///" + f)
- } else {
- msg = message.Image(m.String())
- if ctxext.SendToSelf(ctx)(msg) == 0 {
- msg = msg.Add("cache", "0")
+ if fileutil.IsNotExist(f) {
+ // 下载图片
+ if err := illust.DownloadToCache(0); err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
}
}
+ msg = message.Image("file:///" + f)
p.poolmu.Lock()
p.pool[imgtype] = append(p.pool[imgtype], &msg)
p.poolmu.Unlock()
}
-func (p *imgpool) pop(imgtype string) (msg *message.MessageSegment) {
+func (p *imgpool) pop(imgtype string) (msg *message.Segment) {
p.poolmu.Lock()
defer p.poolmu.Unlock()
if p.size(imgtype) == 0 {
@@ -229,9 +215,9 @@ func (p *imgpool) add(ctx *zero.Ctx, imgtype string, id int64) error {
if len(illust.ImageUrls) == 0 {
return errors.New("nil image url")
}
- err = imagepool.SendImageFromPool(strconv.FormatInt(illust.Pid, 10)+"_p0", illust.Path(0), func() error {
+ err = imagepool.SendImageFromPool(illust.Path(0), func(string) error {
return illust.DownloadToCache(0)
- }, ctxext.Send(ctx), ctxext.GetMessage(ctx))
+ }, ctxext.Send(ctx))
if err != nil {
return err
}
@@ -242,5 +228,5 @@ func (p *imgpool) add(ctx *zero.Ctx, imgtype string, id int64) error {
func (p *imgpool) remove(imgtype string, id int64) error {
p.dbmu.Lock()
defer p.dbmu.Unlock()
- return p.db.Del(imgtype, fmt.Sprintf("WHERE pid=%d", id))
+ return p.db.Del(imgtype, "WHERE pid = ?", id)
}
diff --git a/plugin/steam/store.go b/plugin/steam/store.go
index 3e49422188..a204d39abe 100644
--- a/plugin/steam/store.go
+++ b/plugin/steam/store.go
@@ -1,7 +1,6 @@
package steam
import (
- "strconv"
"sync"
"time"
@@ -16,7 +15,7 @@ var (
database streamDB
// 开启并检查数据库链接
getDB = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- database.db.DBPath = engine.DataFolder() + "steam.db"
+ database.db = sql.New(engine.DataFolder() + "steam.db")
err := database.db.Open(time.Hour)
if err != nil {
ctx.SendChain(message.Text("[steam] ERROR: ", err))
@@ -71,8 +70,7 @@ func (sdb *streamDB) update(dbInfo *player) error {
func (sdb *streamDB) find(steamID int64) (dbInfo player, err error) {
sdb.Lock()
defer sdb.Unlock()
- condition := "where steam_id = " + strconv.FormatInt(steamID, 10)
- err = sdb.db.Find(tableListenPlayer, &dbInfo, condition)
+ err = sdb.db.Find(tableListenPlayer, &dbInfo, "WHERE steam_id = ?", steamID)
if err == sql.ErrNullResult { // 规避没有该用户数据的报错
err = nil
}
@@ -83,8 +81,7 @@ func (sdb *streamDB) find(steamID int64) (dbInfo player, err error) {
func (sdb *streamDB) findWithGroupID(steamID int64, groupID string) (dbInfo player, err error) {
sdb.Lock()
defer sdb.Unlock()
- condition := "where steam_id = " + strconv.FormatInt(steamID, 10) + " AND target LIKE '%" + groupID + "%'"
- err = sdb.db.Find(tableListenPlayer, &dbInfo, condition)
+ err = sdb.db.Find(tableListenPlayer, &dbInfo, "WHERE steam_id = ? AND target LIKE ?", steamID, "%"+groupID+"%")
if err == sql.ErrNullResult { // 规避没有该用户数据的报错
err = nil
}
@@ -102,5 +99,5 @@ func (sdb *streamDB) findAll() (dbInfos []*player, err error) {
func (sdb *streamDB) del(steamID int64) error {
sdb.Lock()
defer sdb.Unlock()
- return sdb.db.Del(tableListenPlayer, "where steam_id = "+strconv.FormatInt(steamID, 10))
+ return sdb.db.Del(tableListenPlayer, "WHERE steam_id = ?", steamID)
}
diff --git a/plugin/tarot/tarot.go b/plugin/tarot/tarot.go
index 442313b817..420ea440eb 100644
--- a/plugin/tarot/tarot.go
+++ b/plugin/tarot/tarot.go
@@ -5,26 +5,21 @@ import (
"encoding/json"
"math/rand"
"os"
+ "path"
"strconv"
"strings"
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
- "github.com/FloatTech/floatbox/file"
- "github.com/FloatTech/floatbox/process"
- "github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
- "github.com/FloatTech/zbputils/img/pool"
"github.com/FloatTech/zbputils/img/text"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
-const bed = "https://gitcode.net/shudorcl/zbp-tarot/-/raw/master/"
-
type cardInfo struct {
Description string `json:"description"`
ReverseDescription string `json:"reverseDescription"`
@@ -48,6 +43,9 @@ var (
formationMap = make(map[string]formation, 10)
majorArcanaName = make([]string, 0, 80)
formationName = make([]string, 0, 10)
+ reverse = [...]string{"", "Reverse/"}
+ arcanaType = [...]string{"MajorArcana", "MinorArcana"}
+ minorArcanaType = [...]string{"Cups", "Pentacles", "Swords", "Wands"}
)
func init() {
@@ -61,13 +59,25 @@ func init() {
PublicDataFolder: "Tarot",
}).ApplySingle(ctxext.DefaultSingle)
- cache := engine.DataFolder() + "cache"
- _ = os.RemoveAll(cache)
- err := os.MkdirAll(cache, 0755)
- if err != nil {
- panic(err)
+ for _, r := range reverse {
+ for _, at := range arcanaType {
+ if at == "MinorArcana" {
+ for _, mat := range minorArcanaType {
+ cachePath := path.Join(engine.DataFolder(), r, at, mat)
+ err := os.MkdirAll(cachePath, 0755)
+ if err != nil {
+ panic(err)
+ }
+ }
+ } else {
+ cachePath := path.Join(engine.DataFolder(), r, at)
+ err := os.MkdirAll(cachePath, 0755)
+ if err != nil {
+ panic(err)
+ }
+ }
+ }
}
-
getTarot := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
data, err := engine.GetLazyData("tarots.json", true)
if err != nil {
@@ -108,7 +118,6 @@ func init() {
n := 1
reasons := [...]string{"您抽到的是~\n", "锵锵锵,塔罗牌的预言是~\n", "诶,让我看看您抽到了~\n"}
position := [...]string{"『正位』", "『逆位』"}
- reverse := [...]string{"", "Reverse/"}
start := 0
length := 22
if match != "" {
@@ -140,31 +149,15 @@ func init() {
if p == 1 {
description = card.ReverseDescription
}
- imgurl := bed + reverse[p] + card.ImgURL
- imgname := ""
- if p == 1 {
- imgname = reverse[p][:len(reverse[p])-1] + name
- } else {
- imgname = name
- }
- imgpath := cache + "/" + imgname + ".png"
- err := pool.SendImageFromPool("pool"+imgname, imgpath, func() error {
- data, err := web.RequestDataWith(web.NewTLS12Client(), imgurl, "GET", "gitcode.net", web.RandUA(), nil)
- if err != nil {
- return err
- }
- f, err := os.Create(imgpath)
- if err != nil {
- return err
- }
- defer f.Close()
- return os.WriteFile(f.Name(), data, 0755)
- }, ctxext.Send(ctx), ctxext.GetMessage(ctx))
+ imgurl := reverse[p] + card.ImgURL
+ data, err := engine.GetLazyData(imgurl, true)
if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
+ // ctx.SendChain(message.Text("ERROR: ", err))
+ logrus.Infof("[tarot]获取图片失败: %v", err)
+ ctx.SendChain(message.Text(reasons[rand.Intn(len(reasons))], position[p], "的『", name, "』\n其释义为: ", description))
return
}
- process.SleepAbout1sTo2s()
+ ctx.SendChain(message.ImageBytes(data))
ctx.SendChain(message.Text(reasons[rand.Intn(len(reasons))], position[p], "的『", name, "』\n其释义为: ", description))
return
}
@@ -185,20 +178,19 @@ func init() {
if p == 1 {
description = card.ReverseDescription
}
- imgurl := bed + reverse[p] + card.ImgURL
+ imgurl := reverse[p] + card.ImgURL
tarotmsg := message.Message{message.Text(reasons[rand.Intn(len(reasons))], position[p], "的『", name, "』\n")}
- var imgmsg message.MessageSegment
+ var imgmsg message.Segment
var err error
- if p == 1 {
- imgmsg, err = poolimg(ctx, imgurl, reverse[p][:len(reverse[p])-1]+name, cache)
- } else {
- imgmsg, err = poolimg(ctx, imgurl, name, cache)
- }
+ data, err := engine.GetLazyData(imgurl, true)
if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
+ // ctx.SendChain(message.Text("ERROR: ", err))
+ logrus.Infof("[tarot]获取图片失败: %v", err)
+ // return
+ } else {
+ imgmsg = message.ImageBytes(data)
+ tarotmsg = append(tarotmsg, imgmsg)
}
- tarotmsg = append(tarotmsg, imgmsg)
tarotmsg = append(tarotmsg, message.Text("\n其释义为: ", description))
msg[i] = ctxext.FakeSenderForwardNode(ctx, tarotmsg...)
}
@@ -211,14 +203,17 @@ func init() {
match := ctx.State["regex_matched"].([]string)[1]
info, ok := infoMap[match]
if ok {
- imgurl := bed + info.ImgURL
+ imgurl := info.ImgURL
var tarotmsg message.Message
- imgmsg, err := poolimg(ctx, imgurl, match, cache)
+ data, err := engine.GetLazyData(imgurl, true)
if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
+ // ctx.SendChain(message.Text("ERROR: ", err))
+ logrus.Infof("[tarot]获取图片失败: %v", err)
+ // return
+ } else {
+ imgmsg := message.ImageBytes(data)
+ tarotmsg = append(tarotmsg, imgmsg)
}
- tarotmsg = append(tarotmsg, imgmsg)
tarotmsg = append(tarotmsg, message.Text("\n", match, "的含义是~\n『正位』:", info.Description, "\n『逆位』:", info.ReverseDescription))
if id := ctx.Send(tarotmsg).ID(); id == 0 {
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
@@ -280,19 +275,18 @@ func init() {
description = card.ReverseDescription
}
var tarotmsg message.Message
- imgurl := bed + reverse[p] + card.ImgURL
- var imgmsg message.MessageSegment
+ imgurl := reverse[p] + card.ImgURL
+ var imgmsg message.Segment
var err error
- if p == 1 {
- imgmsg, err = poolimg(ctx, imgurl, reverse[p][:len(reverse[p])-1]+name, cache)
- } else {
- imgmsg, err = poolimg(ctx, imgurl, name, cache)
- }
+ data, err := engine.GetLazyData(imgurl, true)
if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
+ // ctx.SendChain(message.Text("ERROR: ", err))
+ logrus.Infof("[tarot]获取图片失败: %v", err)
+ // return
+ } else {
+ imgmsg = message.ImageBytes(data)
+ tarotmsg = append(tarotmsg, imgmsg)
}
- tarotmsg = append(tarotmsg, imgmsg)
build.WriteString(info.Represent[0][i])
build.WriteString(":")
build.WriteString(position[p])
@@ -318,37 +312,3 @@ func init() {
}
})
}
-
-func poolimg(ctx *zero.Ctx, imgurl, imgname, cache string) (msg message.MessageSegment, err error) {
- imgfile := cache + "/" + imgname + ".png"
- aimgfile := file.BOTPATH + "/" + imgfile
- m, err := pool.GetImage("pool" + imgname)
- if err == nil {
- msg = message.Image(m.String())
- if ctxext.SendToSelf(ctx)(msg) == 0 {
- msg = msg.Add("cache", "0")
- }
- return
- }
- if file.IsNotExist(aimgfile) {
- var data []byte
- data, err = web.RequestDataWith(web.NewTLS12Client(), imgurl, "GET", "gitcode.net", web.RandUA(), nil)
- if err != nil {
- return
- }
- var f *os.File
- f, err = os.Create(imgfile)
- if err != nil {
- return
- }
- defer f.Close()
- err = os.WriteFile(f.Name(), data, 0755)
- if err != nil {
- return
- }
- }
- m.SetFile(aimgfile)
- _, _ = m.Push(ctxext.SendToSelf(ctx), ctxext.GetMessage(ctx))
- msg = message.Image("file:///" + aimgfile)
- return
-}
diff --git a/plugin/thesaurus/chat.go b/plugin/thesaurus/chat.go
index 6fc9303823..268e3e8400 100644
--- a/plugin/thesaurus/chat.go
+++ b/plugin/thesaurus/chat.go
@@ -1,61 +1,32 @@
-// Package thesaurus 修改过的单纯回复插件
+// Package thesaurus 修改过的单纯回复插件, 仅@触发
package thesaurus
import (
- "bytes"
- "encoding/json"
"math/rand"
- "net/http"
- "os"
- "strconv"
"strings"
- "time"
- "github.com/FloatTech/floatbox/binary"
+ "github.com/go-ego/gse"
+ "github.com/sirupsen/logrus"
+ "gopkg.in/yaml.v3"
+
+ zero "github.com/wdvxdr1123/ZeroBot"
+ "github.com/wdvxdr1123/ZeroBot/message"
+
+ "github.com/FloatTech/AnimeAPI/kimoi"
"github.com/FloatTech/floatbox/ctxext"
- "github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/process"
- "github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
- "github.com/fumiama/jieba"
- "github.com/sirupsen/logrus"
- zero "github.com/wdvxdr1123/ZeroBot"
- "github.com/wdvxdr1123/ZeroBot/message"
- "gopkg.in/yaml.v3"
)
func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
- Brief: "词典匹配回复",
- Help: "- 切换[kimo|傲娇|可爱|🦙]词库\n- 设置词库触发概率0.x (0= 9 {
- ctx.SendChain(message.Text("ERROR: 概率越界"))
- return
- }
- n-- // 0~7
- gid := ctx.Event.GroupID
- if gid == 0 {
- gid = -ctx.Event.UserID
- }
- d := c.GetData(gid)
- err := c.SetData(gid, (d&3)|(int64(n)<<59))
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(message.Text("成功!"))
- })
- engine.OnRegex(`^设置🦙API地址\s*(http.*)\s*$`, zero.SuperUserPermission, zero.OnlyPrivate).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- alpacapiurl = ctx.State["regex_matched"].([]string)[1]
- err := os.WriteFile(alpacapifile, binary.StringToBytes(alpacapiurl), 0644)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(message.Text("成功!"))
- })
- engine.OnRegex(`^设置🦙token\s*([0-9a-f]{112})\s*$`, zero.SuperUserPermission, zero.OnlyPrivate).SetBlock(true).Handle(func(ctx *zero.Ctx) {
- alpacatoken = ctx.State["regex_matched"].([]string)[1]
- err := os.WriteFile(alpacatokenfile, binary.StringToBytes(alpacatoken), 0644)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- ctx.SendChain(message.Text("成功!"))
- })
go func() {
- data, err := engine.GetLazyData("dict.txt", false)
- if err != nil {
- panic(err)
- }
- seg, err := jieba.LoadDictionary(bytes.NewReader(data))
+ var seg gse.Segmenter
+ err := seg.LoadDictEmbed()
if err != nil {
panic(err)
}
@@ -144,21 +68,6 @@ func init() {
if err != nil {
panic(err)
}
- data, err = engine.GetLazyData("kimoi.json", false)
- if err != nil {
- panic(err)
- }
- kimomap := make(kimo, 256)
- err = json.Unmarshal(data, &kimomap)
- if err != nil {
- panic(err)
- }
- chatList := make([]string, 0, len(kimomap))
- for k := range kimomap {
- chatList = append(chatList, k)
- }
- logrus.Infoln("[thesaurus]加载", len(chatList), "条kimoi")
-
chatListD := make([]string, 0, len(sm.D))
for k := range sm.D {
chatListD = append(chatListD, k)
@@ -169,89 +78,47 @@ func init() {
}
logrus.Infoln("[thesaurus]加载", len(chatListD), "条傲娇词库", len(chatListK), "条可爱词库")
- engine.OnMessage(canmatch(tKIMO), match(chatList, seg)).
- SetBlock(false).
- Handle(randreply(kimomap))
- engine.OnMessage(canmatch(tDERE), match(chatListD, seg)).
- SetBlock(false).
- Handle(randreply(sm.D))
- engine.OnMessage(canmatch(tKAWA), match(chatListK, seg)).
- SetBlock(false).
- Handle(randreply(sm.K))
- engine.OnMessage(canmatch(tALPACA), func(_ *zero.Ctx) bool {
- return alpacapiurl != "" && alpacatoken != ""
- }).SetBlock(false).Handle(func(ctx *zero.Ctx) {
+ engine.OnMessage(zero.OnlyToMe, canmatch(tKIMO)).
+ SetBlock(false).Handle(func(ctx *zero.Ctx) {
msg := ctx.ExtractPlainText()
- if msg != "" {
- data, err := web.RequestDataWithHeaders(http.DefaultClient, alpacapiurl+"/reply", "POST",
- func(r *http.Request) error {
- r.Header.Set("Authorization", alpacatoken)
- return nil
- }, bytes.NewReader(binary.NewWriterF(func(writer *binary.Writer) {
- _ = json.NewEncoder(writer).Encode(&[]alpacamsg{{
- Name: ctx.CardOrNickName(ctx.Event.UserID),
- Message: msg,
- }})
- })))
- if err != nil {
- logrus.Warnln("[chat] 🦙 err:", err)
- return
- }
- type reply struct {
- ID int
- Msg string
- }
- m := reply{}
- err = json.Unmarshal(data, &m)
- if err != nil {
- logrus.Warnln("[chat] 🦙 unmarshal err:", err)
- return
- }
- for i := 0; i < 60; i++ {
- time.Sleep(time.Second * 4)
- data, err := web.RequestDataWithHeaders(http.DefaultClient, alpacapiurl+"/get?id="+strconv.Itoa(m.ID), "GET",
- func(r *http.Request) error {
- r.Header.Set("Authorization", alpacatoken)
- return nil
- }, nil)
- if err != nil {
- continue
- }
- err = json.Unmarshal(data, &m)
+ r, err := kimoi.Chat(msg)
+ if err == nil {
+ c := 0
+ for r.Confidence < 0.2 && c < 3 {
+ r, err = kimoi.Chat(msg)
if err != nil {
- logrus.Warnln("[chat] 🦙 unmarshal err:", err)
return
}
- if len(m.Msg) > 0 {
- ctx.Send(message.Text(m.Msg))
- }
+ c++
+ }
+ if r.Confidence < 0.2 {
return
}
+ ctx.Block()
+ ctx.SendChain(message.Text(r.Reply))
}
})
+ engine.OnMessage(zero.OnlyToMe, canmatch(tDERE), match(chatListD, &seg)).
+ SetBlock(false).
+ Handle(randreply(sm.D))
+ engine.OnMessage(zero.OnlyToMe, canmatch(tKAWA), match(chatListK, &seg)).
+ SetBlock(false).
+ Handle(randreply(sm.K))
}()
}
-type kimo = map[string][]string
-
type simai struct {
D map[string][]string `yaml:"傲娇"`
K map[string][]string `yaml:"可爱"`
}
-type alpacamsg struct {
- Name string
- Message string
-}
-
const (
tKIMO = iota
tDERE
tKAWA
- tALPACA
)
-func match(l []string, seg *jieba.Segmenter) zero.Rule {
+func match(l []string, seg *gse.Segmenter) zero.Rule {
return func(ctx *zero.Ctx) bool {
return ctxext.JiebaSimilarity(0.66, seg, func(ctx *zero.Ctx) string {
return ctx.ExtractPlainText()
@@ -273,12 +140,13 @@ func canmatch(typ int64) zero.Rule {
gid = -ctx.Event.UserID
}
d := c.GetData(gid)
- return d&3 == typ && rand.Int63n(10) <= d>>59
+ return ctx.ExtractPlainText() != "" && d&3 == typ
}
}
func randreply(m map[string][]string) zero.Handler {
return func(ctx *zero.Ctx) {
+ ctx.Block()
key := ctx.State["matched"].(string)
val := m[key]
nick := zero.BotConfig.NickName[rand.Intn(len(zero.BotConfig.NickName))]
@@ -287,6 +155,9 @@ func randreply(m map[string][]string) zero.Handler {
text = strings.ReplaceAll(text, "{me}", nick)
id := ctx.Event.MessageID
for _, t := range strings.Split(text, "{segment}") {
+ if t == "" {
+ continue
+ }
process.SleepAbout1sTo2s()
id = ctx.SendChain(message.Reply(id), message.Text(t))
}
diff --git a/plugin/tiangou/tiangou.go b/plugin/tiangou/tiangou.go
index df386a4238..07b650fcfb 100644
--- a/plugin/tiangou/tiangou.go
+++ b/plugin/tiangou/tiangou.go
@@ -19,7 +19,7 @@ type tiangou struct {
Text string `db:"text"`
}
-var db = &sql.Sqlite{}
+var db sql.Sqlite
func init() {
en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
@@ -31,7 +31,7 @@ func init() {
en.OnFullMatch("舔狗日记", fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
- db.DBPath = en.DataFolder() + "tiangou.db"
+ db = sql.New(en.DataFolder() + "tiangou.db")
_, err := en.GetLazyData("tiangou.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
diff --git a/plugin/vitsnyaru/vitsnyaru.go b/plugin/vitsnyaru/vitsnyaru.go
deleted file mode 100644
index 709bc5b471..0000000000
--- a/plugin/vitsnyaru/vitsnyaru.go
+++ /dev/null
@@ -1,141 +0,0 @@
-// Package vitsnyaru vits猫雷
-package vitsnyaru
-
-import (
- "context"
- "encoding/json"
- "fmt"
- "strings"
- "time"
-
- hf "github.com/FloatTech/AnimeAPI/huggingface"
- ctrl "github.com/FloatTech/zbpctrl"
- "github.com/FloatTech/zbputils/control"
- "github.com/tidwall/gjson"
- zero "github.com/wdvxdr1123/ZeroBot"
- "github.com/wdvxdr1123/ZeroBot/message"
-)
-
-const (
- vitsnyaruRepo = "innnky/vits-nyaru"
-)
-
-func init() { // 插件主体
- engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
- DisableOnDefault: false,
- Brief: "vits猫雷",
- Help: "- 让猫雷说 xxx",
- PrivateDataFolder: "vitsnyaru",
- })
-
- // 开启
- engine.OnPrefix(`让猫雷说`).SetBlock(true).
- Handle(func(ctx *zero.Ctx) {
- _ctx, _cancel := context.WithTimeout(context.Background(), hf.TimeoutMax*time.Second)
- defer _cancel()
- ch := make(chan []byte, 1)
-
- args := ctx.State["args"].(string)
- pushURL := fmt.Sprintf(hf.HTTPSPushPath, vitsnyaruRepo)
- statusURL := fmt.Sprintf(hf.HTTPSStatusPath, vitsnyaruRepo)
- ctx.SendChain(message.Text("少女祈祷中..."))
- var (
- pushReq hf.PushRequest
- pushRes hf.PushResponse
- statusReq hf.StatusRequest
- statusRes hf.StatusResponse
- data []byte
- )
-
- // 获取clean后的文本
- pushReq = hf.PushRequest{
- Action: hf.DefaultAction,
- Data: []interface{}{args},
- FnIndex: 1,
- }
- pushRes, err := hf.Push(pushURL, pushReq)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- statusReq = hf.StatusRequest{
- Hash: pushRes.Hash,
- }
-
- t := time.NewTicker(time.Second * 1)
- defer t.Stop()
- LOOP:
- for {
- select {
- case <-t.C:
- data, err = hf.Status(statusURL, statusReq)
- if err != nil {
- ch <- data
- break LOOP
- }
- if gjson.ParseBytes(data).Get("status").String() == hf.CompleteStatus {
- ch <- data
- break LOOP
- }
- case <-_ctx.Done():
- ch <- data
- break LOOP
- }
- }
-
- data = <-ch
- err = json.Unmarshal(data, &statusRes)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
-
- // 用clean的文本预测语音
- pushReq = hf.PushRequest{
- Action: hf.DefaultAction,
- Data: statusRes.Data.Data,
- FnIndex: 2,
- }
- pushRes, err = hf.Push(pushURL, pushReq)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
- statusReq = hf.StatusRequest{
- Hash: pushRes.Hash,
- }
-
- LOOP2:
- for {
- select {
- case <-t.C:
- data, err = hf.Status(statusURL, statusReq)
- if err != nil {
- ch <- data
- break LOOP2
- }
- if gjson.ParseBytes(data).Get("status").String() == hf.CompleteStatus {
- ch <- data
- break LOOP2
- }
- case <-_ctx.Done():
- ch <- data
- break LOOP2
- }
- }
-
- data = <-ch
- err = json.Unmarshal(data, &statusRes)
- if err != nil {
- ctx.SendChain(message.Text("ERROR: ", err))
- return
- }
-
- // 发送语音
- if len(statusRes.Data.Data) < 2 {
- ctx.SendChain(message.Text("ERROR: 未能获取语音"))
- return
- }
- ctx.SendChain(message.Record("base64://" + strings.TrimPrefix(statusRes.Data.Data[1].(string), "data:audio/wav;base64,")))
- })
-}
diff --git a/plugin/wallet/wallet.go b/plugin/wallet/wallet.go
index 9791cf824b..b44abd9ac3 100644
--- a/plugin/wallet/wallet.go
+++ b/plugin/wallet/wallet.go
@@ -4,10 +4,13 @@ package wallet
import (
"math"
"os"
+ "regexp"
"strconv"
+ "strings"
"time"
"github.com/FloatTech/AnimeAPI/wallet"
+ "github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
@@ -21,24 +24,38 @@ import (
func init() {
en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
- DisableOnDefault: false,
- Brief: "钱包",
- Help: "- 查看我的钱包\n- 查看钱包排名",
+ DisableOnDefault: false,
+ Brief: "钱包",
+ Help: "- 查看钱包排名\n" +
+ "- 设置硬币名称XX\n" +
+ "- 管理钱包余额[+金额|-金额][@xxx]\n" +
+ "- 查看我的钱包|查看钱包余额[@xxx]\n" +
+ "- 钱包转账[金额][@xxx]\n" +
+ "注:仅超级用户能“管理钱包余额”\n",
PrivateDataFolder: "wallet",
})
cachePath := en.DataFolder() + "cache/"
+ coinNameFile := en.DataFolder() + "coin_name.txt"
go func() {
_ = os.RemoveAll(cachePath)
err := os.MkdirAll(cachePath, 0755)
if err != nil {
panic(err)
}
+ // 更改硬币名称
+ var coinName string
+ if file.IsExist(coinNameFile) {
+ content, err := os.ReadFile(coinNameFile)
+ if err != nil {
+ panic(err)
+ }
+ coinName = binary.BytesToString(content)
+ } else {
+ // 旧版本数据
+ coinName = "ATRI币"
+ }
+ wallet.SetWalletName(coinName)
}()
- en.OnFullMatch("查看我的钱包").SetBlock(true).Handle(func(ctx *zero.Ctx) {
- uid := ctx.Event.UserID
- money := wallet.GetWalletOf(uid)
- ctx.SendChain(message.At(uid), message.Text("你的钱包当前有", money, "ATRI币"))
- })
en.OnFullMatch("查看钱包排名", zero.OnlyGroup).Limit(ctxext.LimitByGroup).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
@@ -62,7 +79,7 @@ func init() {
return
}
if len(st) == 0 {
- ctx.SendChain(message.Text("ERROR: 当前没人获取过ATRI币"))
+ ctx.SendChain(message.Text("ERROR: 当前没人获取过", wallet.GetWalletName()))
return
} else if len(st) > 10 {
st = st[:10]
@@ -98,7 +115,7 @@ func init() {
}
err = chart.BarChart{
Font: font,
- Title: "ATRI币排名(1天只刷新1次)",
+ Title: wallet.GetWalletName() + "排名(1天只刷新1次)",
Background: chart.Style{
Padding: chart.Box{
Top: 40,
@@ -122,4 +139,119 @@ func init() {
}
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
})
+ en.OnPrefix("设置硬币名称", zero.OnlyToMe, zero.SuperUserPermission).SetBlock(true).
+ Handle(func(ctx *zero.Ctx) {
+ coinName := strings.TrimSpace(ctx.State["args"].(string))
+ err := os.WriteFile(coinNameFile, binary.StringToBytes(coinName), 0644)
+ if err != nil {
+ ctx.SendChain(message.Text("ERROR: ", err))
+ return
+ }
+ wallet.SetWalletName(coinName)
+ ctx.SendChain(message.Text("记住啦~"))
+ })
+
+ en.OnPrefix(`管理钱包余额`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
+ Handle(func(ctx *zero.Ctx) {
+ param := strings.TrimSpace(ctx.State["args"].(string))
+
+ // 捕获修改的金额
+ re := regexp.MustCompile(`^[+-]?\d+$`)
+ amount, err := strconv.Atoi(re.FindString(param))
+ if err != nil {
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("输入的金额异常"))
+ return
+ }
+
+ // 捕获用户QQ号,只支持@事件
+ var uidStr string
+ if len(ctx.Event.Message) > 1 && ctx.Event.Message[1].Type == "at" {
+ uidStr = ctx.Event.Message[1].Data["qq"]
+ } else {
+ // 没at就修改自己的钱包
+ uidStr = strconv.FormatInt(ctx.Event.UserID, 10)
+ }
+
+ uidInt, err := strconv.ParseInt(uidStr, 10, 64)
+ if err != nil {
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("QQ号处理失败"))
+ return
+ }
+ if amount+wallet.GetWalletOf(uidInt) < 0 {
+ ctx.SendChain(message.Text("管理失败:对方钱包余额不足,扣款失败"))
+ return
+ }
+ err = wallet.InsertWalletOf(uidInt, amount)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:管理失败,钱包坏掉了:\n", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("钱包余额修改成功,已修改用户:", uidStr, "的钱包,修改金额为:", amount))
+ })
+
+ // 保留用户习惯,兼容旧语法“查看我的钱包”
+ en.OnPrefixGroup([]string{`查看钱包余额`, `查看我的钱包`}).SetBlock(true).Limit(ctxext.LimitByGroup).
+ Handle(func(ctx *zero.Ctx) {
+ param := ctx.State["args"].(string)
+ var uidStr string
+ if len(ctx.Event.Message) > 1 && ctx.Event.Message[1].Type == "at" {
+ uidStr = ctx.Event.Message[1].Data["qq"]
+ } else if param == "" {
+ uidStr = strconv.FormatInt(ctx.Event.UserID, 10)
+ }
+ uidInt, err := strconv.ParseInt(uidStr, 10, 64)
+ if err != nil {
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("QQ号处理失败"))
+ return
+ }
+ money := wallet.GetWalletOf(uidInt)
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("QQ号:", uidStr, ",的钱包有", money, wallet.GetWalletName()))
+ })
+
+ en.OnPrefix(`钱包转账`, zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByGroup).
+ Handle(func(ctx *zero.Ctx) {
+ param := strings.TrimSpace(ctx.State["args"].(string))
+
+ // 捕获修改的金额,amount扣款金额恒正(要注意符号)
+ re := regexp.MustCompile(`^[+]?\d+$`)
+ amount, err := strconv.Atoi(re.FindString(param))
+ if err != nil || amount <= 0 {
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("输入额异常,请检查金额或at是否正常"))
+ return
+ }
+
+ // 捕获用户QQ号,只支持@事件
+ var uidStr string
+ if len(ctx.Event.Message) > 1 && ctx.Event.Message[1].Type == "at" {
+ uidStr = ctx.Event.Message[1].Data["qq"]
+ } else {
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("获取被转方信息失败"))
+ return
+ }
+
+ uidInt, err := strconv.ParseInt(uidStr, 10, 64)
+ if err != nil {
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("QQ号处理失败"))
+ return
+ }
+
+ // 开始转账流程
+ if amount > wallet.GetWalletOf(ctx.Event.UserID) {
+ ctx.SendChain(message.Text("[ERROR]:钱包余额不足,转账失败"))
+ return
+ }
+
+ err = wallet.InsertWalletOf(ctx.Event.UserID, -amount)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:转账失败,扣款异常:\n", err))
+ return
+ }
+
+ err = wallet.InsertWalletOf(uidInt, amount)
+ if err != nil {
+ ctx.SendChain(message.Text("[ERROR]:转账失败,转账时银行被打劫:\n", err))
+ return
+ }
+ ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("转账成功:成功给"), message.At(uidInt), message.Text(",转账:", amount, wallet.GetWalletName()))
+ })
}
diff --git a/plugin/warframeapi/main.go b/plugin/warframeapi/main.go
index 1fb04d4eda..a11673f250 100644
--- a/plugin/warframeapi/main.go
+++ b/plugin/warframeapi/main.go
@@ -148,7 +148,7 @@ func init() {
// })
// eng.OnFullMatch(`wf订阅检测`).SetBlock(true).Handle(func(ctx *zero.Ctx) {
// rwm.Lock()
- // var msg []message.MessageSegment
+ // var msg []message.Segment
// for i, v := range gameTimes {
// nt := time.Until(v.NextTime).Seconds()
// switch {
@@ -321,9 +321,9 @@ func init() {
}
ismod := iteminfo.ModMaxRank != 0
- max := 5
- if len(sells) < max {
- max = len(sells)
+ maxCount := 5
+ if len(sells) < maxCount {
+ maxCount = len(sells)
}
sb := strings.Builder{}
if ismod {
@@ -332,13 +332,13 @@ func init() {
} else {
msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx, message.Text("请输入编号选择(30s内)\n输入c直接结束会话")))
}
- for i := 0; i < max; i++ {
+ for i := 0; i < maxCount; i++ {
// msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx,
// message.Text(fmt.Sprintf("[%d] (Rank:%d/%d) %dP - %s\n", i, sells[i].ModRank, iteminfo.ModMaxRank, sells[i].Platinum, sells[i].User.IngameName))))
sb.WriteString(fmt.Sprintf("[%d] (Rank:%d/%d) %dP - %s\n", i, sells[i].ModRank, iteminfo.ModMaxRank, sells[i].Platinum, sells[i].User.IngameName))
}
} else {
- for i := 0; i < max; i++ {
+ for i := 0; i < maxCount; i++ {
// msgs = append(msgs, ctxext.FakeSenderForwardNode(ctx,
// message.Text(fmt.Sprintf("[%d] %dP -%s\n", i, sells[i].Platinum, sells[i].User.IngameName))))
sb.WriteString(fmt.Sprintf("[%d] %dP -%s\n", i, sells[i].Platinum, sells[i].User.IngameName))
diff --git a/plugin/wenxinvilg/main.go b/plugin/wenxinvilg/main.go
index 701a7eb468..36d64f8215 100644
--- a/plugin/wenxinvilg/main.go
+++ b/plugin/wenxinvilg/main.go
@@ -31,8 +31,8 @@ const (
)
type keydb struct {
- db *sql.Sqlite
sync.RWMutex
+ db sql.Sqlite
}
// db内容
@@ -48,15 +48,11 @@ type apikey struct {
}
var (
- name = "椛椛"
- limit int
- vilginfo = &keydb{
- db: &sql.Sqlite{},
- }
- modelinfo = &keydb{
- db: &sql.Sqlite{},
- }
- dtype = [...]string{
+ name = "椛椛"
+ limit int
+ vilginfo keydb
+ modelinfo keydb
+ dtype = [...]string{
"古风", "油画", "水彩画", "卡通画", "二次元", "浮世绘", "蒸汽波艺术", "low poly", "像素风格", "概念艺术", "未来主义", "赛博朋克", "写实风格", "洛丽塔风格", "巴洛克风格", "超现实主义",
}
)
@@ -99,7 +95,7 @@ func init() { // 插件主体
}),
))
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- vilginfo.db.DBPath = engine.DataFolder() + "ernieVilg.db"
+ vilginfo.db = sql.New(engine.DataFolder() + "ernieVilg.db")
err := vilginfo.db.Open(time.Hour)
if err != nil {
ctx.SendChain(message.Text(serviceErr, err))
@@ -301,7 +297,7 @@ func init() { // 插件主体
}),
))
getmodeldb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
- modelinfo.db.DBPath = engine.DataFolder() + "ernieModel.db"
+ modelinfo.db = sql.New(engine.DataFolder() + "ernieModel.db")
err := modelinfo.db.Open(time.Hour)
if err != nil {
ctx.SendChain(message.Text(modelErr, err))
@@ -415,14 +411,14 @@ func init() { // 插件主体
minlen := 1
maxlen := 128
if mun != "" {
- max, err := strconv.Atoi(mun)
+ maxNum, err := strconv.Atoi(mun)
if err != nil {
ctx.SendChain(message.Text(modelErr, err))
return
}
- minlen = max
- if max > 128 {
- maxlen = max
+ minlen = maxNum
+ if maxNum > 128 {
+ maxlen = maxNum
}
}
keyword := ctx.State["regex_matched"].([]string)[4]
@@ -521,10 +517,10 @@ func (sql *keydb) insert(gid int64, model, akey, skey string) error {
}
// 获取group信息
groupinfo := apikey{} // 用于暂存数据
- err = sql.db.Find("groupinfo", &groupinfo, "where ID is "+strconv.FormatInt(gid, 10))
+ err = sql.db.Find("groupinfo", &groupinfo, "WHERE ID = ?", gid)
if err != nil {
// 如果该group没有注册过
- err = sql.db.Find("groupinfo", &groupinfo, "where APIKey is '"+akey+"' and SecretKey is '"+skey+"'")
+ err = sql.db.Find("groupinfo", &groupinfo, "WHERE APIKey = ? and SecretKey = ?", akey, skey)
if err == nil {
// 如果key存在过将当前的数据迁移过去
groupinfo.ID = gid
@@ -575,7 +571,7 @@ func (sql *keydb) checkGroup(gid int64, model string) (groupinfo apikey, err err
model = "文心"
}
// 先判断该群是否已经设置过key了
- if ok := sql.db.CanFind("groupinfo", "where ID is "+strconv.FormatInt(gid, 10)); !ok {
+ if ok := sql.db.CanFind("groupinfo", "WHERE ID = ?", gid); !ok {
if gid > 0 {
err = errors.New("该群没有设置过apikey,请前往https://wenxin.baidu.com/moduleApi/key获取key值后,发送指令:\n为本群设置" + model + "key [API Key] [Secret Key]\n或\n为自己设置" + model + "key [API Key] [Secret Key]")
} else {
@@ -584,7 +580,7 @@ func (sql *keydb) checkGroup(gid int64, model string) (groupinfo apikey, err err
return
}
// 获取group信息
- err = sql.db.Find("groupinfo", &groupinfo, "where ID is "+strconv.FormatInt(gid, 10))
+ err = sql.db.Find("groupinfo", &groupinfo, "WHERE ID = ?", gid)
if err != nil {
return
}
@@ -608,19 +604,16 @@ func (sql *keydb) checkGroup(gid int64, model string) (groupinfo apikey, err err
err = sql.db.Insert("groupinfo", &groupinfo)
if err == nil {
// 更新相同key的他人次数
- condition := "where not ID is " + strconv.FormatInt(gid, 10) +
- " and APIKey = '" + groupinfo.APIKey +
- "' and SecretKey = '" + groupinfo.SecretKey + "'"
otherinfo := apikey{}
var groups []int64 // 将相同的key的ID暂存
// 无视没有找到相同的key的err
- _ = sql.db.FindFor("groupinfo", &otherinfo, condition, func() error {
+ _ = sql.db.FindFor("groupinfo", &otherinfo, "WHERE ID <> ? AND APIKey = ? AND SecretKey = ?", func() error {
groups = append(groups, otherinfo.ID)
return nil
- })
+ }, gid, groupinfo.APIKey, groupinfo.SecretKey)
if len(groups) != 0 { // 如果有相同的key就更新
for _, group := range groups {
- err = sql.db.Find("groupinfo", &otherinfo, "where ID is "+strconv.FormatInt(group, 10))
+ err = sql.db.Find("groupinfo", &otherinfo, "WHERE ID = ?", group)
if err == nil {
otherinfo.Token = groupinfo.Token
otherinfo.Updatetime = groupinfo.Updatetime
@@ -644,7 +637,7 @@ func (sql *keydb) update(gid int64, sub int) error {
}
groupinfo := apikey{} // 用于暂存数据
// 获取group信息
- err = sql.db.Find("groupinfo", &groupinfo, "where ID is "+strconv.FormatInt(gid, 10))
+ err = sql.db.Find("groupinfo", &groupinfo, "WHERE ID = ?", gid)
if err != nil {
return err
}
@@ -655,19 +648,16 @@ func (sql *keydb) update(gid int64, sub int) error {
return err
}
// 更新相同key的他人次数
- condition := "where not ID is " + strconv.FormatInt(gid, 10) +
- " and APIKey = '" + groupinfo.APIKey +
- "' and SecretKey = '" + groupinfo.SecretKey + "'"
otherinfo := apikey{}
var groups []int64 // 将相同的key的ID暂存
// 无视没有找到相同的key的err
- _ = sql.db.FindFor("groupinfo", &otherinfo, condition, func() error {
+ _ = sql.db.FindFor("groupinfo", &otherinfo, "WHERE ID <> ? AND APIKey = ? AND SecretKey = ?", func() error {
groups = append(groups, otherinfo.ID)
return nil
- })
+ }, gid, groupinfo.APIKey, groupinfo.SecretKey)
if len(groups) != 0 { // 如果有相同的key就更新
for _, group := range groups {
- err = sql.db.Find("groupinfo", &otherinfo, "where ID is "+strconv.FormatInt(group, 10))
+ err = sql.db.Find("groupinfo", &otherinfo, "WHERE ID = ?", group)
if err == nil {
otherinfo.MaxLimit = groupinfo.MaxLimit
otherinfo.DayLimit = groupinfo.DayLimit
diff --git a/plugin/wordcount/main.go b/plugin/wordcount/main.go
index bb91222d71..127c7da8db 100644
--- a/plugin/wordcount/main.go
+++ b/plugin/wordcount/main.go
@@ -8,9 +8,14 @@ import (
"sort"
"strconv"
"strings"
- "sync"
"time"
+ "github.com/go-ego/gse"
+ "github.com/golang/freetype"
+ "github.com/sirupsen/logrus"
+ "github.com/tidwall/gjson"
+ "github.com/wcharczuk/go-chart/v2"
+
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file"
@@ -18,30 +23,34 @@ import (
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
- "github.com/golang/freetype"
- "github.com/sirupsen/logrus"
- "github.com/tidwall/gjson"
- "github.com/wcharczuk/go-chart/v2"
+
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
+ "github.com/wdvxdr1123/ZeroBot/utils/helper"
)
var (
re = regexp.MustCompile(`^[一-龥]+$`)
stopwords []string
+ seg gse.Segmenter
)
func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "聊天热词",
- Help: "- 热词 [群号] [消息数目]|热词 123456 1000",
+ Help: "- 热词 [消息数目]|热词 1000",
PublicDataFolder: "WordCount",
})
cachePath := engine.DataFolder() + "cache/"
+ // 读取gse内置中文词典
+ err := seg.LoadDictEmbed()
+ if err != nil {
+ panic(err)
+ }
_ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
- engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
+ engine.OnRegex(`^热词\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
_, err := engine.GetLazyData("stopwords.txt", false)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
@@ -75,17 +84,14 @@ func init() {
}
ctx.SendChain(message.Text("少女祈祷中..."))
- gid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
- p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
+ p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
if p > 10000 {
p = 10000
}
if p == 0 {
p = 1000
}
- if gid == 0 {
- gid = ctx.Event.GroupID
- }
+ gid := ctx.Event.GroupID
group := ctx.GetGroupInfo(gid, false)
if group.MemberCount == 0 {
ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获得热词呢"))
@@ -98,42 +104,22 @@ func init() {
return
}
messageMap := make(map[string]int, 256)
- msghists := make(chan *gjson.Result, 256)
- go func() {
- h := ctx.GetLatestGroupMessageHistory(gid)
- messageSeq := h.Get("messages.0.message_seq").Int()
- msghists <- &h
- for i := 1; i < int(p/20) && messageSeq != 0; i++ {
- h := ctx.GetGroupMessageHistory(gid, messageSeq)
- msghists <- &h
- messageSeq = h.Get("messages.0.message_seq").Int()
- }
- close(msghists)
- }()
- var wg sync.WaitGroup
- var mapmu sync.Mutex
- for h := range msghists {
- wg.Add(1)
- go func(h *gjson.Result) {
- for _, v := range h.Get("messages.#.message").Array() {
- tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText())
- if tex == "" {
- continue
- }
- for _, t := range ctx.GetWordSlices(tex).Get("slices").Array() {
- tex := strings.TrimSpace(t.Str)
- i := sort.SearchStrings(stopwords, tex)
- if re.MatchString(tex) && (i >= len(stopwords) || stopwords[i] != tex) {
- mapmu.Lock()
- messageMap[tex]++
- mapmu.Unlock()
- }
+ h := ctx.GetGroupMessageHistory(gid, 0, p, false)
+ h.Get("messages").ForEach(func(_, msgObj gjson.Result) bool {
+ tex := strings.TrimSpace(message.ParseMessageFromString(msgObj.Get("raw_message").Str).ExtractPlainText())
+ if tex != "" {
+ segments := seg.Segment(helper.StringToBytes(tex))
+ words := gse.ToSlice(segments, true)
+ for _, word := range words {
+ word = strings.TrimSpace(word)
+ i := sort.SearchStrings(stopwords, word)
+ if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) {
+ messageMap[word]++
}
}
- wg.Done()
- }(h)
- }
- wg.Wait()
+ }
+ return true
+ })
wc := rankByWordCount(messageMap)
if len(wc) > 20 {
diff --git a/plugin/wordle/wordle.go b/plugin/wordle/wordle.go
index 3a3615846f..491f27dee4 100644
--- a/plugin/wordle/wordle.go
+++ b/plugin/wordle/wordle.go
@@ -235,10 +235,6 @@ func newWordleGame(target string) func(string) (bool, []byte, error) {
}
}
record = append(record, s)
- if len(record) >= cap(record) {
- err = errTimesRunOut
- return
- }
}
var side = 20
var space = 10
@@ -269,6 +265,10 @@ func newWordleGame(target string) func(string) (bool, []byte, error) {
}
}
data, err = imgfactory.ToBytes(ctx.Image())
+ if len(record) >= cap(record) {
+ err = errTimesRunOut
+ return
+ }
return
}
}
diff --git a/shell.nix b/shell.nix
index b7b1aabc2e..303d0b78d3 100644
--- a/shell.nix
+++ b/shell.nix
@@ -13,7 +13,7 @@
mkGoEnv ? pkgs.mkGoEnv,
gomod2nix ? pkgs.gomod2nix,
}: let
- goEnv = mkGoEnv {pwd = ./.;};
+ goEnv = mkGoEnv { pwd = ./.; go = pkgs.go_1_20; };
in
pkgs.mkShell {
packages = [
diff --git a/winres/winres.json b/winres/winres.json
index ad66634919..329dbb5479 100644
--- a/winres/winres.json
+++ b/winres/winres.json
@@ -12,7 +12,7 @@
"0409": {
"identity": {
"name": "ZeroBot-Plugin",
- "version": "1.8.1.1998"
+ "version": "1.9.9.2250"
},
"description": "",
"minimum-os": "vista",
@@ -36,23 +36,23 @@
"#1": {
"0000": {
"fixed": {
- "file_version": "1.8.1.1998",
- "product_version": "v1.8.1",
- "timestamp": "2024-05-30T16:47:38+08:00"
+ "file_version": "1.9.9.2250",
+ "product_version": "v1.9.9",
+ "timestamp": "2025-09-10T10:40:54+08:00"
},
"info": {
"0409": {
"Comments": "OneBot plugins based on ZeroBot",
"CompanyName": "FloatTech",
"FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin",
- "FileVersion": "1.8.1.1998",
+ "FileVersion": "1.9.9.2250",
"InternalName": "",
- "LegalCopyright": "© 2020 - 2024 FloatTech. All Rights Reserved.",
+ "LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.",
"LegalTrademarks": "",
"OriginalFilename": "ZBP.EXE",
"PrivateBuild": "",
"ProductName": "ZeroBot-Plugin",
- "ProductVersion": "v1.8.1",
+ "ProductVersion": "v1.9.9",
"SpecialBuild": ""
}
}