Compare commits

..

51 Commits

Author SHA1 Message Date
github-actions[bot]
6d30750f42 chore(nix): bump deps (#1257)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-07 00:47:03 +08:00
源文雨
78e1aa6783 feat(aichat): force cut multi calls 2026-01-07 00:45:12 +08:00
源文雨
9bfaa0c195 feat(aichat): force cut multi calls 2026-01-06 23:29:48 +08:00
源文雨
36397fb583 feat(aichat): force cut multi calls 2026-01-06 23:16:41 +08:00
源文雨
ae165349b9 feat(aichat): add iter to agent 2026-01-06 22:28:11 +08:00
源文雨
1584ab8b11 🔖 v1.10.9 2026-01-06 21:25:08 +08:00
源文雨
5283e6de61 feat(aichat): add more options 2026-01-06 21:24:31 +08:00
Kajiekazz
caa66d6715 feat(wife): game use Gaussian blur (#1254) 2026-01-06 00:47:53 +08:00
github-actions[bot]
f77d9b3509 chore(nix): bump deps (#1252)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-05 00:51:49 +08:00
源文雨
afc1e034e0 🔖1.10.8 2026-01-05 00:50:54 +08:00
源文雨
67c36aab29 fix(aichat): agent cannot stop 2026-01-05 00:49:16 +08:00
源文雨
3e837bf64c 🔖 v1.10.7 2026-01-03 23:42:08 +08:00
github-actions[bot]
c2af29f23c chore(nix): bump deps (#1251)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-03 23:41:28 +08:00
源文雨
71d43eb101 chore: make lint happy 2026-01-03 23:39:30 +08:00
源文雨
57c41a7db2 feat(aichat): agent add memory 2026-01-03 23:36:49 +08:00
himawari
91d512498d feat: 添加小红书文案 (#1250) 2026-01-02 17:22:52 +08:00
源文雨
ec59133f96 🔖 v1.10.6 2025-12-23 18:29:10 +08:00
源文雨
8fc733ffd1 chore: make lint happy 2025-12-23 17:36:37 +08:00
源文雨
6deaa8b36e chore: make lint happy 2025-12-23 17:32:31 +08:00
源文雨
e187a3159c chore: make lint happy 2025-12-23 17:31:44 +08:00
github-actions[bot]
0d80a43055 chore: bump deps (#1249)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-23 17:31:18 +08:00
源文雨
9bad003301 🔖 v1.10.5 2025-12-23 17:25:36 +08:00
源文雨
a5a1edda2d fix(aichat): cannot save config 2025-12-23 17:25:13 +08:00
github-actions[bot]
6a5ffa0b16 chore: bump deps (#1248)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-23 00:28:01 +08:00
源文雨
531ea7fa0c fix(ci): wrong push lint 2025-12-23 00:27:36 +08:00
源文雨
f23e44503c 🔖 v1.10.4 2025-12-23 00:25:30 +08:00
源文雨
3f0e284cee fix(zbpctrl): disable error 2025-12-23 00:24:57 +08:00
github-actions[bot]
6634d94ff4 chore: bump deps (#1246)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-12-22 00:59:05 +08:00
源文雨
bf3ecfcb55 chore: make lint happy 2025-12-22 00:57:22 +08:00
源文雨
6a83d74eb7 chore: make lint happy 2025-12-22 00:53:55 +08:00
源文雨
a13486faef 🔖 v1.10.3 2025-12-22 00:39:25 +08:00
源文雨
a828f49012 🔖 v1.10.3 2025-12-22 00:12:42 +08:00
源文雨
2ac3730f87 🔖 v1.10.2 2025-12-21 22:24:58 +08:00
源文雨
b897a11158 chore: update deps 2025-12-21 22:24:09 +08:00
昔音幻离
62a57fa61d chore: update gopsutil to v4 (#1244) 2025-12-16 10:58:21 +08:00
himawari
0142050d6a fix(aichat): gpt 修改提示词 (#1242) 2025-11-26 13:06:33 +08:00
源文雨
9cfad308b0 fix: boot panic on rsshub 2025-11-24 17:51:56 +08:00
源文雨
28a8ee945c chore: update go.sum 2025-11-24 17:46:33 +08:00
github-actions[bot]
2154faec2c chore: bump deps (#1240)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-13 00:13:59 +08:00
莫思潋
577b3f3f30 chore: update deps (#1241) 2025-11-09 17:57:01 +08:00
源文雨
99c9cc3de6 feat(aichat): add tests for storage 2025-10-24 00:54:36 +08:00
源文雨
216d99c05d feat: drop unsafe -checklinkname 2025-10-24 00:28:47 +08:00
宇~
8101c02ca8 fix: 启动脚本 (#1236) 2025-10-08 19:41:16 +08:00
github-actions[bot]
da81875f8b chore: bump deps (#1235)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-07 13:22:19 +08:00
github-actions[bot]
dd1a0ae478 chore: bump deps (#1234)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-07 12:39:39 +08:00
himawari
aafac8d6fa feat: 添加 rsshub (#1232) 2025-10-07 12:39:16 +08:00
源文雨
a74bcb869a chore: update deps 2025-10-07 12:36:40 +08:00
github-actions[bot]
584882282f chore: bump deps (#1233)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-10-03 23:41:32 +08:00
源文雨
05345e4c13 fix(wordcount): jieba panic 2025-10-02 16:42:59 +08:00
源文雨
b8a57b80f2 doc: add go generate main.go in README 2025-10-02 16:34:02 +08:00
源文雨
177fdbae68 optimize: drop gse in order to reduce mem cons. 2025-10-02 16:32:48 +08:00
33 changed files with 2107 additions and 1095 deletions

View File

@@ -1,6 +1,8 @@
name: 自动更新 nix 依赖
on:
push:
branches:
- master
paths:
- 'go.mod'
- 'go.sum'
@@ -23,6 +25,7 @@ jobs:
- name: Check out code into the Go module directory
uses: actions/checkout@master
with:
ref: master
fetch-depth: 0
- name: gomod2nix update
@@ -35,11 +38,12 @@ jobs:
git config --local user.name 'github-actions[bot]'
git config --local user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add --all
git commit -m "chore: bump deps"
git commit -m "chore(nix): bump deps"
- name: Create Pull Request
if: ${{ !github.head_ref }}
continue-on-error: true
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v8
with:
delete-branch: true
branch-suffix: short-commit-hash
title: "chore(nix): bump deps"

View File

@@ -6,7 +6,7 @@ env:
BINARY_PREFIX: "zbp_"
BINARY_SUFFIX: ""
PR_PROMPT: "::warning:: Build artifact will not be uploaded due to the workflow is trigged by pull request."
LD_FLAGS: "-w -s -checklinkname=0"
LD_FLAGS: "-w -s"
jobs:
build:

View File

@@ -1,5 +1,8 @@
name: PushLint
on: [ push ]
on:
push:
branches:
- master
jobs:
golangci:
name: lint
@@ -8,12 +11,14 @@ jobs:
- name: Set up Go
uses: actions/setup-go@master
with:
go-version: '1.24'
go-version: '1.25'
- name: Check out code into the Go module directory
uses: actions/checkout@master
with:
ref: master
fetch-depth: 0
submodules: 'recursive'
- name: Tidy Modules
run: |
@@ -37,4 +42,6 @@ jobs:
- name: Create Pull Request
if: ${{ !github.head_ref }}
continue-on-error: true
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v8
with:
title: "chore(lint): 改进代码样式"

View File

@@ -57,6 +57,8 @@ run:
issues-exit-code: 1
tests: false
go: '1.24'
skip-dirs:
- data
# output configuration options
output:

View File

@@ -26,7 +26,7 @@ builds:
flags:
- -trimpath
ldflags:
- -s -w -checklinkname=0
- -s -w
- id: win
env:
- CGO_ENABLED=0
@@ -39,7 +39,7 @@ builds:
flags:
- -trimpath
ldflags:
- -s -w -checklinkname=0
- -s -w
checksum:
name_template: "zbp_checksums.txt"

View File

@@ -917,6 +917,15 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 疯狂星期四
</details>
<details>
<summary>大模型聊天和群聊总结</summary>
`_ "github.com/FloatTech/ZeroBot-Plugin/plugin/llm"`
- [x] 群聊总结 [消息数目]|群聊总结 1000
- [x] /gpt [内容](使用大模型聊天)
</details>
<details>
<summary>kokomi原神面板</summary>
@@ -1284,6 +1293,17 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 打劫[对方Q号|@对方QQ]
</details>
<details>
<summary>RSSHub</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub"`
- [x] 添加rsshub订阅-/bookfere/weekly
- [x] 删除rsshub订阅-/bookfere/weekly
- [x] 查看rsshub订阅列表
- [x] rsshub同步 (使用job执行定时任务------记录在"@every 10m"触发的指令)
</details>
<details>
<summary>在线代码运行</summary>
@@ -1503,6 +1523,16 @@ print("run[CQ:image,file="+j["img"]+"]")
- 注:由于需要科学,默认注释。
</details>
<details>
<summary>小红书文案</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/xhstext"`
- [x] 捧场
- [x] 有梗
</details>
<details>
<summary>游戏王白鸽API卡查</summary>
@@ -1583,9 +1613,9 @@ print("run[CQ:image,file="+j["img"]+"]")
### *低优先级*
<details>
<summary>OpenAI聊天</summary>
<summary>大模型聊天和Agent配置</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat"`
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichatcfg"`
- [x] 设置AI聊天触发概率10
- [x] 设置AI聊天温度80
@@ -1603,10 +1633,20 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 设置AI聊天最大长度4096
- [x] 设置AI聊天TopP 0.9
- [x] 设置AI聊天(不)以AI语音输出
- [x] 设置AI聊天Agent性格
- [x] 查看AI聊天Agent性格
- [x] 重置AI聊天Agent性格
- [x] 重置AI聊天Agent
- [x] 查看AI聊天配置
- [x] 重置AI聊天
- [x] 群聊总结 [消息数目]|群聊总结 1000
- [x] /gpt [内容](使用大模型聊天)
</details>
<details>
<summary>大模型聊天和Agent</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat"`
- [x] (随意聊天, 概率匹配)
</details>
<details>
@@ -1674,6 +1714,7 @@ go version
go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=auto
go mod tidy
go generate main.go
```
3. 编辑 main.go 文件,内容按需修改
@@ -1681,15 +1722,15 @@ go mod tidy
```bash
# 本机平台
go build -ldflags "-s -w -checklinkname=0" -o zerobot -trimpath
go build -ldflags "-s -w" -o zerobot -trimpath
# x64 Linux 平台 如各种云服务器
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -checklinkname=0" -o zerobot -trimpath
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o zerobot -trimpath
# x64 Windows 平台 如大多数家用电脑
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w -checklinkname=0" -o zerobot.exe -trimpath
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o zerobot.exe -trimpath
# armv6 Linux 平台 如树莓派 zero W
GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=0 go build -ldflags "-s -w -checklinkname=0" -o zerobot -trimpath
GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=0 go build -ldflags "-s -w" -o zerobot -trimpath
# 由于引入了github.com/fumiama/sqlite3本项不再可用mips Linux 平台 如 路由器 wndr4300
GOOS=linux GOARCH=mips GOMIPS=softfloat CGO_ENABLED=0 go build -ldflags "-s -w -checklinkname=0" -o zerobot -trimpath
GOOS=linux GOARCH=mips GOMIPS=softfloat CGO_ENABLED=0 go build -ldflags "-s -w" -o zerobot -trimpath
```
5. 运行 OneBot 框架,并同时运行本插件

2
data

Submodule data updated: 1b0abcd3fe...74e3bf5dc8

79
go.mod
View File

@@ -4,34 +4,34 @@ go 1.24.2
require (
github.com/Baidu-AIP/golang-sdk v1.1.1
github.com/FloatTech/AnimeAPI v1.7.1-0.20250926171956-ba37dfebfc4a
github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80
github.com/FloatTech/AnimeAPI v1.7.1-0.20251028071248-0c948e3db65c
github.com/FloatTech/floatbox v0.0.0-20251002074805-f95cbc7edb31
github.com/FloatTech/gg v1.1.3
github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef
github.com/FloatTech/rendercard v0.2.0
github.com/FloatTech/sqlite v1.7.2
github.com/FloatTech/ttl v0.0.0-20250224045156-012b1463287d
github.com/FloatTech/zbpctrl v1.7.0
github.com/FloatTech/zbputils v1.7.2-0.20250930154951-b9dba62964b2
github.com/FloatTech/zbpctrl v1.7.1
github.com/FloatTech/zbputils v1.7.2-0.20260106163901-1f0a51ecf35d
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
github.com/RomiChan/websocket v1.4.3-0.20251002072000-d3eb41798438
github.com/Tnze/go-mc v1.20.2
github.com/antchfx/htmlquery v1.3.4
github.com/corona10/goimagehash v1.1.0
github.com/antchfx/htmlquery v1.3.5
github.com/corona10/goimagehash v1.1.1-0.20240121134706-d8115886f360
github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3
github.com/disintegration/imaging v1.6.2
github.com/fumiama/ahsai v0.1.1
github.com/fumiama/cron v1.3.0
github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa
github.com/fumiama/go-base16384 v1.7.0
github.com/fumiama/go-onebot-agent v0.0.0-20250926145606-37ebfa6131c8
github.com/fumiama/deepinfra v0.0.0-20251221163610-e98ee3ba437a
github.com/fumiama/go-base16384 v1.7.1
github.com/fumiama/go-onebot-agent v0.0.0-20260106161505-f7d25272034c
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3
github.com/fumiama/imgsz v0.0.4
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565
github.com/fumiama/slowdo v0.0.0-20241001074058-27c4fe5259a4
github.com/fumiama/terasu v0.0.0-20250930151601-1d9b679c36c2
github.com/fumiama/terasu v1.0.2
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
@@ -39,62 +39,67 @@ require (
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.10.0
github.com/pkg/errors v0.9.1
github.com/shirou/gopsutil/v3 v3.24.5
github.com/shirou/gopsutil/v4 v4.25.12
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.20250921063512-13752a73d444
gitlab.com/gomidi/midi/v2 v2.3.16
golang.org/x/image v0.31.0
golang.org/x/sys v0.36.0
golang.org/x/text v0.29.0
gopkg.in/yaml.v3 v3.0.1
github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260103120253-8a8f1347f983
gitlab.com/gomidi/midi/v2 v2.3.18
golang.org/x/image v0.34.0
golang.org/x/sys v0.39.0
golang.org/x/text v0.32.0
)
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.3 // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/antchfx/xpath v1.3.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/ebitengine/oto/v3 v3.3.2 // indirect
github.com/ebitengine/purego v0.8.0 // indirect
github.com/ebitengine/purego v0.9.1 // indirect
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // 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/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/fumiama/gofastTEA v0.1.3 // indirect
github.com/fumiama/orbyte v0.0.0-20251002065953-3bb358367eb5 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/gopxl/beep/v2 v2.1.1 // indirect
github.com/jfreymuth/oggvorbis v1.0.5 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
github.com/jinzhu/inflection v1.0.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/ncruces/go-strftime v0.1.9 // 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 v1.0.0 // 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/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/net v0.42.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.33.1 // indirect
golang.org/x/net v0.48.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.67.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.41.0 // indirect
)
replace modernc.org/sqlite => github.com/fumiama/sqlite3 v1.29.10-simp

174
go.sum
View File

@@ -1,9 +1,9 @@
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/FloatTech/AnimeAPI v1.7.1-0.20250926171956-ba37dfebfc4a h1:D/+ni0hzmfC+5TVQyGuq/AReGrSNKWTSMqU+lNG60rc=
github.com/FloatTech/AnimeAPI v1.7.1-0.20250926171956-ba37dfebfc4a/go.mod h1:cuDd67B23xmICSmFBhWzXN51blod2BlM1liN9Ux0pSc=
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/AnimeAPI v1.7.1-0.20251028071248-0c948e3db65c h1:fmvlRUzwoK6KdoRSW+XeTQ9myKHimd0pV6GbmRJLNRo=
github.com/FloatTech/AnimeAPI v1.7.1-0.20251028071248-0c948e3db65c/go.mod h1:cuDd67B23xmICSmFBhWzXN51blod2BlM1liN9Ux0pSc=
github.com/FloatTech/floatbox v0.0.0-20251002074805-f95cbc7edb31 h1:2K+/M64ixD1Pg5hr00Nbxr7GoWQOgahvpmp1pAMnrYc=
github.com/FloatTech/floatbox v0.0.0-20251002074805-f95cbc7edb31/go.mod h1:kf+Ywc2lk8PLdg3RX0vrUmFLPO6k+23MFmt4GviV8C0=
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=
@@ -14,15 +14,17 @@ github.com/FloatTech/sqlite v1.7.2 h1:b8COegNLSzofzOyARsVwSbz9OOzWEa8IElsTlx1TBL
github.com/FloatTech/sqlite v1.7.2/go.mod h1:/4tzfCGhrZnnjC1U8vcfwGQeF6eR649fhOsS3+Le0+s=
github.com/FloatTech/ttl v0.0.0-20250224045156-012b1463287d h1:mUQ/c3wXKsUGa4Sg9DBy01APXKB68PmobhxOyaJI7lY=
github.com/FloatTech/ttl v0.0.0-20250224045156-012b1463287d/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.20250930154951-b9dba62964b2 h1:3XgC953tTNOt4sbvST4LqddsH2yV355qaxFUskvzW1A=
github.com/FloatTech/zbputils v1.7.2-0.20250930154951-b9dba62964b2/go.mod h1:klguZgIkWv7+1U/eh/5MFR9enyPqUuthSdXp/KrBE2g=
github.com/FloatTech/zbpctrl v1.7.1 h1:0yPEmCForhyMbnhTckmjDUFFDZgQp1RjO2bVF4ZVqOs=
github.com/FloatTech/zbpctrl v1.7.1/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE=
github.com/FloatTech/zbputils v1.7.2-0.20260106163901-1f0a51ecf35d h1:Ur2R0bWD88qbY6lgEQ21QojZ7ErQRWDHz4RA4NNX8gw=
github.com/FloatTech/zbputils v1.7.2-0.20260106163901-1f0a51ecf35d/go.mod h1:O3Js6obaao6BhxHx7hl1xwup4jXm/Fk2cmZEg5qNur8=
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/RomiChan/websocket v1.4.3-0.20251002072000-d3eb41798438 h1:I0bdwHZ+2DY45b39xPoTD2u+Z8zhvBuu9aZfjMZeiZM=
github.com/RomiChan/websocket v1.4.3-0.20251002072000-d3eb41798438/go.mod h1:GO+9i5UYB4BuZEel6BfGx7O1u3ggwgZWUnGxPATUoTE=
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=
@@ -30,12 +32,14 @@ github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0
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.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/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.5 h1:aYthDDClnG2a2xePf6tys/UyyM/kRcsFRm+ifhFKoU0=
github.com/antchfx/htmlquery v1.3.5/go.mod h1:5oyIPIa3ovYGtLqMPNjBF2Uf25NPCKsMjCnQ8lvjaoA=
github.com/antchfx/xpath v1.3.5 h1:PqbXLC3TkfeZyakF5eeh3NTWEbYl4VHNVeufANzDbKQ=
github.com/antchfx/xpath v1.3.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/corona10/goimagehash v1.1.1-0.20240121134706-d8115886f360 h1:SvD9vQN+3r0wskoSrQ7IOyDmOtRIXhT3rlnf819r/bY=
github.com/corona10/goimagehash v1.1.1-0.20240121134706-d8115886f360/go.mod h1:PFDP0Q0oYvEuipJHMSbeK8tjbuRzkQqjPDYm3n71ITA=
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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -49,8 +53,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/oto/v3 v3.3.2 h1:VTWBsKX9eb+dXzaF4jEwQbs4yWIdXukJ0K40KgkpYlg=
github.com/ebitengine/oto/v3 v3.3.2/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 h1:BBade+JlV/f7JstZ4pitd4tHhpN+w+6I+LyOS7B4fyU=
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4/go.mod h1:H7chHJglrhPPzetLdzBleF8d22WYOv7UM/lEKYiwlKM=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
@@ -59,18 +63,18 @@ github.com/fumiama/ahsai v0.1.1 h1:/t5tdKRim0TK6YwgNFQfqtDOW7Y2tFBsmdUWt3JK+C0=
github.com/fumiama/ahsai v0.1.1/go.mod h1:rBhHLgN2bygcqLpBi+XQa8B8Afn4UkPHQ5vvQibdbDQ=
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-20250924162107-cf156d49a0fa h1:UMMNejpPp8dn92GPaVSZ2XKNSgp7+CVneOkZfExUilk=
github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa/go.mod h1:uqsWK/GM9OvKV0pXZOQB63rWugBbiXInY8E1JoRKhkg=
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-onebot-agent v0.0.0-20250926145606-37ebfa6131c8 h1:aXk5IVXvPy2IfajL6gH+V/6ZOVV1BBVKjnFISLvyw60=
github.com/fumiama/go-onebot-agent v0.0.0-20250926145606-37ebfa6131c8/go.mod h1:oH8DGDpRPjUAu8Fd/K+RxsB+z0Yis+BHeJAh+ZkO5EM=
github.com/fumiama/deepinfra v0.0.0-20251221163610-e98ee3ba437a h1:a0+2vaXajfxsNcIaYG7A/cAb2e4+M3s5J6oyIiPsL3c=
github.com/fumiama/deepinfra v0.0.0-20251221163610-e98ee3ba437a/go.mod h1:uqsWK/GM9OvKV0pXZOQB63rWugBbiXInY8E1JoRKhkg=
github.com/fumiama/go-base16384 v1.7.1 h1:1P1x6FWRvd7PtbH4idDAGWAjKKcVxggxlROYKRXbw58=
github.com/fumiama/go-base16384 v1.7.1/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-onebot-agent v0.0.0-20260106161505-f7d25272034c h1:kCAFF41GkJklKvftchjuEpo4Mp/qUSiEGBeChs4QJdk=
github.com/fumiama/go-onebot-agent v0.0.0-20260106161505-f7d25272034c/go.mod h1:rTrS23rvTYuZcSngENJTvcBFTz1nGsImSv+bW7yfhqs=
github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs=
github.com/fumiama/go-registry v0.2.7/go.mod h1:m+wp5fF8dYgVoFkBPZl+vlK90loymaJE0JCtocVQLEs=
github.com/fumiama/go-simple-protobuf v0.2.0 h1:ACyN1MAlu7pDR3EszWgzUeNP+IRsSHwH6V9JCJA5R5o=
github.com/fumiama/go-simple-protobuf v0.2.0/go.mod h1:5yYNapXq1tQMOZg9bOIVhQlZk9pQqpuFIO4DZLbsdy4=
github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoGhhQ=
github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk=
github.com/fumiama/gofastTEA v0.1.3 h1:fxOi2D66knV6QN170hb59YiqxPhjlgizvBw+o0OjxUA=
github.com/fumiama/gofastTEA v0.1.3/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=
@@ -79,19 +83,19 @@ github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 h1:sQuR2+N5HurnvsZhi
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/orbyte v0.0.0-20251002065953-3bb358367eb5 h1:j9o0XVvdAeLwrBYMnh0SerrMc9CgNU6AGszbsvFzoc0=
github.com/fumiama/orbyte v0.0.0-20251002065953-3bb358367eb5/go.mod h1:FOjdw7KdCbK2eH3gRPhwFNCoXKpu9sN5vPH4El/8e0c=
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-20250930151601-1d9b679c36c2 h1:NAmsblfyAfQIZLPxkJil/kh6EV7Vrs/c7LGOavnUONM=
github.com/fumiama/terasu v0.0.0-20250930151601-1d9b679c36c2/go.mod h1:UVx8YP1jKKL1Cj+uy+OnQRM2Ih6U36Mqy9GSf7jabsI=
github.com/fumiama/terasu v1.0.2 h1:Dxj2vPIgLHyeMlBd61xYHx5Jg8v7k24yg7taNLlK8/8=
github.com/fumiama/terasu v1.0.2/go.mod h1:1eHzpL/GJVcOnqEkh1vUbKu2zo6gojSuueUNJ9yHJE0=
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/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
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=
@@ -105,8 +109,10 @@ 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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
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/gopxl/beep/v2 v2.1.1 h1:6FYIYMm2qPAdWkjX+7xwKrViS1x0Po5kDMdRkq8NVbU=
@@ -123,6 +129,8 @@ 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/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/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
@@ -141,10 +149,19 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
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=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/notnil/chess v1.10.0 h1:RR3MgS9G6zZmJ+VPTJolyxdaIgxoUPyUUY+2iaw35G0=
@@ -159,48 +176,43 @@ github.com/pkumza/numcn v1.0.0 h1:ZT5cf9IJkUZgRgEtCiNNykk0RwsrKXSTsvDHOwUTzgE=
github.com/pkumza/numcn v1.0.0/go.mod h1:QSeH+al9dWCd8di5HZM/ZqHqhZmUKfph572e9Ev/ETc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/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/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/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY=
github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU=
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/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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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.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=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
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/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/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
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.20250921063512-13752a73d444 h1:7aYFXzvVr2zuxBvqrGaJb24Z4W12aXBdW8DuE1mteE4=
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250921063512-13752a73d444/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260103120253-8a8f1347f983 h1:hb2FUDooAf3u32wCTgJcBBuGPZF1sjgj8NfJaSy529s=
github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260103120253-8a8f1347f983/go.mod h1:trueIIVRywKJa3ov4QphzVvzYzgCNrlXdf9JvPJOFW8=
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=
gitlab.com/gomidi/midi/v2 v2.3.16 h1:yufWSENyjnJ4LFQa9BerzUm4E4aLfTyzw5nmnCteO0c=
gitlab.com/gomidi/midi/v2 v2.3.16/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
gitlab.com/gomidi/midi/v2 v2.3.18 h1:sj2fOhtvOe+zI8YJe8qTxLw5zv0ntULLUDwcFOaZQbI=
gitlab.com/gomidi/midi/v2 v2.3.18/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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=
@@ -209,25 +221,26 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
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/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.31.0 h1:mLChjE2MV6g1S7oqbXC0/UcKijjm5fnJLUYKIYrLESA=
golang.org/x/image v0.31.0/go.mod h1:R9ec5Lcp96v9FTF+ajwaH3uGxPH4fKfHHAVbUILxghA=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
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.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/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
@@ -235,8 +248,8 @@ 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/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
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=
@@ -244,14 +257,15 @@ 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.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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=
@@ -259,13 +273,12 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
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.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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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=
@@ -277,6 +290,7 @@ 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=
@@ -285,16 +299,16 @@ 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
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.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.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
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=
@@ -310,10 +324,10 @@ modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=

View File

@@ -5,11 +5,11 @@ schema = 3
version = "v1.1.1"
hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo="
[mod."github.com/FloatTech/AnimeAPI"]
version = "v1.7.1-0.20250926171956-ba37dfebfc4a"
hash = "sha256-wtr9hgHSPsD5NqSmI3Lo8RVWE+cNMIUGo2bcsKTN8Gg="
version = "v1.7.1-0.20251028071248-0c948e3db65c"
hash = "sha256-EkC8QYgmXElKGM8GzcQ4r/c+lM1ysJOccfoeqhMtAvs="
[mod."github.com/FloatTech/floatbox"]
version = "v0.0.0-20250513111443-adba80e84e80"
hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE="
version = "v0.0.0-20251002074805-f95cbc7edb31"
hash = "sha256-c50unGhF0JVPHN8geZM/YYQKgGqJgCtVksh4Ij1Pg+4="
[mod."github.com/FloatTech/gg"]
version = "v1.1.3"
hash = "sha256-7K/R2mKjUHVnoJ3b1wDObJ5Un2Htj59Y97G1Ja1tuPo="
@@ -26,17 +26,20 @@ schema = 3
version = "v0.0.0-20250224045156-012b1463287d"
hash = "sha256-C5xBt0roPgahradCOTgkhL+j5bvoSXmGwdqcu0aSczc="
[mod."github.com/FloatTech/zbpctrl"]
version = "v1.7.0"
hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU="
version = "v1.7.1"
hash = "sha256-wkeiaUTpPVbpH7fcXeoLtG+aGIMJbvoc/9sbi2IXK0I="
[mod."github.com/FloatTech/zbputils"]
version = "v1.7.2-0.20250930154951-b9dba62964b2"
hash = "sha256-slSHly7Po3LXvmU99RKvdYHkecDzvNRExWLysUfQmFk="
version = "v1.7.2-0.20260106163901-1f0a51ecf35d"
hash = "sha256-Tb4X/usnhiNKaArlp96+KTKOdM4DK0SUZLHo8TlXi/c="
[mod."github.com/PuerkitoBio/goquery"]
version = "v1.8.0"
hash = "sha256-I3QaPWATvBOL/F26fIiYWKS13yBUYo+9o3tcsGIu8tY="
[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="
version = "v1.4.3-0.20251002072000-d3eb41798438"
hash = "sha256-vLu9Va+9AbOIdh1LEetz5JlJK0P2IXKsYRvCdAO8tYw="
[mod."github.com/Tnze/go-mc"]
version = "v1.20.2"
hash = "sha256-Nu4PXNxeARH0itm6yIIplFaywL2yQnPJFksmmuyIptI="
@@ -46,15 +49,18 @@ schema = 3
[mod."github.com/ajstarks/svgo"]
version = "v0.0.0-20200320125537-f189e35d30ca"
hash = "sha256-ALeRuEJN9jHjGb4wNKJcxC59vVx8Tj7hHikEGkaZZ0s="
[mod."github.com/andybalholm/cascadia"]
version = "v1.3.1"
hash = "sha256-M0u22DXSeXUaYtl1KoW1qWL46niFpycFkraCEQ/luYA="
[mod."github.com/antchfx/htmlquery"]
version = "v1.3.4"
hash = "sha256-nrtIgRgdOvo0iIQyrhHOFKOmoT8e2gduUsct3f5zDNA="
version = "v1.3.5"
hash = "sha256-AyfSTQY2eiNPhTS/FVgaBlSzPOObSaluhSee8Gvc8ho="
[mod."github.com/antchfx/xpath"]
version = "v1.3.3"
hash = "sha256-Ent9bgBTjKS8/61LKrIu/JcBI/Qsv6EEIojwsMjCgdY="
version = "v1.3.5"
hash = "sha256-AVM0rR81hgVAI0QVzlz4WijFUjByf6Zew3ZwuikKw2Q="
[mod."github.com/corona10/goimagehash"]
version = "v1.1.0"
hash = "sha256-HyS8nc7kUNnDaVBDzJ9Ym4pRs83YB4M2vHSRwfm6mr4="
version = "v1.1.1-0.20240121134706-d8115886f360"
hash = "sha256-/corDVYILmy/DMWtgM1D1MK6I7pejA5JNfaxeojwkkA="
[mod."github.com/davidscholberg/go-durationfmt"]
version = "v0.0.0-20170122144659-64843a2083d3"
hash = "sha256-0rdbpBf3AAjMpxvVEGFb2ImgB2i7vdEhIwCyqJs1iHE="
@@ -68,8 +74,8 @@ schema = 3
version = "v3.3.2"
hash = "sha256-TPu3qvJscLZbjwIqC3jj0T1md0mX3lQxcC8GAk7kB1w="
[mod."github.com/ebitengine/purego"]
version = "v0.8.0"
hash = "sha256-fM+5HYIKDPLDrvv2f9/WQG0F20+o0+At0AbPQtF2JP0="
version = "v0.9.1"
hash = "sha256-iVfU8vaJ7IPa92dUeHeuW+yKvUbe59F/eV7GlDRIAcE="
[mod."github.com/ericpauley/go-quantize"]
version = "v0.0.0-20200331213906-ae555eb2afa4"
hash = "sha256-sMN6D7IlDpDqUWM8ppoE5Sdb7DvLAJaN6qAucBWJ3rs="
@@ -80,14 +86,14 @@ schema = 3
version = "v1.3.0"
hash = "sha256-/sN7X8dKXQgv8J+EDzVUB+o+AY9gBC8e1C6sYhaTy1k="
[mod."github.com/fumiama/deepinfra"]
version = "v0.0.0-20250924162107-cf156d49a0fa"
hash = "sha256-D0lgA7jBDLE8v9ePDiWwH439eB5+cDlj2fKRAc0wUms="
version = "v0.0.0-20251221163610-e98ee3ba437a"
hash = "sha256-XubewUDOLaN7fgv9E8PAfP7DtADdLm+M2sgSn5OE6eQ="
[mod."github.com/fumiama/go-base16384"]
version = "v1.7.0"
hash = "sha256-vTAsBBYe2ISzb2Nba5E96unodZSkhMcqo6hbwR01nz8="
version = "v1.7.1"
hash = "sha256-Fd1QaeYx+3q4C3XQXlPFnDmKPsoZH6837fN/7rn8i9s="
[mod."github.com/fumiama/go-onebot-agent"]
version = "v0.0.0-20250926145606-37ebfa6131c8"
hash = "sha256-oq1SSyddsXhsFvSHFhqSEAF9SH03b2jI0KvGbsFPXYQ="
version = "v0.0.0-20260106161505-f7d25272034c"
hash = "sha256-+bu2VbZVwS7SDgfx53aZjQxSRtrXFi+g0Jn8W9w6QQ8="
[mod."github.com/fumiama/go-registry"]
version = "v0.2.7"
hash = "sha256-Rjl+z0Hlp2LMi8+pnFe5HrxctyHMi7UPiK33g/OgLdA="
@@ -95,8 +101,8 @@ schema = 3
version = "v0.2.0"
hash = "sha256-2kULBi1sXsFDX2g/KRFmCGkwF60o/UXacNUbIYa/cvw="
[mod."github.com/fumiama/gofastTEA"]
version = "v0.0.10"
hash = "sha256-FOCbkXoS8s/K54yZbhX5pmaN/ouELnCHZoNS8a90VAg="
version = "v0.1.3"
hash = "sha256-/Qu57mkkFt7aFufhlkMYPgrWj5XCGbuM28EHDD8w4XY="
[mod."github.com/fumiama/gotracemoe"]
version = "v0.0.3"
hash = "sha256-O3cDkVXu5NG1ZtzubxhH+S91zfgu4uH1L+OiSGYSNXQ="
@@ -106,21 +112,21 @@ schema = 3
[mod."github.com/fumiama/jieba"]
version = "v0.0.0-20221203025406-36c17a10b565"
hash = "sha256-DvDx1pdldkdaSszrbadM/VwqT9TTSmWl6G6a+ysXYEM="
[mod."github.com/fumiama/orbyte"]
version = "v0.0.0-20251002065953-3bb358367eb5"
hash = "sha256-mRQwhR0v922UXlJ7lXo/osv21K8kZDaHx3DsBCjmzoo="
[mod."github.com/fumiama/slowdo"]
version = "v0.0.0-20241001074058-27c4fe5259a4"
hash = "sha256-rsV3MKRCSOBMIgJXFCGbCHRY2aBAb32ftU49hT3GjqY="
[mod."github.com/fumiama/terasu"]
version = "v0.0.0-20250930151601-1d9b679c36c2"
hash = "sha256-82fl+7St6LaUR6J4G/1RTToBlutA2EF40vAMYptAS/g="
version = "v1.0.2"
hash = "sha256-tYHvvfJT1/R4koqGFPjdU211Dcez2XWuvpRke/tFfQ8="
[mod."github.com/fumiama/unibase2n"]
version = "v0.0.0-20240530074540-ec743fd5a6d6"
hash = "sha256-I3xNzjrj5y0fy0dfa75V57GanfmHIHmubEn9/y0BBHw="
[mod."github.com/gabriel-vasile/mimetype"]
version = "v1.4.8"
hash = "sha256-ElqfQtnoGHyVqtN0mJjeWakQ6N5x+nVaX3+uOV7Q5Xk="
[mod."github.com/go-ego/gse"]
version = "v0.80.3"
hash = "sha256-uxTQN4cxE/ZReZqjlIEQ3WYD9w2Ec37LRHQftJXsSZQ="
version = "v1.4.12"
hash = "sha256-vY2g58yUrkT//8fttRKhS9rbg89YSae/BzOARS5uH30="
[mod."github.com/go-ole/go-ole"]
version = "v1.2.6"
hash = "sha256-+oxitLeJxYF19Z6g+6CgmCHJ1Y5D8raMi2Cb3M6nXCs="
@@ -151,6 +157,9 @@ schema = 3
[mod."github.com/jozsefsallai/gophersauce"]
version = "v1.0.1"
hash = "sha256-29DsfnGmK51DPunR/leRBKCcokN/yLoB7S2HxCsqtgY="
[mod."github.com/json-iterator/go"]
version = "v1.1.12"
hash = "sha256-To8A0h+lbfZ/6zM+2PpRpY3+L6725OPC66lffq6fUoM="
[mod."github.com/kanrichan/resvg-go"]
version = "v0.0.2-0.20231001163256-63db194ca9f5"
hash = "sha256-plRZ3yhyCafCXmAD4vnFUoCTRsHmLp7Jn9gFKcEKbds="
@@ -172,12 +181,24 @@ schema = 3
[mod."github.com/mattn/go-isatty"]
version = "v0.0.20"
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
[mod."github.com/mmcdole/gofeed"]
version = "v1.3.0"
hash = "sha256-GHpqGZvNg+3RSIkVKXrWg6/e8dJD8Y5v2Sx6MzmRlQ0="
[mod."github.com/mmcdole/goxpp"]
version = "v1.1.1-0.20240225020742-a0c311522b23"
hash = "sha256-2pGg+LxHHQn2lwQBvc7EtrpMwZbZF7qepglzhS3TfW4="
[mod."github.com/modern-go/concurrent"]
version = "v0.0.0-20180306012644-bacd9c7ef1dd"
hash = "sha256-OTySieAgPWR4oJnlohaFTeK1tRaVp/b0d1rYY8xKMzo="
[mod."github.com/modern-go/reflect2"]
version = "v1.0.2"
hash = "sha256-+W9EIW7okXIXjWEgOaMh58eLvBZ7OshW2EhaIpNLSBU="
[mod."github.com/mroth/weightedrand"]
version = "v1.0.0"
hash = "sha256-bP+yIaBUY5+oI455mNM8zh14z/SNPaQg44L3RJ0/v/c="
[mod."github.com/ncruces/go-strftime"]
version = "v0.1.9"
hash = "sha256-T0iw+UEckzueWHT88PkTnZZixyKCEa+DTLzIiiohuWY="
version = "v1.0.0"
hash = "sha256-GYIwYDONuv/yTE0AEugCHQbtV3oiBaco93xUNYFcVBQ="
[mod."github.com/nfnt/resize"]
version = "v0.0.0-20180221191011-83c6a9932646"
hash = "sha256-yvPV+HlDOyJsiwAcVHQkmtw8DHSXyw+cXHkigXm8rAA="
@@ -194,17 +215,14 @@ schema = 3
version = "v1.0.0"
hash = "sha256-cPxqj5tb10+MurN1Lehkk/v8KjaxXpL08+pVgL4x4Hg="
[mod."github.com/power-devops/perfstat"]
version = "v0.0.0-20210106213030-5aafc221ea8c"
hash = "sha256-ywykDYuqcMt0TvZOz1l9Z6Z2JMTYQw8cP2fT8AtpmX4="
version = "v0.0.0-20240221224432-82ca36839d55"
hash = "sha256-ujzuJ1ttQgjHQJEij4O/2+I8DZaUVZQCQgA4ysfqulI="
[mod."github.com/remyoudompheng/bigfft"]
version = "v0.0.0-20230129092748-24d4a6f8daec"
hash = "sha256-vYmpyCE37eBYP/navhaLV4oX4/nu0Z/StAocLIFqrmM="
[mod."github.com/shirou/gopsutil/v3"]
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="
[mod."github.com/shirou/gopsutil/v4"]
version = "v4.25.12"
hash = "sha256-gzk9GW4+tXUWmxAVD3by/k4D/+l++TvajRVTkQJvwmM="
[mod."github.com/sirupsen/logrus"]
version = "v1.9.3"
hash = "sha256-EnxsWdEUPYid+aZ9H4/iMTs1XMvCLbXZRDyvj89Ebms="
@@ -215,44 +233,41 @@ schema = 3
version = "v1.18.0"
hash = "sha256-CO6hqDu8Y58Po6A01e5iTpwiUBQ5khUZsw7czaJHw0I="
[mod."github.com/tidwall/match"]
version = "v1.1.1"
hash = "sha256-M2klhPId3Q3T3VGkSbOkYl/2nLHnsG+yMbXkPkyrRdg="
[mod."github.com/tidwall/pretty"]
version = "v1.2.0"
hash = "sha256-esRQGsn2Ee/CiySlwyuOICSLdqUkH4P7u8qXszos8Yc="
hash = "sha256-O2wTU0SmNIEEOxfncl2BW2czgWeIW5vqR6+A7dtNtXI="
[mod."github.com/tidwall/pretty"]
version = "v1.2.1"
hash = "sha256-S0uTDDGD8qr415Ut7QinyXljCp0TkL4zOIrlJ+9OMl8="
[mod."github.com/tklauser/go-sysconf"]
version = "v0.3.12"
hash = "sha256-91VBZNb3L2TZkEETF1AE4wnraLoGxKeofUbC5ZiWVHk="
version = "v0.3.16"
hash = "sha256-hNVbsk0G+M9bLtHNywPD0nCW0z/9pM4jQV53nnDo/MY="
[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="
version = "v0.11.0"
hash = "sha256-ObydGqAvRiHwVfO2ytmL28pRHyuCWYWwjH4hWdgI/Vs="
[mod."github.com/wcharczuk/go-chart/v2"]
version = "v2.1.2"
hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM="
[mod."github.com/wdvxdr1123/ZeroBot"]
version = "v1.8.2-0.20250921063512-13752a73d444"
hash = "sha256-eTqHrn/uzpa+IUoIg+LxHKQmR3oF1CgByz7speXxazE="
version = "v1.8.3-0.20260103120253-8a8f1347f983"
hash = "sha256-/6biuhXUIYxyJ0uT1R5Yk8RD2cm9/SQWtFvcyQgnWJE="
[mod."github.com/yusufpapurcu/wmi"]
version = "v1.2.4"
hash = "sha256-N+YDBjOW59YOsZ2lRBVtFsEEi48KhNQRb63/0ZSU3bA="
[mod."gitlab.com/gomidi/midi/v2"]
version = "v2.3.16"
hash = "sha256-o+6UtQH+TRSQlcX8J53esAA/b2c9e7BY7gcO5iSeOy0="
version = "v2.3.18"
hash = "sha256-lkU9M+h56+Ai/bpQDST3Su71dhjp1Vk2S7/okrELo7s="
[mod."golang.org/x/image"]
version = "v0.31.0"
hash = "sha256-ZFTlu9+4QToPPLA8C5UcG2eq/lQylq81RoG/WtYo9rg="
version = "v0.34.0"
hash = "sha256-7V1bDhd++dCw11DCXOAUb5f1Hj51EfS0DZ03pq0V/p0="
[mod."golang.org/x/net"]
version = "v0.42.0"
hash = "sha256-YxileisIIez+kcAI+21kY5yk0iRuEqti2YdmS8jvP2s="
version = "v0.48.0"
hash = "sha256-oZpddsiJwWCH3Aipa+XXpy7G/xHY5fEagUSok7T0bXE="
[mod."golang.org/x/sys"]
version = "v0.36.0"
hash = "sha256-9h4SHGnlJzmTENUp6226hC8fQ73QrQC3D85NNMxLuXg="
version = "v0.39.0"
hash = "sha256-dxTBu/JAWUkPbjFIXXRFdhQWyn+YyEpIC+tWqGo0Y6U="
[mod."golang.org/x/text"]
version = "v0.29.0"
hash = "sha256-2cWBtJje+Yc+AnSgCANqBlIwnOMZEGkpQ2cFI45VfLI="
version = "v0.32.0"
hash = "sha256-9PXtWBKKY9rG4AgjSP4N+I1DhepXhy8SF/vWSIDIoWs="
[mod."gopkg.in/yaml.v3"]
version = "v3.0.1"
hash = "sha256-FqL9TKYJ0XkNwJFnq9j0VvJ5ZUU1RvH/52h/f5bkYAU="
@@ -261,11 +276,11 @@ schema = 3
hash = "sha256-SJYYRaiDUmIbqy9l/IgiT/4VkFsPYsaslqGEowut34w="
replaced = "github.com/fumiama/libc"
[mod."modernc.org/mathutil"]
version = "v1.6.0"
hash = "sha256-lfuEiS1odd2TWrTylnaGihSJ9myqKs3FLdpvd7PqTnE="
version = "v1.7.1"
hash = "sha256-COZ5rF2GhQVR1r6a0DanJ8qwQ94JSKdQxTMWrDzE0Cc="
[mod."modernc.org/memory"]
version = "v1.8.0"
hash = "sha256-ucvPr73zg8LjvU+bcoIPKTgwgcon3U9VhKrLEMH81xg="
version = "v1.11.0"
hash = "sha256-MkybF8vvrxXS5j7O8w3skwTo0aMo1yjWS0K440rYcHM="
[mod."modernc.org/sqlite"]
version = "v1.29.10-simp"
hash = "sha256-HCUVN6gZDG0g2WIsQ4ksqE1+XR1IjxvnqEBEU2MO1eE="

View File

@@ -3,13 +3,13 @@
package banner
// Version ...
var Version = "v1.10.0"
var Version = "v1.10.9"
// Copyright ...
var Copyright = "© 2020 - 2025 FloatTech"
var Copyright = "© 2020 - 2026 FloatTech"
// Banner ...
var Banner = "* OneBot + ZeroBot + Golang\n" +
"* Version " + Version + " - 2025-09-30 23:45:28 +0800 CST\n" +
"* Version " + Version + " - 2026-01-06 21:24:56 +0800 CST\n" +
"* Copyright " + Copyright + ". All Rights Reserved.\n" +
"* Project: https://github.com/FloatTech/ZeroBot-Plugin"

View File

@@ -112,6 +112,7 @@ import (
_ "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/llm" // 大模型聊天和群聊总结
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolimi" // 桑帛云 API
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/magicprompt" // magicprompt吟唱提示
@@ -136,6 +137,7 @@ import (
_ "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" // 分数
@@ -153,6 +155,7 @@ import (
_ "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/xhstext" // 小红书文案
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ygocdb" // 游戏王白鸽API卡查
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ygotrade" // 游戏王集换社卡价查询
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
@@ -178,6 +181,8 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichatcfg" // AI聊天配置
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aichat" // AI聊天
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse" // 骂人

View File

@@ -1,302 +0,0 @@
package aichat
import (
"errors"
"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()
)
var (
apitypes = map[string]uint8{
"OpenAI": 0,
"OLLaMA": 1,
"GenAI": 2,
}
apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"}
)
// ModelType 支持打印 string 并生产 protocal
type ModelType int
func newModelType(typ string) (ModelType, error) {
t, ok := apitypes[typ]
if !ok {
return 0, errors.New("未知类型 " + typ)
}
return ModelType(t), nil
}
func (mt ModelType) String() string {
return apilist[mt]
}
func (mt ModelType) protocol(modn string, temp float32, topp float32, maxn uint) (mod model.Protocol, err error) {
switch cfg.Type {
case 0:
mod = model.NewOpenAI(
modn, cfg.Separator,
temp, topp, maxn,
)
case 1:
mod = model.NewOLLaMA(
modn, cfg.Separator,
temp, topp, maxn,
)
case 2:
mod = model.NewGenAI(
modn,
temp, topp, maxn,
)
default:
err = errors.New("unsupported model type " + strconv.Itoa(int(cfg.Type)))
}
return
}
// ModelBool 支持打印成 "是/否"
type ModelBool bool
func (mb ModelBool) String() string {
if mb {
return "是"
}
return "否"
}
// ModelKey 支持隐藏密钥
type ModelKey string
func (mk ModelKey) String() string {
if len(mk) == 0 {
return "未设置"
}
if len(mk) <= 4 {
return "****"
}
key := string(mk)
return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:]
}
type config struct {
ModelName string
ImageModelName string
AgentModelName string
Type ModelType
ImageType ModelType
AgentType ModelType
MaxN uint
TopP float32
SystemP string
API string
ImageAPI string
AgentAPI string
Key ModelKey
ImageKey ModelKey
AgentKey ModelKey
Separator string
NoSystemP ModelBool
}
func newconfig() config {
return config{
ModelName: model.ModelDeepDeek,
SystemP: chat.SystemPrompt,
API: deepinfra.OpenAIDeepInfra,
}
}
func (c *config) String() string {
topp, maxn := c.mparams()
sb := strings.Builder{}
sb.WriteString(fmt.Sprintf("• 模型名:%s\n", c.ModelName))
sb.WriteString(fmt.Sprintf("• 图像模型名:%s\n", c.ImageModelName))
sb.WriteString(fmt.Sprintf("• Agent模型名%s\n", c.AgentModelName))
sb.WriteString(fmt.Sprintf("• 接口类型:%v\n", c.Type))
sb.WriteString(fmt.Sprintf("• 图像接口类型:%v\n", c.ImageType))
sb.WriteString(fmt.Sprintf("• Agent接口类型%v\n", c.AgentType))
sb.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn))
sb.WriteString(fmt.Sprintf("• TopP%.1f\n", topp))
sb.WriteString(fmt.Sprintf("• 系统提示词:%s\n", c.SystemP))
sb.WriteString(fmt.Sprintf("• 接口地址:%s\n", c.API))
sb.WriteString(fmt.Sprintf("• 图像接口地址:%s\n", c.ImageAPI))
sb.WriteString(fmt.Sprintf("• Agent接口地址%s\n", c.AgentAPI))
sb.WriteString(fmt.Sprintf("• 密钥:%v\n", c.Key))
sb.WriteString(fmt.Sprintf("• 图像密钥:%v\n", c.ImageKey))
sb.WriteString(fmt.Sprintf("• Agent密钥%v\n", c.AgentKey))
sb.WriteString(fmt.Sprintf("• 分隔符:%s\n", c.Separator))
sb.WriteString(fmt.Sprintf("• 支持系统提示词:%v\n", !c.NoSystemP))
return sb.String()
}
func (c *config) isvalid() bool {
return c.ModelName != "" && c.API != "" && c.Key != ""
}
// 获取全局模型参数TopP和最大长度
func (c *config) mparams() (topp float32, maxn uint) {
// 处理TopP参数
topp = c.TopP
if topp == 0 {
topp = 0.9
}
// 处理最大长度参数
maxn = c.MaxN
if maxn == 0 {
maxn = 4096
}
return topp, maxn
}
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[T ~string](ptr *T) 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 = T(args)
err := c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}
func newextrasetbool[T ~bool](ptr *T) 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 = T(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 newextrasetmodeltype(ptr *ModelType) 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
}
typ, err := newModelType(args)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
*ptr = typ
err = c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}

View File

@@ -1,18 +1,13 @@
// Package aichat OpenAI聊天和群聊总结
// Package aichat 大模型聊天和Agent
package aichat
import (
"encoding/json"
"math/rand"
"strconv"
"strings"
"time"
"github.com/fumiama/deepinfra"
"github.com/fumiama/deepinfra/model"
goba "github.com/fumiama/go-onebot-agent"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/extension/single"
@@ -23,7 +18,6 @@ import (
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/chat"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
)
var (
@@ -31,27 +25,8 @@ var (
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Extra: control.ExtraFromString("aichat"),
Brief: "OpenAI聊天",
Help: "- 设置AI聊天触发概率10\n" +
"- 设置AI聊天温度80\n" +
"- 设置AI聊天(识图|Agent)接口类型[OpenAI|OLLaMA|GenAI]\n" +
"- 设置AI聊天(不)使用Agent模式\n" +
"- 设置AI聊天(不)支持系统提示词\n" +
"- 设置AI聊天(识图|Agent)接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
"- 设置AI聊天(识图|Agent)密钥xxx\n" +
"- 设置AI聊天(识图|Agent)模型名Qwen/Qwen3-8B\n" +
"- 查看AI聊天系统提示词\n" +
"- 重置AI聊天系统提示词\n" +
"- 设置AI聊天系统提示词xxx\n" +
"- 设置AI聊天分隔符</think>(留空则清除)\n" +
"- 设置AI聊天(不)响应AT\n" +
"- 设置AI聊天最大长度4096\n" +
"- 设置AI聊天TopP 0.9\n" +
"- 设置AI聊天(不)以AI语音输出\n" +
"- 查看AI聊天配置\n" +
"- 重置AI聊天\n" +
"- 群聊总结 [消息数目]|群聊总结 1000\n" +
"- /gpt [内容] (使用大模型聊天)\n",
Brief: "大模型聊天和Agent",
Help: "- (随意聊天, 概率匹配)",
PrivateDataFolder: "aichat",
}).ApplySingle(single.New(
@@ -66,46 +41,46 @@ var (
)
var (
limit = ctxext.NewLimiterManager(time.Second*30, 1)
fastfailnorecord = false
)
func init() {
en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
stor, err := newstorage(ctx, gid)
if err != nil {
logrus.Warnln("ERROR: ", err)
en.OnMessage(chat.EnsureConfig, func(ctx *zero.Ctx) bool {
stor, ok := ctx.State[zero.StateKeyPrefixKeep+"aichatcfg_stor__"].(chat.Storage)
if !ok {
logrus.Warnln("ERROR: cannot get stor")
return false
}
ctx.State["__aichat_stor__"] = stor
return ctx.ExtractPlainText() != "" &&
(!stor.noreplyat() || (stor.noreplyat() && !ctx.Event.IsToMe))
if !(ctx.ExtractPlainText() != "" &&
(!stor.NoReplyAt() || (stor.NoReplyAt() && !ctx.Event.IsToMe))) {
return false
}
rate := stor.Rate()
if !ctx.Event.IsToMe && rand.Intn(100) >= int(rate) {
return false
}
if chat.AC.Key == "" {
logrus.Warnln("ERROR: get extra err: empty key")
return false
}
if ctx.Event.IsToMe {
ctx.Block()
}
return true
}).SetBlock(false).Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
stor := ctx.State["__aichat_stor__"].(storage)
rate := stor.rate()
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 := stor.temp()
topp, maxn := cfg.mparams()
stor := ctx.State[zero.StateKeyPrefixKeep+"aichatcfg_stor__"].(chat.Storage)
temperature := stor.Temp()
topp, maxn := chat.AC.MParams()
if !stor.noagent() && cfg.AgentAPI != "" && cfg.AgentModelName != "" {
x := deepinfra.NewAPI(cfg.AgentAPI, string(cfg.AgentKey))
mod, err := cfg.Type.protocol(cfg.AgentModelName, temperature, topp, maxn)
logrus.Debugln("[aichat] agent mode test: noagent", stor.NoAgent(), "hasapi", chat.AC.AgentAPI != "", "hasmodel", chat.AC.AgentModelName != "")
if !stor.NoAgent() && chat.AC.AgentAPI != "" && chat.AC.AgentModelName != "" {
logrus.Debugln("[aichat] enter agent mode")
x := deepinfra.NewAPI(chat.AC.AgentAPI, string(chat.AC.AgentKey))
mod, err := chat.AC.Type.Protocol(chat.AC.AgentModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
@@ -117,49 +92,81 @@ func init() {
role = goba.PermRoleOwner
}
}
ag := chat.AgentOf(ctx.Event.SelfID)
if cfg.ImageAPI != "" && !ag.CanViewImage() {
mod, err := cfg.ImageType.protocol(cfg.ImageModelName, temperature, topp, maxn)
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
logrus.Warnln("ERROR: cannot get ctrl mamager")
}
ag := chat.AgentOf(ctx.Event.SelfID, c.Service)
logrus.Debugln("[aichat] got agent")
if chat.AC.ImageAPI != "" && !ag.CanViewImage() {
mod, err := chat.AC.ImageType.Protocol(chat.AC.ImageModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
}
ag.SetViewImageAPI(deepinfra.NewAPI(cfg.ImageAPI, string(cfg.ImageKey)), mod)
ag.SetViewImageAPI(deepinfra.NewAPI(chat.AC.ImageAPI, string(chat.AC.ImageKey)), mod)
logrus.Debugln("[aichat] agent set img")
}
ctx.NoTimeout()
logrus.Debugln("[aichat] agent set no timeout")
hasresp := false
ispuremsg := false
hassavemem := false
var (
reqs []zero.APIRequest
cl func()
)
defer func() {
if cl != nil {
cl()
}
}()
for i := 0; i < 8; i++ { // 最大运行 8 轮因为问答上下文只有 16
reqs := chat.CallAgent(ag, zero.SuperUserPermission(ctx), x, mod, gid, role)
reqs, cl = chat.CallAgent(ag, zero.SuperUserPermission(ctx), i+1, x, mod, gid, role)
if cl != nil {
cl()
cl = nil
}
if len(reqs) == 0 {
logrus.Debugln("[aichat] agent call got empty response")
break
}
hasresp = true
for _, req := range reqs {
resp := ctx.CallAction(req.Action, req.Params)
logrus.Infoln("[aichat] agent get resp:", reqs)
ag.AddResponse(gid, &goba.APIResponse{
Status: resp.Status,
Data: json.RawMessage(resp.Data.Raw),
Message: resp.Message,
Wording: resp.Wording,
RetCode: resp.RetCode,
})
if req.Action == goba.SVM { // is a fake action
if hassavemem {
ag.AddTerminus(gid)
logrus.Warnln("[aichat] agent call save mem multi times, force inserting EOA")
return
}
hassavemem = true
continue
}
if req.Action == "send_private_msg" || req.Action == "send_group_msg" {
if ispuremsg {
ag.AddTerminus(gid)
logrus.Warnln("[aichat] agent call send msg multi times, force inserting EOA")
return
}
ispuremsg = true
}
_ = ctx.CallAction(req.Action, req.Params)
}
}
if hasresp {
ag.AddTerminus(gid)
return
}
// no response, fall back to normal chat
logrus.Debugln("[aichat] agent fell back to normal chat")
}
x := deepinfra.NewAPI(cfg.API, string(cfg.Key))
mod, err := cfg.Type.protocol(cfg.ModelName, temperature, topp, maxn)
x := deepinfra.NewAPI(chat.AC.API, string(chat.AC.Key))
mod, err := chat.AC.Type.Protocol(chat.AC.ModelName, temperature, topp, maxn)
if err != nil {
logrus.Warnln("ERROR: ", err)
return
}
data, err := x.Request(chat.GetChatContext(mod, gid, cfg.SystemP, bool(cfg.NoSystemP)))
data, err := x.Request(chat.GetChatContext(mod, gid, chat.AC.SystemP, bool(chat.AC.NoSystemP)))
if err != nil {
logrus.Warnln("[aichat] post err:", err)
return
@@ -182,295 +189,21 @@ func init() {
logrus.Infoln("[aichat] 回复内容:", t)
recCfg := airecord.GetConfig()
record := ""
if !stor.norecord() {
if !fastfailnorecord && !stor.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))
if record != "" {
ctx.SendChain(message.Record(record))
continue
}
fastfailnorecord = true
}
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(ctxext.NewStorageSaveBitmapHandler(bitmaprate, 0, 100))
en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBitmapHandler(bitmaptemp, 0, 100))
en.OnPrefix("设置AI聊天接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetmodeltype(&cfg.Type))
en.OnPrefix("设置AI聊天识图接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetmodeltype(&cfg.ImageType))
en.OnPrefix("设置AI聊天Agent接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetmodeltype(&cfg.AgentType))
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.ImageAPI))
en.OnPrefix("设置AI聊天Agent接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.AgentAPI))
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.ImageKey))
en.OnPrefix("设置AI聊天Agent密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.AgentKey))
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.ImageModelName))
en.OnPrefix("设置AI聊天Agent模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.AgentModelName))
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(ctxext.NewStorageSaveBoolHandler(bitmapnrat))
en.OnRegex("^设置AI聊天(不)?支持系统提示词$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetbool(&cfg.NoSystemP))
en.OnRegex("^设置AI聊天(不)?使用Agent模式$", ensureconfig, zero.SuperUserPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBoolHandler(bitmapnagt))
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.AdminPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBoolHandler(bitmapnrec))
en.OnFullMatch("查看AI聊天配置", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(
message.Text(
"【当前AI聊天本群配置】\n",
"• 触发概率:", int(stor.rate()), "\n",
"• 温度:", stor.temp(), "\n",
"• 以AI语音输出", ModelBool(!stor.norecord()), "\n",
"• 使用Agent", ModelBool(!stor.noagent()), "\n",
"• 响应@", ModelBool(!stor.noreplyat()), "\n",
),
message.Text("【当前AI聊天全局配置】\n", &cfg),
)
})
en.OnFullMatch("重置AI聊天", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
chat.ResetChat()
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
}
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")
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 调用大模型API进行总结
summary, err := llmchat(summaryPrompt, stor.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
}
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
}
stor, err := newstorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 调用大模型API进行聊天
reply, err := llmchat(query, stor.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 float32) (string, error) {
topp, maxn := cfg.mparams()
x := deepinfra.NewAPI(cfg.API, string(cfg.Key))
mod, err := cfg.Type.protocol(cfg.ModelName, temp, topp, maxn)
if err != nil {
return "", nil
}
data, err := x.Request(mod.User(model.NewContentText(prompt)))
if err != nil {
return "", err
}
return strings.TrimSpace(data), nil
}

View File

@@ -1,49 +0,0 @@
package aichat
import (
"github.com/FloatTech/zbputils/ctxext"
zero "github.com/wdvxdr1123/ZeroBot"
)
const (
bitmaprate = 0x0000ff
bitmaptemp = 0x00ff00
bitmapnagt = 0x010000
bitmapnrec = 0x020000
bitmapnrat = 0x040000
)
type storage ctxext.Storage
func newstorage(ctx *zero.Ctx, gid int64) (storage, error) {
s, err := ctxext.NewStorage(ctx, gid)
return storage(s), err
}
func (s storage) rate() uint8 {
return uint8((ctxext.Storage)(s).Get(bitmaprate))
}
func (s storage) temp() float32 {
temp := (ctxext.Storage)(s).Get(bitmaptemp)
// 处理温度参数
if temp <= 0 {
temp = 70 // default setting
}
if temp > 100 {
temp = 100
}
return float32(temp) / 100
}
func (s storage) noagent() bool {
return (ctxext.Storage)(s).GetBool(bitmapnagt)
}
func (s storage) norecord() bool {
return (ctxext.Storage)(s).GetBool(bitmapnrec)
}
func (s storage) noreplyat() bool {
return (ctxext.Storage)(s).GetBool(bitmapnrat)
}

181
plugin/aichatcfg/main.go Normal file
View File

@@ -0,0 +1,181 @@
// Package aichatcfg aichat 的配置, 优先级要比 aichat 高
package aichatcfg
import (
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
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: "aichat 的配置",
Help: "- 设置AI聊天触发概率10\n" +
"- 设置AI聊天温度80\n" +
"- 设置AI聊天(|识图|Agent)接口类型[OpenAI|OLLaMA|GenAI]\n" +
"- 设置AI聊天(不)使用Agent模式\n" +
"- 设置AI聊天(不)支持系统提示词\n" +
"- 设置AI聊天(|识图|Agent)接口地址https://api.siliconflow.cn/v1/chat/completions\n" +
"- 设置AI聊天(|识图|Agent)密钥xxx\n" +
"- 设置AI聊天(|识图|Agent)模型名Qwen/Qwen3-8B\n" +
"- 查看AI聊天系统提示词\n" +
"- 重置AI聊天系统提示词\n" +
"- 设置AI聊天系统提示词xxx\n" +
"- 设置AI聊天Agent性格" +
"- 查看AI聊天Agent性格xxx" +
"- 重置AI聊天Agent性格\n" +
"- 设置AI聊天分隔符</think>(留空则清除)\n" +
"- 设置AI聊天(不)响应AT\n" +
"- 设置AI聊天最大长度4096\n" +
"- 设置AI聊天TopP 0.9\n" +
"- 设置AI聊天(不)以AI语音输出\n" +
"- 查看AI聊天配置\n" +
"- 重置AI聊天Agent\n" +
"- 重置AI聊天\n",
})
)
func init() {
en.UsePreHandler(chat.EnsureConfig, func(ctx *zero.Ctx) bool {
if !chat.IsAgentCharReady {
if chat.AC.AgentChar != "" {
chat.AgentChar = []byte(chat.AC.AgentChar)
}
chat.IsAgentCharReady = true
}
k := zero.StateKeyPrefixKeep + "aichatcfg_stor__"
if _, ok := ctx.State[k]; ok {
return true
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
stor, err := chat.NewStorage(ctx, gid)
if err != nil {
logrus.Warnln("ERROR: ", err)
return false
}
ctx.State[k] = stor
return true
})
en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBitmapHandler(chat.BitmapRate, 0, 100))
en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBitmapHandler(chat.BitmapTemp, 0, 100))
en.OnPrefix("设置AI聊天接口类型", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetModelType(&chat.AC.Type))
en.OnPrefix("设置AI聊天识图接口类型", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetModelType(&chat.AC.ImageType))
en.OnPrefix("设置AI聊天Agent接口类型", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetModelType(&chat.AC.AgentType))
en.OnPrefix("设置AI聊天接口地址", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.API))
en.OnPrefix("设置AI聊天识图接口地址", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.ImageAPI))
en.OnPrefix("设置AI聊天Agent接口地址", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.AgentAPI))
en.OnPrefix("设置AI聊天密钥", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.Key))
en.OnPrefix("设置AI聊天识图密钥", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.ImageKey))
en.OnPrefix("设置AI聊天Agent密钥", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.AgentKey))
en.OnPrefix("设置AI聊天模型名", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.ModelName))
en.OnPrefix("设置AI聊天识图模型名", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.ImageModelName))
en.OnPrefix("设置AI聊天Agent模型名", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.AgentModelName))
en.OnPrefix("设置AI聊天系统提示词", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.SystemP))
en.OnPrefix("设置AI聊天Agent性格", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.AgentChar))
en.OnFullMatch("查看AI聊天系统提示词", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
ctx.SendChain(message.Text(chat.AC.SystemP))
})
en.OnFullMatch("查看AI聊天Agent性格", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
ctx.SendChain(message.Text(chat.AC.AgentChar))
})
en.OnFullMatch("重置AI聊天系统提示词", chat.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
}
chat.AC.SystemP = chat.SystemPrompt
err := c.SetExtra(&chat.AC)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
})
en.OnFullMatch("重置AI聊天Agent性格", chat.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
}
chat.AC.AgentChar = ""
err := c.SetExtra(&chat.AC)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功, 请重置AI聊天Agent"))
})
en.OnPrefix("设置AI聊天分隔符", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.Separator))
en.OnRegex("^设置AI聊天(不)?响应AT$", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBoolHandler(chat.BitmapNrat))
en.OnRegex("^设置AI聊天(不)?支持系统提示词$", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetBool(&chat.AC.NoSystemP))
en.OnRegex("^设置AI聊天(不)?使用Agent模式$", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBoolHandler(chat.BitmapNagt))
en.OnPrefix("设置AI聊天最大长度", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetUint(&chat.AC.MaxN))
en.OnPrefix("设置AI聊天TopP", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetFloat32(&chat.AC.TopP))
en.OnRegex("^设置AI聊天(不)?以AI语音输出$", chat.EnsureConfig, zero.AdminPermission).SetBlock(true).
Handle(ctxext.NewStorageSaveBoolHandler(chat.BitmapNrec))
en.OnFullMatch("查看AI聊天配置", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
stor, err := chat.NewStorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(
message.Text(
"【当前AI聊天本群配置】\n",
"• 触发概率:", int(stor.Rate()), "\n",
"• 温度:", stor.Temp(), "\n",
"• 以AI语音输出", chat.ModelBool(!stor.NoRecord()), "\n",
"• 使用Agent", chat.ModelBool(!stor.NoAgent()), "\n",
"• 响应@", chat.ModelBool(!stor.NoReplyAt()), "\n",
),
message.Text("【当前AI聊天全局配置】\n", &chat.AC),
)
})
en.OnFullMatch("重置AI聊天Agent", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
chat.ResetAgents()
ctx.SendChain(message.Text("成功"))
})
en.OnFullMatch("重置AI聊天", chat.EnsureConfig, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
chat.ResetChat()
ctx.SendChain(message.Text("成功"))
})
}

View File

@@ -25,10 +25,10 @@ import (
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
"github.com/disintegration/imaging"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/disk"
"github.com/shirou/gopsutil/v3/host"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v4/cpu"
"github.com/shirou/gopsutil/v4/disk"
"github.com/shirou/gopsutil/v4/host"
"github.com/shirou/gopsutil/v4/mem"
"github.com/sirupsen/logrus"
"golang.org/x/text/cases"
"golang.org/x/text/language"

246
plugin/llm/main.go Normal file
View File

@@ -0,0 +1,246 @@
// Package llm 大模型聊天和群聊总结
package llm
import (
"strconv"
"strings"
"time"
"github.com/fumiama/deepinfra"
"github.com/fumiama/deepinfra/model"
"github.com/tidwall/gjson"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/extension/single"
"github.com/wdvxdr1123/ZeroBot/message"
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,
Brief: "大模型聊天和群聊总结",
Help: "- 群聊总结 [消息数目]|群聊总结 1000\n" +
"- /gpt [内容] (使用大模型聊天)\n",
}).ApplySingle(single.New(
single.WithKeyFn(func(ctx *zero.Ctx) int64 {
if ctx.Event.GroupID == 0 {
return -ctx.Event.UserID
}
return ctx.Event.GroupID
}),
// no post option, silently quit
))
)
var (
limit = ctxext.NewLimiterManager(time.Second*30, 1)
)
func init() {
// 添加群聊总结功能
en.OnRegex(`^群聊总结\s?(\d*)$`, chat.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
}
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
}
// 构造总结请求提示 (使用通用版省流提示词)
// 使用反引号定义多行字符串,更清晰
promptTemplate := `请对以下群聊对话进行【极简总结】。
要求:
1. 剔除客套与废话,直击主题。
2. 使用 Markdown 列表格式。
3. 按以下结构输出:
- 🎯 核心议题:(一句话概括)
- 💡 关键观点/结论:(提取3-5个重点)
- ✅ 下一步/待办:(如果有,明确谁做什么)
群聊对话内容如下:
`
summaryPrompt := promptTemplate + strings.Join(messages, "\n")
stor, err := chat.NewStorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 调用大模型API进行总结
summary, err := llmchat(summaryPrompt, stor.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", chat.EnsureConfig).SetBlock(true).Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
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
}
stor, err := chat.NewStorage(ctx, gid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 调用大模型API进行聊天
reply, err := llmchat(query, stor.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 float32) (string, error) {
topp, maxn := chat.AC.MParams()
x := deepinfra.NewAPI(chat.AC.API, string(chat.AC.Key))
mod, err := chat.AC.Type.Protocol(chat.AC.ModelName, temp, topp, maxn)
if err != nil {
return "", nil
}
data, err := x.Request(mod.User(model.NewContentText(prompt)))
if err != nil {
return "", err
}
return strings.TrimSpace(data), nil
}

134
plugin/rsshub/domain/job.go Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,178 @@
package domain
import (
"context"
"errors"
"net/http"
"os"
"time"
"github.com/jinzhu/gorm"
"github.com/sirupsen/logrus"
)
// 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
}

View File

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

View File

@@ -0,0 +1,271 @@
package domain
import (
"context"
"errors"
"fmt"
"time"
"github.com/jinzhu/gorm"
"github.com/sirupsen/logrus"
)
// 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]

152
plugin/rsshub/main.go Normal file
View File

@@ -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.AutoRegister(&ctrl.Options[*zero.Ctx]{
// 默认不启动
DisableOnDefault: false,
Brief: "rsshub订阅姬",
// 详细帮助
Help: "rsshub订阅姬desu~ \n" +
"支持的详细订阅列表文档可见:\n" +
"https://rsshub.netlify.app/zh/ \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))
}
}
}
}

100
plugin/rsshub/view.go Normal file
View File

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

View File

@@ -2,19 +2,10 @@
package thesaurus
import (
"math/rand"
"strings"
"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/process"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
)
@@ -23,143 +14,35 @@ func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "词典匹配回复, 仅@触发",
Help: "- 切换[kimo|傲娇|可爱]词库",
PublicDataFolder: "Chat",
})
engine.OnRegex(`^切换(kimo|傲娇|可爱)词库$`, zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: 找不到 manager"))
return
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
d := c.GetData(gid)
t := int64(0)
switch ctx.State["regex_matched"].([]string)[1] {
case "kimo":
t = tKIMO
case "傲娇":
t = tDERE
case "可爱":
t = tKAWA
}
err := c.SetData(gid, (d&^3)|t)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("成功!"))
})
go func() {
var seg gse.Segmenter
err := seg.LoadDictEmbed()
if err != nil {
panic(err)
}
smd, err := engine.GetLazyData("simai.yml", false)
if err != nil {
panic(err)
}
sm := simai{D: make(map[string][]string, 8192), K: make(map[string][]string, 16384)}
err = yaml.Unmarshal(smd, &sm)
if err != nil {
panic(err)
}
chatListD := make([]string, 0, len(sm.D))
for k := range sm.D {
chatListD = append(chatListD, k)
}
chatListK := make([]string, 0, len(sm.K))
for k := range sm.K {
chatListK = append(chatListK, k)
}
logrus.Infoln("[thesaurus]加载", len(chatListD), "条傲娇词库", len(chatListK), "条可爱词库")
engine.OnMessage(zero.OnlyToMe, canmatch(tKIMO)).
SetBlock(false).Handle(func(ctx *zero.Ctx) {
msg := ctx.ExtractPlainText()
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 {
return
}
c++
}
if r.Confidence < 0.2 {
engine.OnMessage(zero.OnlyToMe, canmatch()).
SetBlock(false).Handle(func(ctx *zero.Ctx) {
msg := ctx.ExtractPlainText()
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 {
return
}
ctx.Block()
ctx.SendChain(message.Text(r.Reply))
c++
}
})
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))
}()
if r.Confidence < 0.2 {
return
}
ctx.Block()
ctx.SendChain(message.Text(r.Reply))
}
})
}
type simai struct {
D map[string][]string `yaml:"傲娇"`
K map[string][]string `yaml:"可爱"`
}
const (
tKIMO = iota
tDERE
tKAWA
)
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()
}, l...)(ctx)
}
}
func canmatch(typ int64) zero.Rule {
func canmatch() zero.Rule {
return func(ctx *zero.Ctx) bool {
if zero.HasPicture(ctx) {
return false
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return false
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
d := c.GetData(gid)
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))]
text := val[rand.Intn(len(val))]
text = strings.ReplaceAll(text, "{name}", ctx.CardOrNickName(ctx.Event.UserID))
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))
}
return ctx.ExtractPlainText() != ""
}
}

View File

@@ -4,7 +4,6 @@ package wife
import (
"bytes"
"image"
"image/color"
"math/rand"
"strings"
"time"
@@ -15,7 +14,6 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
zbmath "github.com/FloatTech/floatbox/math"
"github.com/FloatTech/imgfactory"
)
@@ -134,24 +132,8 @@ func init() {
})
}
// 马赛克生成
// 高斯模糊生成
func mosaic(dst *imgfactory.Factory, level int) ([]byte, error) {
b := dst.Image().Bounds()
p := imgfactory.NewFactoryBG(dst.W(), dst.H(), color.NRGBA{255, 255, 255, 255})
markSize := zbmath.Max(b.Max.X, b.Max.Y) * sizeList[level] / 200
for yOfMarknum := 0; yOfMarknum <= zbmath.Ceil(b.Max.Y, markSize); yOfMarknum++ {
for xOfMarknum := 0; xOfMarknum <= zbmath.Ceil(b.Max.X, markSize); xOfMarknum++ {
a := dst.Image().At(xOfMarknum*markSize+markSize/2, yOfMarknum*markSize+markSize/2)
cc := color.NRGBAModel.Convert(a).(color.NRGBA)
for y := 0; y < markSize; y++ {
for x := 0; x < markSize; x++ {
xOfPic := xOfMarknum*markSize + x
yOfPic := yOfMarknum*markSize + y
p.Image().Set(xOfPic, yOfPic, cc)
}
}
}
}
return imgfactory.ToBytes(p.Blur(3).Image())
blurRadius := float64(sizeList[level] * 3)
return imgfactory.ToBytes(dst.Blur(blurRadius).Image())
}

View File

@@ -2,6 +2,7 @@
package wordcount
import (
"bytes"
"fmt"
"os"
"regexp"
@@ -10,7 +11,7 @@ import (
"strings"
"time"
"github.com/go-ego/gse"
"github.com/fumiama/jieba"
"github.com/golang/freetype"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
@@ -26,13 +27,11 @@ import (
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() {
@@ -43,8 +42,11 @@ func init() {
PublicDataFolder: "WordCount",
})
cachePath := engine.DataFolder() + "cache/"
// 读取gse内置中文词典
err := seg.LoadDictEmbed()
dat, err := file.GetLazyData("data/Chat/dict.txt", control.Md5File, true)
if err != nil {
panic(err)
}
seg, err := jieba.LoadDictionary(bytes.NewReader(dat))
if err != nil {
panic(err)
}
@@ -108,8 +110,7 @@ func init() {
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)
words := seg.Cut(tex, true)
for _, word := range words {
word = strings.TrimSpace(word)
i := sort.SearchStrings(stopwords, word)

83
plugin/xhstext/xhstext.go Normal file
View File

@@ -0,0 +1,83 @@
// Package xhstext 小红书文案
package xhstext
import (
"time"
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"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
type xhstext struct {
ID uint32 `db:"id"`
Text string `db:"text"`
Label string `db:"label"`
}
var db sql.Sqlite
func init() {
en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "小红书文案",
Help: "- 捧场\n- 有梗",
PublicDataFolder: "Xhstext",
})
// 初始化数据库
initDB := fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
db = sql.New(en.DataFolder() + "xhstext.db")
_, err := en.GetLazyData("xhstext.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Open(time.Hour)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("all_texts", &xhstext{})
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
c, err := db.Count("all_texts")
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Infoln("[xhstext]加载", c, "条小红书文案")
return true
},
)
// 捧场命令
en.OnFullMatch("捧场", initDB).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
var x xhstext
err := db.Find("all_texts", &x, "WHERE label = '捧场' ORDER BY RANDOM() LIMIT 1")
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(x.Text))
})
// 有梗命令
en.OnFullMatch("有梗", initDB).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
var x xhstext
err := db.Find("all_texts", &x, "WHERE label = '有梗' ORDER BY RANDOM() LIMIT 1")
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(x.Text))
})
}

View File

@@ -3,5 +3,6 @@ go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=auto
go mod tidy
::go build -ldflags="-s -w" -o ZeroBot-Plugin.exe
go run main.go
go generate main.go
go run -ldflags "-s -w" main.go
pause

3
run.sh
View File

@@ -3,4 +3,5 @@ go env -w GOPROXY=https://goproxy.cn,direct
go env -w GO111MODULE=auto
go mod tidy
#go build -ldflags="-s -w" -o ZeroBot-Plugin
go run main.go
go generate main.go
go run -ldflags "-s -w" main.go

View File

@@ -12,7 +12,7 @@
"0409": {
"identity": {
"name": "ZeroBot-Plugin",
"version": "1.10.0.2279"
"version": "1.10.9.2338"
},
"description": "",
"minimum-os": "vista",
@@ -36,23 +36,23 @@
"#1": {
"0000": {
"fixed": {
"file_version": "1.10.0.2279",
"product_version": "v1.10.0",
"timestamp": "2025-09-30T23:54:09+08:00"
"file_version": "1.10.9.2338",
"product_version": "v1.10.9",
"timestamp": "2026-01-06T21:25:04+08:00"
},
"info": {
"0409": {
"Comments": "OneBot plugins based on ZeroBot",
"CompanyName": "FloatTech",
"FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin",
"FileVersion": "1.10.0.2279",
"FileVersion": "1.10.9.2338",
"InternalName": "",
"LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.",
"LegalCopyright": "© 2020 - 2026 FloatTech. All Rights Reserved.",
"LegalTrademarks": "",
"OriginalFilename": "ZBP.EXE",
"PrivateBuild": "",
"ProductName": "ZeroBot-Plugin",
"ProductVersion": "v1.10.0",
"ProductVersion": "v1.10.9",
"SpecialBuild": ""
}
}