Compare commits

..

33 Commits

Author SHA1 Message Date
源文雨
6b505d050a 🔖 v1.9.9 2025-09-10 10:41:07 +08:00
github-actions[bot]
fc9a21d2d1 Changes by create-pull-request action (#1199)
* chore: bump deps

* Change Go version in go.mod

Updated Go version from 1.23.0 to 1.20 and removed toolchain specification.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
2025-09-10 10:39:51 +08:00
github-actions[bot]
e84e44476a Changes by create-pull-request action (#1198)
* chore: bump deps

* Change Go version in go.mod

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
2025-09-10 10:39:15 +08:00
宇~
b012df4c23 feat(niuniu): 添加可自定义购买商品数量,调整商品单价 (#1189) 2025-09-10 10:33:21 +08:00
源文雨
08e02ab730 fix(aichat): adapt to 百炼 2025-09-10 10:32:30 +08:00
himawari
fb090839d6 feat(crypter): 添加语音 (#1197) 2025-09-03 17:07:09 +08:00
莫思潋
ac2d53352c feat(bilibiliparse): B站视频解析 视频上传开关 (#1196)
* add control for getVideoDownload

* fix: typo
2025-09-03 13:02:12 +08:00
github-actions[bot]
35292a69fc chore(lint): 改进代码样式 (#1192)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-09-02 13:13:22 +08:00
Kajiekazz
cd16a755d7 doc: update README.md (#1193) 2025-09-02 13:12:13 +08:00
Kajiekazz
1e7b2d3335 feat: 添加插件 crypter (#1191) 2025-09-01 22:42:42 +08:00
himawari
20d49ccf15 feat(aichat): 添加/gpt命令,直接聊天 (#1190)
*  添加大模型聊天,便于使用版本的

* 🐛 减少重复关键词

* 🎨 优化换行符

*  添加转换函数

*  添加lint优化

* 🎨 按字符切片

* 🎨 修改lint
2025-09-01 22:38:15 +08:00
github-actions[bot]
1f66f47ce6 chore: bump deps (#1188)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-08-25 11:08:05 +08:00
himawari
34f3b9ba2a feat(aiimage&aichat): add new plugin & summary of group chat (#1187) 2025-08-22 22:37:35 +08:00
Dodoj
2fa7868838 fix(gif): branch名称导致的404问题 (#1186) 2025-08-12 00:18:32 +08:00
Dodoj
b6ddda1d51 fix(kfccrazythursday): API解析 (#1184) 2025-07-25 10:31:33 +09:00
himawari
a1621f34a0 optimize(antiabuse): 添加违禁词解释 (#1183) 2025-07-23 21:24:00 +09:00
github-actions[bot]
21aa3bc49f chore: bump deps (#1182)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-20 14:33:29 +09:00
himawari
617d4f50a4 feat: airecord (#1180)
Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
2025-07-20 14:31:06 +09:00
himawari
cb0ffa0c17 feat(music): 龙珠聚合搜索 (#1179)
* 🐛 修改听歌问题

*  添加龙珠聚合搜索

* 🎨 优化聚合搜索

* 🐛 一定能点出歌

* 🎨 删除调试
2025-07-05 18:05:28 +09:00
宇~
0615993297 fix: 修复注销牛牛无法进行累加收费的问题&&优化代码 (#1178)
* fix:修复注销牛牛无法进行累加收费的问题

* 修改牛牛商店的循环条件为商品变量的长度
2025-07-02 14:49:59 +09:00
Rinai
1c0d91424a fix: 修复 niuniu 插件 bug,修改标点,添加部分注释 (#1177) 2025-06-27 17:49:50 +09:00
github-actions[bot]
c94ee365ce chore: bump deps (#1175)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-15 17:04:45 +09:00
github-actions[bot]
19e5e6636f chore: bump deps (#1173)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-15 02:03:33 +09:00
github-actions[bot]
cac3a4be81 chore(lint): 改进代码样式 (#1174)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-15 02:03:23 +09:00
源文雨
beada7f4da chore: update deps 2025-06-15 02:01:23 +09:00
github-actions[bot]
43b45ce6c5 chore: bump deps (#1172)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-01 20:32:24 +09:00
源文雨
95dd5e6b94 chore: update deps 2025-06-01 20:30:46 +09:00
源文雨
566f6ecfd5 🔖 v1.9.8 2025-06-01 18:53:06 +09:00
github-actions[bot]
5b28ad75b7 chore: bump deps (#1171)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-01 17:58:46 +09:00
源文雨
997857a558 chore: update deps 2025-06-01 17:56:37 +09:00
github-actions[bot]
4269057283 chore: bump deps (#1170)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-01 16:27:17 +09:00
源文雨
f70cab80c2 feat(aichat): add more configs 2025-06-01 15:49:03 +09:00
源文雨
609d819610 chore: sync data 2025-06-01 15:06:20 +09:00
30 changed files with 1360 additions and 201 deletions

View File

@@ -192,6 +192,18 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
- [x] 早安 | 晚安
</details>
<details>
<summary>违禁词检测</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/antiabuse"
`
- [x] 添加违禁词
- [x] 删除违禁词
- [x] 查看违禁词
</details>
<details>
<summary>ATRI</summary>
@@ -255,6 +267,8 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
- [x] 翻牌
- [x] 赞我
- [x] 群签到
- [x] [开启 | 关闭]入群验证
@@ -276,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} 是当前群群名
</details>
<details>
<summary>群应用AI声聊</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/airecord"`
- [x] 设置AI语音群号1048452984(tips机器人任意所在群聊即可)
- [x] 设置AI语音模型
- [x] 查看AI语音配置
- [x] 发送AI语音xxx
</details>
<details>
<summary>定时指令触发器</summary>
@@ -384,6 +412,18 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 设置默认限速为每 m [分钟 | 秒] n 次触发
</details>
<details>
<summary>aiimage</summary>
`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画图 [描述]
</details>
<details>
<summary>AIWife</summary>
@@ -605,6 +645,17 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 磕cp大老师 雪乃
</details>
<details>
<summary>奇怪语言加解密</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/crypter"`
- [x] 齁语加密 [文本] 或 h加密 [文本]
- [x] 齁语解密 [密文] 或 h解密 [密文]
- [x] fumo加密 [文本]
- [x] fumo解密 [文本]
</details>
<details>
<summary>今日早报</summary>
@@ -1026,6 +1077,10 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 酷我点歌[xxx]
- [x] 酷狗点歌[xxx]
- [x] qq点歌[xxx]
- [x] 咪咕点歌[xxx]
</details>
<details>
@@ -1464,7 +1519,7 @@ print("run[CQ:image,file="+j["img"]+"]")
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/word_count"`
- [x] 热词 [群号] [消息数目]|热词 123456 1000
- [x] 热词 [消息数目]|热词 1000
</details>
<details>
@@ -1580,14 +1635,20 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 设置AI聊天温度80
- [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]
- [x] 设置AI聊天(不)支持系统提示词
- [x] 设置AI聊天接口地址https://xxx
- [x] 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions
- [x] 设置AI聊天密钥xxx
- [x] 设置AI聊天模型名xxx
- [x] 设置AI聊天模型名Qwen/Qwen3-8B
- [x] 查看AI聊天系统提示词
- [x] 重置AI聊天系统提示词
- [x] 设置AI聊天系统提示词xxx
- [x] 设置AI聊天分隔符`</think>`(留空则清除)
- [x] 设置AI聊天(不)响应AT
- [x] 设置AI聊天最大长度4096
- [x] 设置AI聊天TopP 0.9
- [x] 设置AI聊天(不)以AI语音输出
- [x] 查看AI聊天配置
- [x] 重置AI聊天
- [x] 群聊总结 [消息数目]|群聊总结 1000
</details>
<details>

View File

@@ -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 // 禁用快速编辑模式

2
data

Submodule data updated: ca3652920a...6bcac0faab

8
go.mod
View File

@@ -4,7 +4,7 @@ go 1.20
require (
github.com/Baidu-AIP/golang-sdk v1.1.1
github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c
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
@@ -12,7 +12,7 @@ require (
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.20250330125231-d8be1c9d3b9c
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/Tnze/go-mc v1.20.2
@@ -22,7 +22,7 @@ require (
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-20250330125128-71ec2f7c085e
github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4
github.com/fumiama/go-base16384 v1.7.0
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3
@@ -45,7 +45,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/tidwall/gjson v1.18.0
github.com/wcharczuk/go-chart/v2 v2.1.2
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250804063440-ccc03e33ac20
gitlab.com/gomidi/midi/v2 v2.1.7
golang.org/x/image v0.24.0
golang.org/x/sys v0.30.0

18
go.sum
View File

@@ -1,8 +1,8 @@
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.20250423082452-e16339a3962c h1:bEe8VP2aHLR2NHk1BsBQFtP0XE3cxquvr0tW0CkKcDk=
github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js=
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=
@@ -17,8 +17,8 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ
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.20250330125231-d8be1c9d3b9c h1:nIybmanPvQknseVOJ+s4/m3q7EZxtqMoTy3wiiZts6E=
github.com/FloatTech/zbputils v1.7.2-0.20250330125231-d8be1c9d3b9c/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8=
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/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
@@ -59,8 +59,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-20250330125128-71ec2f7c085e h1:L/Z5N6UfpuqNIiPUrjSzmrnWj3mjd3auwl/2ctpGXNY=
github.com/fumiama/deepinfra v0.0.0-20250330125128-71ec2f7c085e/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY=
github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4 h1:cV3HXXLNudIL9rIEYt1RCgl6H4703nE3+jL4pJNsRtc=
github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4/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=
@@ -124,8 +124,8 @@ 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/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY=
@@ -199,8 +199,8 @@ github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFe
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.20250330133859-27c25d9412b5 h1:HsMcBsVpYuQv+W8pjX5WdwYROrFQP9c5Pbf4x4adDus=
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
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=

View File

@@ -5,8 +5,8 @@ schema = 3
version = "v1.1.1"
hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo="
[mod."github.com/FloatTech/AnimeAPI"]
version = "v1.7.1-0.20250423082452-e16339a3962c"
hash = "sha256-pyv242GLfslHEgQN5TgYsdpJKWzWR8qc4mJ8NXF0I8s="
version = "v1.7.1-0.20250901143505-180d33844860"
hash = "sha256-k1MlgaBGwpaqoVk+8WYfXoVLfzqyEQW5LQaJgBKlhUA="
[mod."github.com/FloatTech/floatbox"]
version = "v0.0.0-20250513111443-adba80e84e80"
hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE="
@@ -29,8 +29,8 @@ schema = 3
version = "v1.7.0"
hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU="
[mod."github.com/FloatTech/zbputils"]
version = "v1.7.2-0.20250330125231-d8be1c9d3b9c"
hash = "sha256-v2ueCLEwy6oqF8HoxodDHcNOeDPzKn+7fiYSC2L2/e4="
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="
@@ -77,8 +77,8 @@ schema = 3
version = "v1.3.0"
hash = "sha256-/sN7X8dKXQgv8J+EDzVUB+o+AY9gBC8e1C6sYhaTy1k="
[mod."github.com/fumiama/deepinfra"]
version = "v0.0.0-20250330125128-71ec2f7c085e"
hash = "sha256-O7Om4mIcBB2zdVxaeKh7qHbiG83gVLP+f8vxSF17kCw="
version = "v0.0.0-20250910022828-8cde75e137f4"
hash = "sha256-1CV8t3R91maqJztHg7whECqvS4+sxWcSvq+EyO4PyZ8="
[mod."github.com/fumiama/go-base16384"]
version = "v1.7.0"
hash = "sha256-vTAsBBYe2ISzb2Nba5E96unodZSkhMcqo6hbwR01nz8="
@@ -227,8 +227,8 @@ schema = 3
version = "v2.1.2"
hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM="
[mod."github.com/wdvxdr1123/ZeroBot"]
version = "v1.8.2-0.20250330133859-27c25d9412b5"
hash = "sha256-gT3uFTg5E0Th3r1M1vLzr0QtOjbMusqEjD/ckoBdDFc="
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="

View File

@@ -3,13 +3,13 @@
package banner
// Version ...
var Version = "v1.9.7"
var Version = "v1.9.9"
// Copyright ...
var Copyright = "© 2020 - 2025 FloatTech"
// Banner ...
var Banner = "* OneBot + ZeroBot + Golang\n" +
"* Version " + Version + " - 2025-05-14 21:52:40 +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"

View File

@@ -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")

View File

@@ -38,6 +38,8 @@ import (
_ "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" // 群管
@@ -65,6 +67,7 @@ import (
_ "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识别
@@ -81,6 +84,7 @@ import (
_ "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" // 嘉心糖发病

View File

@@ -1,6 +1,8 @@
package aichat
import (
"fmt"
"strconv"
"strings"
ctrl "github.com/FloatTech/zbpctrl"
@@ -12,17 +14,22 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
)
var cfg = newconfig()
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 {
@@ -94,3 +101,98 @@ func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) {
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 "否"
}

View File

@@ -1,22 +1,27 @@
// Package aichat OpenAI聊天
// 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 (
@@ -29,22 +34,60 @@ var (
"- 设置AI聊天温度80\n" +
"- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" +
"- 设置AI聊天(不)支持系统提示词\n" +
"- 设置AI聊天接口地址https://xxx\n" +
"- 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
"- 设置AI聊天密钥xxx\n" +
"- 设置AI聊天模型名xxx\n" +
"- 设置AI聊天模型名Qwen/Qwen3-8B\n" +
"- 查看AI聊天系统提示词\n" +
"- 重置AI聊天系统提示词\n" +
"- 设置AI聊天系统提示词xxx\n" +
"- 设置AI聊天分隔符</think>(留空则清除)\n" +
"- 设置AI聊天(不)响应AT",
"- 设置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,
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() {
@@ -74,31 +117,27 @@ func init() {
return
}
if temp <= 0 {
temp = 70 // default setting
}
if temp > 100 {
temp = 100
}
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,
float32(temp)/100, 0.9, 4096,
)
temperature, topp, maxn,
).SetExtra(&map[string]bool{
"enable_thinking": false,
})
case 1:
mod = model.NewOLLaMA(
cfg.ModelName, cfg.Separator,
float32(temp)/100, 0.9, 4096,
temperature, topp, maxn,
)
case 2:
mod = model.NewGenAI(
cfg.ModelName,
float32(temp)/100, 0.9, 4096,
temperature, topp, maxn,
)
default:
logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
@@ -125,10 +164,20 @@ func init() {
if t == "" {
continue
}
if id != nil {
id = ctx.SendChain(message.Reply(id), message.Text(t))
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 {
id = ctx.SendChain(message.Text(t))
if id != nil {
id = ctx.SendChain(message.Reply(id), message.Text(t))
} else {
id = ctx.SendChain(message.Text(t))
}
}
process.SleepAbout1sTo2s()
}
@@ -255,4 +304,242 @@ func init() {
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,
).SetExtra(&map[string]bool{
"enable_thinking": false,
})
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
}

56
plugin/aiimage/config.go Normal file
View File

@@ -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()
}

171
plugin/aiimage/main.go Normal file
View File

@@ -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)
}
})
}

134
plugin/airecord/record.go Normal file
View File

@@ -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))
})
}

View File

@@ -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 {

View File

@@ -24,8 +24,10 @@ import (
)
const (
enableHex = 0x10
unableHex = 0x7fffffff_fffffffd
enableVideoSummary = int64(0x10)
disableVideoSummary = ^enableVideoSummary
enableVideoDownload = int64(0x20)
disableVideoDownload = ^enableVideoDownload
bilibiliparseReferer = "https://www.bilibili.com"
)
@@ -92,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
}
@@ -105,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)
@@ -127,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))
@@ -136,12 +167,14 @@ func handleVideo(ctx *zero.Ctx) {
}
}
ctx.SendChain(msg...)
downLoadMsg, err := getVideoDownload(cfg, card, cachePath)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
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...)
}
ctx.SendChain(downLoadMsg...)
}
func handleDynamic(ctx *zero.Ctx) {
@@ -163,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

View File

@@ -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)
}

View File

@@ -43,8 +43,15 @@ func init() {
})
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, ctx.Event.UserID)
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).

95
plugin/crypter/fumo.go Normal file
View File

@@ -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
}

View File

@@ -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))
}

88
plugin/crypter/hou.go Normal file
View File

@@ -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 == ""
}

31
plugin/crypter/main.go Normal file
View File

@@ -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)
}

View File

@@ -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.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/main/` + name)
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.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/main/` + name)
data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/master/` + name)
if err != nil {
_ = os.Remove(target)
return "", err

View File

@@ -2,8 +2,6 @@
package kfccrazythursday
import (
"encoding/json"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
@@ -15,12 +13,6 @@ const (
crazyURL = "https://api.pearktrue.cn/api/kfc/"
)
type crazyResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Text string `json:"text"`
}
func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
@@ -34,17 +26,7 @@ func init() {
return
}
var resp crazyResponse
if err := json.Unmarshal(data, &resp); err != nil {
ctx.SendChain(message.Text("JSON解析失败: ", err))
return
}
if resp.Code != 200 {
ctx.SendChain(message.Text("API返回错误: ", resp.Msg))
return
}
ctx.SendChain(message.Text(resp.Text))
// 根据来源API修改返回方式到直接输出文本
ctx.SendChain(message.Text(string(data)))
})
}

View File

@@ -50,6 +50,7 @@ const (
"- 列出所有提醒\n" +
"- 翻牌\n" +
"- 赞我\n" +
"- 群签到\n" +
"- 对信息回复: 回应表情 [表情]\n" +
"- 设置欢迎语XXX 可选添加 [{at}] [{nickname}] [{avatar}] [{uid}] [{gid}] [{groupname}]\n" +
"- 测试欢迎语\n" +
@@ -405,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).

View File

@@ -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,12 +47,37 @@ 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.Segment {
headers := http.Header{

View File

@@ -2,9 +2,11 @@
package niuniu
import (
"errors"
"fmt"
"math/rand"
"strconv"
"strings"
"time"
"github.com/FloatTech/AnimeAPI/niu"
@@ -18,12 +20,6 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
)
type lastLength struct {
TimeLimit time.Time
Count int
Length float64
}
var (
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
@@ -47,8 +43,8 @@ var (
})
dajiaoLimiter = rate.NewManager[string](time.Second*90, 1)
jjLimiter = rate.NewManager[string](time.Second*150, 1)
jjCount = syncx.Map[string, *lastLength]{}
register = syncx.Map[string, *lastLength]{}
jjCount = syncx.Map[string, *niu.PKRecord]{}
register = syncx.Map[string, *niu.PKRecord]{}
)
func init() {
@@ -65,7 +61,7 @@ func init() {
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+1, info.UserID, info.Money, wallet.GetWalletName(), info.Length)
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 {
@@ -81,7 +77,7 @@ func init() {
for {
select {
case <-timer.C:
ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消"))
ctx.SendChain(message.At(uid), message.Text(" 超时已自动取消"))
return
case r := <-recv:
answer = r.Event.Message.String()
@@ -90,7 +86,6 @@ func init() {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
n--
msg, err := niu.Auction(gid, uid, n)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
@@ -104,11 +99,21 @@ func init() {
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 err != nil {
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) {
@@ -135,29 +140,33 @@ func init() {
cost int
scope string
description string
count int
}{
1: {"伟哥", 300, "打胶", "可以让你打胶每次都增长", 5},
2: {"媚药", 300, "打胶", "可以让你打胶每次都减少", 5},
3: {"击剑神器", 500, "jj", "可以让你每次击剑都立于不败之地", 2},
4: {"击剑神稽", 500, "jj", "可以让你每次击剑都失败", 2},
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 := range propMap {
for id := 1; id <= len(propMap); id++ {
product := propMap[id]
productInfo := fmt.Sprintf("商品%d\n商品名: %s\n商品价格: %dATRI币\n商品作用域: %s\n商品描述: %s\n使用次数:%d",
id, product.name, product.cost, product.scope, product.description, product.count)
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
}
ctx.SendChain(message.Text("输入对应序号进行购买商品"))
recv, cancel := zero.NewFutureEvent("message", 999, false, zero.CheckUser(uid), zero.CheckGroup(gid), zero.RegexRule(`^(\d+)$`)).Repeat()
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 := ""
@@ -165,17 +174,17 @@ func init() {
for {
select {
case <-timer.C:
ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消"))
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
}
if err = niu.Store(gid, uid, n); err != nil {
// 解析输入的商品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
}
@@ -196,16 +205,16 @@ func init() {
}
if time.Since(last.TimeLimit) > time.Hour {
ctx.SendChain(message.Text("时间已经过期了,牛牛已被收回!"))
ctx.SendChain(message.Text("时间已经过期了牛牛已被收回!"))
jjCount.Delete(fmt.Sprintf("%d_%d", gid, uid))
return
}
if last.Count < 4 {
ctx.SendChain(message.Text("你还没有被厥够4次呢,不能赎牛牛"))
ctx.SendChain(message.Text("你还没有被厥够4次呢不能赎牛牛"))
return
}
ctx.SendChain(message.Text("再次确认一下哦,这次赎牛牛,牛牛长度将会变成", last.Length, "cm\n还需要嘛【是|否】"))
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)
@@ -222,11 +231,11 @@ func init() {
return
}
if err := niu.Redeem(gid, uid, last.Length); err == nil {
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)))
@@ -332,7 +341,7 @@ func init() {
}
uid := ctx.Event.UserID
gid := ctx.Event.GroupID
msg, length, err := niu.JJ(gid, uid, adduser, patternParsed[0].Text()[1])
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))
@@ -341,22 +350,27 @@ func init() {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(msg))
j := fmt.Sprintf("%d_%d", gid, adduser)
count, ok := jjCount.Load(j)
var c lastLength
// 按照最后一次被jj时的时间计算超过60分钟则重置
var c niu.PKRecord
// 按照最后一次被 jj 时的时间计算超过60分钟则重置
if !ok {
c = lastLength{
// 第一次被 jj
c = niu.PKRecord{
NiuID: niuID,
TimeLimit: time.Now(),
Count: 1,
Length: length,
}
} else {
c = lastLength{
c = niu.PKRecord{
NiuID: niuID,
TimeLimit: time.Now(),
Count: count.Count + 1,
Length: count.Length,
}
// 超时了,重置
if time.Since(c.TimeLimit) > time.Hour {
c = lastLength{
c = niu.PKRecord{
NiuID: niuID,
TimeLimit: time.Now(),
Count: 1,
Length: length,
@@ -372,6 +386,9 @@ func init() {
)))
if c.Count >= 4 {
if c.Count == 6 {
return
}
id := ctx.SendPrivateMessage(adduser,
message.Text(fmt.Sprintf("你在%d群里已经被厥冒烟了快去群里赎回你原本的牛牛!\n发送:`赎牛牛`即可!", gid)))
if id == 0 {
@@ -386,8 +403,8 @@ func init() {
key := fmt.Sprintf("%d_%d", gid, uid)
data, ok := register.Load(key)
switch {
case !ok || time.Since(data.TimeLimit) > time.Hour*12:
data = &lastLength{
case !ok || time.Since(data.TimeLimit) > time.Hour*24:
data = &niu.PKRecord{
TimeLimit: time.Now(),
Count: 1,
}
@@ -396,6 +413,7 @@ func init() {
ctx.SendChain(message.Text("你的钱不够你注销牛牛了,这次注销需要", data.Count*50, wallet.GetWalletName()))
return
}
data.Count++
}
register.Store(key, data)
msg, err := niu.Cancel(gid, uid)

View File

@@ -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
}

View File

@@ -8,7 +8,6 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/go-ego/gse"
@@ -40,7 +39,7 @@ func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "聊天热词",
Help: "- 热词 [群号] [消息数目]|热词 123456 1000",
Help: "- 热词 [消息数目]|热词 1000",
PublicDataFolder: "WordCount",
})
cachePath := engine.DataFolder() + "cache/"
@@ -51,7 +50,7 @@ func init() {
}
_ = 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))
@@ -85,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, "),无法获得热词呢"))
@@ -108,44 +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
}
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) {
mapmu.Lock()
messageMap[word]++
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 {

View File

@@ -12,7 +12,7 @@
"0409": {
"identity": {
"name": "ZeroBot-Plugin",
"version": "1.9.7.2217"
"version": "1.9.9.2250"
},
"description": "",
"minimum-os": "vista",
@@ -36,23 +36,23 @@
"#1": {
"0000": {
"fixed": {
"file_version": "1.9.7.2217",
"product_version": "v1.9.7",
"timestamp": "2025-05-14T21:53:06+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.9.7.2217",
"FileVersion": "1.9.9.2250",
"InternalName": "",
"LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.",
"LegalTrademarks": "",
"OriginalFilename": "ZBP.EXE",
"PrivateBuild": "",
"ProductName": "ZeroBot-Plugin",
"ProductVersion": "v1.9.7",
"ProductVersion": "v1.9.9",
"SpecialBuild": ""
}
}