Compare commits

...

63 Commits

Author SHA1 Message Date
github-actions[bot]
9cb3a5019f
chore(nix): bump deps (#1282)
Some checks failed
最新版 / Build binary CI (386, linux) (push) Has been cancelled
最新版 / Build binary CI (386, windows) (push) Has been cancelled
最新版 / Build binary CI (amd64, linux) (push) Has been cancelled
最新版 / Build binary CI (amd64, windows) (push) Has been cancelled
最新版 / Build binary CI (arm, linux) (push) Has been cancelled
最新版 / Build binary CI (arm64, linux) (push) Has been cancelled
自动更新 nix 依赖 / gomod2nix update (push) Has been cancelled
PushLint / lint (push) Has been cancelled
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-01 01:13:50 +08:00
源文雨
d5729b4e27 🔖 v1.10.18 2026-02-01 01:08:32 +08:00
github-actions[bot]
7f4830d50f
chore(nix): bump deps (#1279)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-01 00:46:18 +08:00
源文雨
39d319bf70 🔖 v1.10.17 2026-02-01 00:40:20 +08:00
源文雨
1dd94c2d37 fix(aichat): concurrent map write 2026-02-01 00:39:20 +08:00
Nobody6825
f6934709cc
fix: fix nix build error (#1280)
Some checks are pending
最新版 / Build binary CI (386, linux) (push) Waiting to run
最新版 / Build binary CI (386, windows) (push) Waiting to run
最新版 / Build binary CI (amd64, linux) (push) Waiting to run
最新版 / Build binary CI (amd64, windows) (push) Waiting to run
最新版 / Build binary CI (arm, linux) (push) Waiting to run
最新版 / Build binary CI (arm64, linux) (push) Waiting to run
PushLint / lint (push) Waiting to run
2026-01-30 23:18:10 +08:00
源文雨
1c8ff7b143 fix(aichat): possible panic when save mem err
Some checks failed
最新版 / Build binary CI (386, linux) (push) Has been cancelled
最新版 / Build binary CI (386, windows) (push) Has been cancelled
最新版 / Build binary CI (amd64, linux) (push) Has been cancelled
最新版 / Build binary CI (amd64, windows) (push) Has been cancelled
最新版 / Build binary CI (arm, linux) (push) Has been cancelled
最新版 / Build binary CI (arm64, linux) (push) Has been cancelled
自动更新 nix 依赖 / gomod2nix update (push) Has been cancelled
PushLint / lint (push) Has been cancelled
2026-01-28 21:33:41 +08:00
github-actions[bot]
79080cc2f0
chore(nix): bump deps (#1278)
Some checks failed
最新版 / Build binary CI (386, linux) (push) Has been cancelled
最新版 / Build binary CI (386, windows) (push) Has been cancelled
最新版 / Build binary CI (amd64, linux) (push) Has been cancelled
最新版 / Build binary CI (amd64, windows) (push) Has been cancelled
最新版 / Build binary CI (arm, linux) (push) Has been cancelled
最新版 / Build binary CI (arm64, linux) (push) Has been cancelled
自动更新 nix 依赖 / gomod2nix update (push) Has been cancelled
PushLint / lint (push) Has been cancelled
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-24 14:29:55 +08:00
himawari
e59ab9f798
🐛修复点歌 (#1277) 2026-01-24 14:26:53 +08:00
源文雨
37dfad9a29 optimize: move console into ab
Some checks failed
自动更新 nix 依赖 / gomod2nix update (push) Has been cancelled
最新版 / Build binary CI (386, linux) (push) Has been cancelled
最新版 / Build binary CI (386, windows) (push) Has been cancelled
最新版 / Build binary CI (amd64, linux) (push) Has been cancelled
最新版 / Build binary CI (amd64, windows) (push) Has been cancelled
最新版 / Build binary CI (arm, linux) (push) Has been cancelled
最新版 / Build binary CI (arm64, linux) (push) Has been cancelled
PushLint / lint (push) Has been cancelled
2026-01-17 21:33:39 +08:00
github-actions[bot]
d84a4a57a8
chore(nix): bump deps (#1274)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 21:25:57 +08:00
源文雨
bb14767829 chore: make lint happy? 2026-01-17 21:25:33 +08:00
源文雨
dcb83bbeee fix(aichat): concurrent map w/r 2026-01-17 21:21:39 +08:00
github-actions[bot]
add70383c9
chore(nix): bump deps (#1271)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 21:04:49 +08:00
源文雨
d43239841d 🔖1.10.16 2026-01-17 21:01:05 +08:00
github-actions[bot]
a3115da138
chore(lint): 改进代码样式 (#1270)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 18:46:01 +08:00
github-actions[bot]
454425daf2
chore(nix): bump deps (#1269)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 18:44:02 +08:00
源文雨
d8c4aa82e3 🔖1.10.15 2026-01-17 18:43:44 +08:00
源文雨
6fe621fbfe chore: make lint happy 2026-01-17 18:43:16 +08:00
源文雨
f27e603e39 feat(aichat): agent +more configs & fix too long log evs 2026-01-17 18:37:56 +08:00
github-actions[bot]
ec8c402593
chore(lint): 改进代码样式 (#1268)
Some checks are pending
自动更新 nix 依赖 / gomod2nix update (push) Waiting to run
最新版 / Build binary CI (386, linux) (push) Waiting to run
最新版 / Build binary CI (386, windows) (push) Waiting to run
最新版 / Build binary CI (amd64, linux) (push) Waiting to run
最新版 / Build binary CI (amd64, windows) (push) Waiting to run
最新版 / Build binary CI (arm, linux) (push) Waiting to run
最新版 / Build binary CI (arm64, linux) (push) Waiting to run
PushLint / lint (push) Waiting to run
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 12:26:02 +08:00
github-actions[bot]
be42865c2a
chore(lint): 改进代码样式 (#1267)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 12:13:31 +08:00
github-actions[bot]
ac2d9bb596
chore(nix): bump deps (#1266)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-17 12:13:08 +08:00
源文雨
b025ca5738 🔖1.10.14 2026-01-17 12:00:28 +08:00
源文雨
4fd22ee7c7 fix(aichat): agent may consume too many tokens 2026-01-17 11:59:45 +08:00
fumiama
e4fede7a75
feat(aichat): remove unnecessary ensure cfg (#1265) 2026-01-13 18:01:29 +08:00
github-actions[bot]
4fa79497da
chore(nix): bump deps (#1264)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-11 23:57:24 +08:00
源文雨
8bf1f0b221 🔖 v1.10.13 2026-01-11 23:54:12 +08:00
源文雨
7d231dfd8d fix(aichat): agent may panic on logev 2026-01-11 23:51:04 +08:00
github-actions[bot]
c8d30b9f06
chore(nix): bump deps (#1263)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-09 21:16:42 +08:00
github-actions[bot]
1e6bc9abdd
chore(nix): bump deps (#1261)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-09 21:10:20 +08:00
github-actions[bot]
625ed39d13
chore(lint): 改进代码样式 (#1262)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-09 21:10:03 +08:00
源文雨
55e538cdb6 🔖 v1.10.12 2026-01-09 21:09:33 +08:00
fumiama
b432731c9b
feat(aichat): agent reduce log evs (#1260) 2026-01-09 14:14:34 +08:00
github-actions[bot]
d14e1be92b
chore(nix): bump deps (#1259)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-09 00:14:26 +08:00
源文雨
6c4c4770f9 🔖 v1.10.11 2026-01-09 00:11:55 +08:00
源文雨
213b4e21ad fix(aichat): agent eventt log 2026-01-09 00:11:32 +08:00
github-actions[bot]
fafbc8165e
chore(nix): bump deps (#1258)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-01-08 23:14:34 +08:00
源文雨
981f4c5dd7 🔖 v1.10.10 2026-01-08 23:10:33 +08:00
源文雨
d1abd3e1d5 fix(aichat): log message may panic 2026-01-08 23:09:57 +08:00
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
35 changed files with 1050 additions and 1348 deletions

View File

@ -38,7 +38,7 @@ 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
@ -46,3 +46,4 @@ jobs:
with:
delete-branch: true
branch-suffix: short-commit-hash
title: "chore(nix): bump deps"

View File

@ -18,11 +18,12 @@ jobs:
with:
ref: master
fetch-depth: 0
submodules: 'recursive'
- name: Tidy Modules
- name: Prepare Necessary Runtime Files
run: |
go mod tidy
go generate main.go
go mod tidy
- name: Run Lint
uses: golangci/golangci-lint-action@master
@ -42,3 +43,5 @@ jobs:
if: ${{ !github.head_ref }}
continue-on-error: true
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

@ -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>
@ -1514,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>
@ -1594,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
@ -1614,10 +1633,22 @@ 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聊天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>

View File

@ -1,7 +1,6 @@
//go:build !windows
// Package console sets console's behavior on init
package console
package abineundo
import (
"fmt"

View File

@ -1,5 +1,4 @@
// Package console sets console's behavior on init
package console
package abineundo
import (
"bytes"

View File

@ -9,7 +9,7 @@
//
// Place this package at the very top of top-level main.go so its init (present
// or future) executes before other plugin packages, filling in a predictable
// plugin priority.
// plugin priority and setup console properties.
//
// Typical usage:
//

2
data

@ -1 +1 @@
Subproject commit 1b0abcd3fe4943fa3298885cf0311e8d94a02c0b
Subproject commit 74e3bf5dc8639de19b1d4a41c79b0a4be14c4667

View File

@ -18,9 +18,9 @@ buildGoApplication {
version = "1.8.0";
pwd = ./.;
src = ./.;
# spec go version manually bcs
# https://github.com/nix-community/gomod2nix/blob/30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6/builder/default.nix#L130
# do not work
go = pkgs.go_1_20;
go = pkgs.go_1_24;
preBuild = ''
go generate main.go
'';
modules = ./gomod2nix.toml;
}

View File

@ -28,11 +28,11 @@
]
},
"locked": {
"lastModified": 1742209644,
"narHash": "sha256-jMy1XqXqD0/tJprEbUmKilTkvbDY/C0ZGSsJJH4TNCE=",
"lastModified": 1767019875,
"narHash": "sha256-NodN+lhWTD59b44Q2bPjE1edINfjfRkQYdZsrxifCeU=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "8f3534eb8f6c5c3fce799376dc3b91bae6b11884",
"rev": "49662a44272806ff785df2990a420edaaca15db4",
"type": "github"
},
"original": {
@ -43,11 +43,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1745391562,
"narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=",
"lastModified": 1769461804,
"narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7",
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
"type": "github"
},
"original": {
@ -57,28 +57,11 @@
"type": "github"
}
},
"nixpkgs-with-go_1_20": {
"locked": {
"lastModified": 1710843028,
"narHash": "sha256-CMbK45c4nSkGvayiEHFkGFH+doGPbgo3AWfecd2t1Fk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "33c51330782cb486764eb598d5907b43dc87b4c2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "33c51330782cb486764eb598d5907b43dc87b4c2",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs",
"nixpkgs-with-go_1_20": "nixpkgs-with-go_1_20"
"nixpkgs": "nixpkgs"
}
},
"systems": {

109
flake.nix
View File

@ -1,7 +1,6 @@
{
description = " ZeroBot OneBot ";
inputs.nixpkgs-with-go_1_20.url = "github:NixOS/nixpkgs/33c51330782cb486764eb598d5907b43dc87b4c2";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.gomod2nix.url = "github:nix-community/gomod2nix";
@ -11,53 +10,85 @@
outputs = {
self,
nixpkgs,
nixpkgs-with-go_1_20,
flake-utils,
gomod2nix,
...
} @ inputs: let
allSystems = flake-utils.lib.allSystems;
in (
flake-utils.lib.eachSystem allSystems
(system: let
old-nixpkgs = nixpkgs-with-go_1_20.legacyPackages.${system};
pkgs = import nixpkgs {
inherit system;
}: (flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
overlays = [
(_: _: {
go_1_20 = old-nixpkgs.go_1_20;
})
callPackage = pkgs.callPackage;
# Simple test check added to nix flake check
go-test = pkgs.stdenvNoCC.mkDerivation {
name = "go-test";
dontBuild = true;
src = ./.;
doCheck = true;
nativeBuildInputs = with pkgs; [
go
writableTmpDirAsHomeHook
];
checkPhase = ''
go test -v ./...
'';
installPhase = ''
mkdir "$out"
'';
};
# The current default sdk for macOS fails to compile go projects, so we use a newer one for now.
# This has no effect on other platforms.
callPackage = pkgs.darwin.apple_sdk_11_0.callPackage or pkgs.callPackage;
in {
# doCheck will fail at write files
packages = rec {
ZeroBot-Plugin = (callPackage ./. (inputs
// {
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
}))
.overrideAttrs (_: {doCheck = false;});
default = ZeroBot-Plugin;
docker_builder = pkgs.dockerTools.buildLayeredImage {
name = "ZeroBot-Plugin";
tag = "latest";
contents = [
self.packages.${system}.ZeroBot-Plugin
pkgs.cacert
];
# Simple lint check added to nix flake check
go-lint = pkgs.stdenvNoCC.mkDerivation {
name = "go-lint";
dontBuild = true;
src = ./.;
doCheck = true;
nativeBuildInputs = with pkgs; [
golangci-lint
go
writableTmpDirAsHomeHook
];
checkPhase = ''
golangci-lint run
'';
installPhase = ''
mkdir "$out"
'';
};
# doCheck will fail at download files
ZeroBot-Plugin = (callPackage ./. {
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
}).overrideAttrs (_: {doCheck = false;});
# Build container layered image, useful overtime to save storage on duplicated layers
containerImage = pkgs.dockerTools.buildLayeredImage {
name = "ZeroBot-Plugin";
tag = "latest";
created = "now";
contents = [
pkgs.cacert
pkgs.openssl
];
config = {
Cmd = ["${ZeroBot-Plugin}/bin/ZeroBot-Plugin"];
};
};
in {
inherit containerImage;
checks = {
inherit go-test go-lint;
};
packages.default = ZeroBot-Plugin;
devShells.default = callPackage ./shell.nix {
inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix;
};
# Custom application to build and load container image into the docker daemon
# For now docker is a requirement
apps.build-and-load = {
type = "app";
program = "${pkgs.writeShellScriptBin "build-and-load" ''
nix build .#containerImage.${system}
docker load < result
echo "Container image loaded"
''}/bin/build-and-load";
};
formatter = pkgs.alejandra;
})
);
}
));
}

35
go.mod
View File

@ -11,12 +11,12 @@ require (
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.1-0.20251222053912-c0e551828149
github.com/FloatTech/zbputils v1.7.2-0.20251221141527-35f149bccf9a
github.com/FloatTech/zbpctrl v1.7.1
github.com/FloatTech/zbputils v1.7.2-0.20260131170726-494cb1776a47
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
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/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
@ -24,7 +24,7 @@ require (
github.com/fumiama/cron v1.3.0
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-20251221163750-c11c679e4636
github.com/fumiama/go-onebot-agent v0.0.0-20260128132028-05e6b4809f0a
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3
github.com/fumiama/imgsz v0.0.4
@ -34,6 +34,7 @@ require (
github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.6.0
github.com/guohuiyuan/music-lib v1.0.2-0.20260121020416-53f6cb24629d
github.com/jinzhu/gorm v1.9.16
github.com/jozsefsallai/gophersauce v1.0.1
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5
@ -43,33 +44,33 @@ require (
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/v4 v4.25.11
github.com/sirupsen/logrus v1.9.3
github.com/shirou/gopsutil/v4 v4.25.12
github.com/sirupsen/logrus v1.9.4
github.com/tidwall/gjson v1.18.0
github.com/wcharczuk/go-chart/v2 v2.1.2
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20251002074418-56567b7fc282
gitlab.com/gomidi/midi/v2 v2.3.16
github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260117102541-393033a35adb
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.10.3 // indirect
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-20211024235047-1546f124cd8b // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // 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.4.0 // indirect
github.com/ebitengine/oto/v3 v3.3.2 // 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.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.3.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // 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
@ -77,9 +78,9 @@ require (
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-20251013123823-9fd1530e3ec3 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mmcdole/goxpp v1.1.1 // 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
@ -88,7 +89,7 @@ require (
github.com/pkumza/numcn v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/tetratelabs/wazero v1.5.0 // 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

92
go.sum
View File

@ -1,6 +1,5 @@
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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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=
@ -15,13 +14,13 @@ 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.1-0.20251222053912-c0e551828149 h1:iJgVyjZhzNbwpvB3A0rUZpeOfV8rNpFa9isrrya3L9o=
github.com/FloatTech/zbpctrl v1.7.1-0.20251222053912-c0e551828149/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE=
github.com/FloatTech/zbputils v1.7.2-0.20251221141527-35f149bccf9a h1:q2kVZfYBeJXzdBw92BPGTeRZWft0SGB3e11VFsv+wx8=
github.com/FloatTech/zbputils v1.7.2-0.20251221141527-35f149bccf9a/go.mod h1:99hNZ2re2AwUdiMC5y+8nX8ObWwluFF7+k3jZTO+P+c=
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.20260131170726-494cb1776a47 h1:slMr6r4XDKnYCFmWhcHA02O3MTAUnU8p2gEe843JyQA=
github.com/FloatTech/zbputils v1.7.2-0.20260131170726-494cb1776a47/go.mod h1:W2kaR/A5oUtEb7DnveXCc0T374VjI+f3KmOWH9FE5vU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
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.20251002072000-d3eb41798438 h1:I0bdwHZ+2DY45b39xPoTD2u+Z8zhvBuu9aZfjMZeiZM=
@ -30,17 +29,13 @@ github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q=
github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5UaBEQLjcvn7aAU01nqU/NUyOBEU+ew=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
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/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
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/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
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=
@ -56,8 +51,8 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/ebitengine/oto/v3 v3.4.0 h1:br0PgASsEWaoWn38b2Goe7m1GKFYfNgnsjSd5Gg+/bQ=
github.com/ebitengine/oto/v3 v3.4.0/go.mod h1:IOleLVD0m+CMak3mRVwsYY8vTctQgOM0iiL6S7Ar7eI=
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.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=
@ -72,8 +67,8 @@ github.com/fumiama/deepinfra v0.0.0-20251221163610-e98ee3ba437a h1:a0+2vaXajfxsN
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-20251221163750-c11c679e4636 h1:PzjoPyYQnrdhsiPj366q6QwyN2amw/msIciRAtt+P7s=
github.com/fumiama/go-onebot-agent v0.0.0-20251221163750-c11c679e4636/go.mod h1:rTrS23rvTYuZcSngENJTvcBFTz1nGsImSv+bW7yfhqs=
github.com/fumiama/go-onebot-agent v0.0.0-20260128132028-05e6b4809f0a h1:8GYo5nctK2si5WDNX0WmZTxY7TWXRjAOBu5pjK7GDW0=
github.com/fumiama/go-onebot-agent v0.0.0-20260128132028-05e6b4809f0a/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=
@ -101,9 +96,8 @@ github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6/go.mod h1:lEaZsT
github.com/gabriel-vasile/mimetype v1.0.4/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
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-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@ -112,9 +106,9 @@ github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZ
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
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/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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=
@ -123,6 +117,8 @@ 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=
github.com/gopxl/beep/v2 v2.1.1/go.mod h1:ZAm9TGQ9lvpoiFLd4zf5B1IuyxZhgRACMId1XJbaW0E=
github.com/guohuiyuan/music-lib v1.0.2-0.20260121020416-53f6cb24629d h1:6Cw52c4JaYvq55yAa9ZgUQeBL6b3ZWErQqkbeMZiAYw=
github.com/guohuiyuan/music-lib v1.0.2-0.20260121020416-53f6cb24629d/go.mod h1:D/6kQDwhQFDNZEMjN8y760DQSVYpOGlQXrYzhKz0rCQ=
github.com/jfreymuth/oggvorbis v1.0.5 h1:u+Ck+R0eLSRhgq8WTmffYnrVtSztJcYrl588DM4e3kQ=
github.com/jfreymuth/oggvorbis v1.0.5/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
@ -139,7 +135,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
@ -150,16 +145,16 @@ github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d h1:hTRDIpJ1FjS9ULJuEzu69n
github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d/go.mod h1:7xD3p0XnHvJFQ3t/stEJd877CSIMkH/fACVWen5pYnc=
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5 h1:wnbHIeP1UX8ClYEWKGnw66PfYvReCHu9G5lXSte3Sqc=
github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5/go.mod h1:7KaV9YIR92M1FpbczAcfYQ3UZ5ayT27pNtunDmXvLBo=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 h1:PwQumkgq4/acIiZhtifTV5OUqqiP82UAl0h87xj/l9k=
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
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 h1:RGIX+D6iQRIunGHrKqnA2+700XMCnNv0bAOOv5MUhx8=
github.com/mmcdole/goxpp v1.1.1/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8=
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=
@ -187,18 +182,17 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
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/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY=
github.com/shirou/gopsutil/v4 v4.25.11/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/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.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
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/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
@ -213,19 +207,16 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
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.20251002074418-56567b7fc282 h1:YctW/t88sQ0H8cJ69PWULU6xWfh8kNsX/XgCpW2OPHw=
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20251002074418-56567b7fc282/go.mod h1:trueIIVRywKJa3ov4QphzVvzYzgCNrlXdf9JvPJOFW8=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260117102541-393033a35adb h1:pwmyrnnMzb0WygAwUgXoZckzJkusK4WSaXxXqsIJYmU=
github.com/wdvxdr1123/ZeroBot v1.8.3-0.20260117102541-393033a35adb/go.mod h1:kCLja2sXXgbBTsEOyBNCuT4z9tI+URQ2y0q/GGXprzU=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
@ -237,7 +228,6 @@ golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
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.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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=
@ -250,8 +240,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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=
@ -262,7 +252,6 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
@ -275,15 +264,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
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-20200930185726-fdedc70b468f/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-20210119212857-b64e53b001e4/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=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
@ -304,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=
@ -316,7 +303,6 @@ 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.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
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=
@ -324,14 +310,12 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=

View File

@ -26,14 +26,14 @@ schema = 3
version = "v0.0.0-20250224045156-012b1463287d"
hash = "sha256-C5xBt0roPgahradCOTgkhL+j5bvoSXmGwdqcu0aSczc="
[mod."github.com/FloatTech/zbpctrl"]
version = "v1.7.1-0.20251222053912-c0e551828149"
version = "v1.7.1"
hash = "sha256-wkeiaUTpPVbpH7fcXeoLtG+aGIMJbvoc/9sbi2IXK0I="
[mod."github.com/FloatTech/zbputils"]
version = "v1.7.2-0.20251221141527-35f149bccf9a"
hash = "sha256-rYeX7AqJ+/YQFvCjRjruSItwDpz2+GmeeDxBK19AEoo="
version = "v1.7.2-0.20260131170726-494cb1776a47"
hash = "sha256-KLhPEHoJiq2qVkK+7OVy7Wc2wpCFEJOqa/0zZGQ8TnM="
[mod."github.com/PuerkitoBio/goquery"]
version = "v1.10.3"
hash = "sha256-Mth7nYm/MtcOhPMbHj7gXF+Mot7eDUBVN570RitGR/c="
version = "v1.8.0"
hash = "sha256-I3QaPWATvBOL/F26fIiYWKS13yBUYo+9o3tcsGIu8tY="
[mod."github.com/RomiChan/syncx"]
version = "v0.0.0-20240418144900-b7402ffdebc7"
hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4="
@ -47,14 +47,14 @@ schema = 3
version = "v0.0.0-20170805034717-80a9c64b256d"
hash = "sha256-N19KTxh70IUBqnchFuWkrJD8uuFOIVqv1iSuN3YFIT0="
[mod."github.com/ajstarks/svgo"]
version = "v0.0.0-20211024235047-1546f124cd8b"
hash = "sha256-sPwt5sImKFk949TzUeYEF2UiJDqHxXFJKRL2Y7JWJ6Y="
version = "v0.0.0-20200320125537-f189e35d30ca"
hash = "sha256-ALeRuEJN9jHjGb4wNKJcxC59vVx8Tj7hHikEGkaZZ0s="
[mod."github.com/andybalholm/cascadia"]
version = "v1.3.3"
hash = "sha256-jv7ZshpSd7FZzKKN6hqlUgiR8C3y85zNIS/hq7g76Ho="
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.5"
hash = "sha256-AVM0rR81hgVAI0QVzlz4WijFUjByf6Zew3ZwuikKw2Q="
@ -71,8 +71,8 @@ schema = 3
version = "v1.0.1"
hash = "sha256-yuvxYYngpfVkUg9yAmG99IUVmADTQA0tMbBXe0Fq0Mc="
[mod."github.com/ebitengine/oto/v3"]
version = "v3.4.0"
hash = "sha256-8JU4iu+2pUKWVWMpEe8EAZ8FVo3MZdILu82vVVmDSVY="
version = "v3.3.2"
hash = "sha256-TPu3qvJscLZbjwIqC3jj0T1md0mX3lQxcC8GAk7kB1w="
[mod."github.com/ebitengine/purego"]
version = "v0.9.1"
hash = "sha256-iVfU8vaJ7IPa92dUeHeuW+yKvUbe59F/eV7GlDRIAcE="
@ -92,8 +92,8 @@ schema = 3
version = "v1.7.1"
hash = "sha256-Fd1QaeYx+3q4C3XQXlPFnDmKPsoZH6837fN/7rn8i9s="
[mod."github.com/fumiama/go-onebot-agent"]
version = "v0.0.0-20251221163750-c11c679e4636"
hash = "sha256-U0aZeAo1jNo2u75HRItGhA8xH2a+2FWPf9cGXcAH0fU="
version = "v0.0.0-20260128132028-05e6b4809f0a"
hash = "sha256-ratY7o52v0KuxgZC4wqHNXdgGXzliEecs8egE3SBLeo="
[mod."github.com/fumiama/go-registry"]
version = "v0.2.7"
hash = "sha256-Rjl+z0Hlp2LMi8+pnFe5HrxctyHMi7UPiK33g/OgLdA="
@ -128,20 +128,23 @@ schema = 3
version = "v1.4.12"
hash = "sha256-vY2g58yUrkT//8fttRKhS9rbg89YSae/BzOARS5uH30="
[mod."github.com/go-ole/go-ole"]
version = "v1.3.0"
hash = "sha256-tF8t3VcV71jQ4jbPL91BwR59AKDpUAFV1waIKzkXJu8="
version = "v1.2.6"
hash = "sha256-+oxitLeJxYF19Z6g+6CgmCHJ1Y5D8raMi2Cb3M6nXCs="
[mod."github.com/golang/freetype"]
version = "v0.0.0-20170609003504-e2365dfdc4a0"
hash = "sha256-AHAFBd20/tqxohkWyQkui2bUef9i1HWYgk9LOIFErvA="
[mod."github.com/golang/groupcache"]
version = "v0.0.0-20241129210726-2c02b8208cf8"
hash = "sha256-AdLZ3dJLe/yduoNvZiXugZxNfmwJjNQyQGsIdzYzH74="
version = "v0.0.0-20210331224755-41bb18bfe9da"
hash = "sha256-7Gs7CS9gEYZkbu5P4hqPGBpeGZWC64VDwraSKFF+VR0="
[mod."github.com/google/uuid"]
version = "v1.6.0"
hash = "sha256-VWl9sqUzdOuhW0KzQlv0gwwUQClYkmZwSydHG2sALYw="
[mod."github.com/gopxl/beep/v2"]
version = "v2.1.1"
hash = "sha256-JLCUJCG+VvNlVF296JWIOUvvUFHlqEAJvZfw853qwwU="
[mod."github.com/guohuiyuan/music-lib"]
version = "v1.0.2-0.20260121020416-53f6cb24629d"
hash = "sha256-juVJ/nh6zA5Gu5+dRzIx8tElXLscRQYwY9vLvVKh078="
[mod."github.com/jfreymuth/oggvorbis"]
version = "v1.0.5"
hash = "sha256-jphTCaPr34ZT9Id4ZZ6zU9Vnxzy6cTjCwjpQ819eGV0="
@ -176,8 +179,8 @@ schema = 3
version = "v0.0.0-20231231122217-0372e1059ca5"
hash = "sha256-Dr1xDbO+eR4Y/EpPgQ/S6g6C5etRFKWr8de77skcJR8="
[mod."github.com/lufia/plan9stats"]
version = "v0.0.0-20251013123823-9fd1530e3ec3"
hash = "sha256-N760qPHHaMcxICyA3Ap/b/3exi40AStu7458VPvC9GI="
version = "v0.0.0-20211012122336-39d0f177ccd0"
hash = "sha256-thb+rkDx5IeWMgw5/5jgu5gZ+6RjJAUXeMgSkJHhRlA="
[mod."github.com/mattn/go-isatty"]
version = "v0.0.20"
hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
@ -185,8 +188,8 @@ schema = 3
version = "v1.3.0"
hash = "sha256-GHpqGZvNg+3RSIkVKXrWg6/e8dJD8Y5v2Sx6MzmRlQ0="
[mod."github.com/mmcdole/goxpp"]
version = "v1.1.1"
hash = "sha256-LtZDUtflL06HnDuQiCS6cpoF3VPk+gmABkYXBvdQOz0="
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="
@ -221,14 +224,14 @@ schema = 3
version = "v0.0.0-20230129092748-24d4a6f8daec"
hash = "sha256-vYmpyCE37eBYP/navhaLV4oX4/nu0Z/StAocLIFqrmM="
[mod."github.com/shirou/gopsutil/v4"]
version = "v4.25.11"
hash = "sha256-2Hzp/8m/otBxC32lvVqFz94FivWP3ApNlvJ/iX85v4Y="
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="
version = "v1.9.4"
hash = "sha256-ltRvmtM3XTCAFwY0IesfRqYIivyXPPuvkFjL4ARh1wg="
[mod."github.com/tetratelabs/wazero"]
version = "v1.9.0"
hash = "sha256-b8D0cDMuDgyjvJ6LFY8REdcL95BIjM27SeOEQWfB0+0="
version = "v1.5.0"
hash = "sha256-fGdJM4LJrZA9jxHuYVo4EUQ3I1k0IVG3QQCBCgZkeZI="
[mod."github.com/tidwall/gjson"]
version = "v1.18.0"
hash = "sha256-CO6hqDu8Y58Po6A01e5iTpwiUBQ5khUZsw7czaJHw0I="
@ -248,14 +251,14 @@ schema = 3
version = "v2.1.2"
hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM="
[mod."github.com/wdvxdr1123/ZeroBot"]
version = "v1.8.2-0.20251002074418-56567b7fc282"
hash = "sha256-KaoqopWcXqiRhGYNaA3UqYtXf27yMuBEj/bvqOWxaC4="
version = "v1.8.3-0.20260117102541-393033a35adb"
hash = "sha256-Yz2OTU05kDZOHX8J04jX5Jg5ya9rwqsH0TySSBhMOp0="
[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.34.0"
hash = "sha256-7V1bDhd++dCw11DCXOAUb5f1Hj51EfS0DZ03pq0V/p0="

View File

@ -3,13 +3,13 @@
package banner
// Version ...
var Version = "v1.10.4"
var Version = "v1.10.18"
// Copyright ...
var Copyright = "© 2020 - 2025 FloatTech"
var Copyright = "© 2020 - 2026 FloatTech"
// Banner ...
var Banner = "* OneBot + ZeroBot + Golang\n" +
"* Version " + Version + " - 2025-12-23 00:25:15 +0800 CST\n" +
"* Version " + Version + " - 2026-02-01 01:08:19 +0800 CST\n" +
"* Copyright " + Copyright + ". All Rights Reserved.\n" +
"* Project: https://github.com/FloatTech/ZeroBot-Plugin"

View File

@ -14,8 +14,7 @@ import (
"strings"
"time"
_ "github.com/FloatTech/ZeroBot-Plugin/abineundo" // 设置插件优先级
_ "github.com/FloatTech/ZeroBot-Plugin/console" // 更改控制台属性
_ "github.com/FloatTech/ZeroBot-Plugin/abineundo" // 设置插件优先级&更改控制台属性
"github.com/FloatTech/ZeroBot-Plugin/kanban" // 打印 banner
// ---------以下插件均可通过前面加 // 注释,注释后停用并不加载插件--------- //
@ -112,6 +111,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吟唱提示
@ -154,6 +154,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
@ -179,6 +180,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,15 @@
// Package aichat OpenAI聊天和群聊总结
// Package aichat 大模型聊天和Agent
package aichat
import (
"encoding/json"
"math/rand"
"strconv"
"strings"
"time"
"github.com/RomiChan/syncx"
"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 +20,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 +27,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 +43,48 @@ 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))
mp := ctx.State[control.StateKeySyncxState].(*syncx.Map[string, any])
if _, ok := mp.Load(chat.StateKeyAgentHooked); !ok && !stor.NoAgent() {
logrus.Infoln("[aichat] skip agent for ctx has not been hooked by agent")
return false
}
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 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()
mp := ctx.State[control.StateKeySyncxState].(*syncx.Map[string, any])
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 != "" && chat.AC.Key != "" {
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 +96,79 @@ 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
for i := 0; i < 8; i++ { // 最大运行 8 轮因为问答上下文只有 16
reqs := chat.CallAgent(ag, zero.SuperUserPermission(ctx), x, mod, gid, role)
reqs := chat.CallAgent(ag, zero.SuperUserPermission(ctx), i+1, x, mod, gid, role)
if len(reqs) == 0 {
logrus.Debugln("[aichat] agent call got empty response")
break
}
hasresp = true
mp.Store(chat.StateKeyAgentTriggered, struct{}{})
for _, req := range reqs {
resp := ctx.CallAction(req.Action, req.Params)
logrus.Infoln("[aichat] agent get resp:", reqs)
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
}*/
logrus.Debugln("[chat] agent triggered", gid, "add requ:", &req)
ag.AddRequest(gid, &req)
rsp := ctx.CallAction(req.Action, req.Params)
logrus.Debugln("[chat] agent triggered", gid, "add resp:", &rsp)
ag.AddResponse(gid, &goba.APIResponse{
Status: resp.Status,
Data: json.RawMessage(resp.Data.Raw),
Message: resp.Message,
Wording: resp.Wording,
RetCode: resp.RetCode,
Status: rsp.Status,
Data: json.RawMessage(rsp.Data.Raw),
Message: rsp.Message,
Wording: rsp.Wording,
RetCode: rsp.RetCode,
})
}
}
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
@ -179,309 +188,24 @@ func init() {
if t == "" {
continue
}
logrus.Infoln("[aichat] 回复内容:", t)
logrus.Debugln("[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.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.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.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
}
// 构造总结请求提示 (使用通用版省流提示词)
// 使用反引号定义多行字符串,更清晰
promptTemplate := `请对以下群聊对话进行极简总结
要求
1. 剔除客套与废话直击主题
2. 使用 Markdown 列表格式
3. 按以下结构输出
- 🎯 核心议题(一句话概括)
- 💡 关键观点/结论(提取3-5个重点)
- 下一步/待办(如果有明确谁做什么)
群聊对话内容如下
`
summaryPrompt := promptTemplate + 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 := int8((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)
}

View File

@ -1,149 +0,0 @@
package aichat
import (
"testing"
"github.com/FloatTech/zbputils/ctxext"
)
func TestStorage_rate(t *testing.T) {
s := storage(ctxext.Storage(0))
// 测试默认值
if rate := s.rate(); rate != 0 {
t.Errorf("default rate() = %v, want 0", rate)
}
// 设置值并测试
s = storage((ctxext.Storage)(s).Set(int64(100), bitmaprate))
if rate := s.rate(); rate != 100 {
t.Errorf("rate() after set = %v, want 100", rate)
}
}
func TestStorage_temp(t *testing.T) {
s := storage(ctxext.Storage(0))
tests := []struct {
name string
setValue int64
expected float32
}{
{"default temp (0)", 0, 0.70}, // 默认值 70/100
{"valid temp 50", 50, 0.50}, // 50/100 = 0.50
{"valid temp 80", 80, 0.80}, // 80/100 = 0.80
{"max temp 100", 100, 1.00}, // 100/100 = 1.00
{"over max temp", 127, 1.00}, // 限制为 100/100 = 1.00
{"negative temp", -10, 0.70}, // 默认值 70/100
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s = storage((ctxext.Storage)(s).Set(tt.setValue, bitmaptemp))
result := s.temp()
if result != tt.expected {
t.Errorf("temp() = %v, want %v", result, tt.expected)
}
})
}
}
func TestStorage_noagent(t *testing.T) {
s := storage(ctxext.Storage(0))
// 测试默认值
if noagent := s.noagent(); noagent != false {
t.Errorf("default noagent() = %v, want false", noagent)
}
// 设置为 true 并测试
s = storage((ctxext.Storage)(s).Set(1, bitmapnagt))
if noagent := s.noagent(); noagent != true {
t.Errorf("noagent() after set true = %v, want true", noagent)
}
}
func TestStorage_norecord(t *testing.T) {
s := storage(ctxext.Storage(0))
// 测试默认值
if norecord := s.norecord(); norecord != false {
t.Errorf("default norecord() = %v, want false", norecord)
}
// 设置为 true 并测试
s = storage((ctxext.Storage)(s).Set(1, bitmapnrec))
if norecord := s.norecord(); norecord != true {
t.Errorf("norecord() after set true = %v, want true", norecord)
}
}
func TestStorage_noreplyat(t *testing.T) {
s := storage(ctxext.Storage(0))
// 测试默认值
if noreplyat := s.noreplyat(); noreplyat != false {
t.Errorf("default noreplyat() = %v, want false", noreplyat)
}
// 设置为 true 并测试
s = storage((ctxext.Storage)(s).Set(1, bitmapnrat))
if noreplyat := s.noreplyat(); noreplyat != true {
t.Errorf("noreplyat() after set true = %v, want true", noreplyat)
}
}
func TestStorage_Integration(t *testing.T) {
s := storage(ctxext.Storage(0))
// 设置各种值
s = storage((ctxext.Storage)(s).Set(int64(75), bitmaprate))
s = storage((ctxext.Storage)(s).Set(int64(85), bitmaptemp))
s = storage((ctxext.Storage)(s).Set(1, bitmapnagt))
s = storage((ctxext.Storage)(s).Set(0, bitmapnrec))
s = storage((ctxext.Storage)(s).Set(1, bitmapnrat))
// 验证所有方法
if rate := s.rate(); rate != 75 {
t.Errorf("rate() = %v, want 75", rate)
}
if temp := s.temp(); temp != 0.85 {
t.Errorf("temp() = %v, want 0.85", temp)
}
if noagent := s.noagent(); !noagent {
t.Errorf("noagent() = %v, want true", noagent)
}
if norecord := s.norecord(); norecord {
t.Errorf("norecord() = %v, want false", norecord)
}
if noreplyat := s.noreplyat(); !noreplyat {
t.Errorf("noreplyat() = %v, want true", noreplyat)
}
}
func BenchmarkStorage_rate(b *testing.B) {
s := storage(ctxext.Storage(0))
s = storage((ctxext.Storage)(s).Set(int64(100), bitmaprate))
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.rate()
}
}
func BenchmarkStorage_temp(b *testing.B) {
s := storage(ctxext.Storage(0))
s = storage((ctxext.Storage)(s).Set(int64(80), bitmaptemp))
b.ResetTimer()
for i := 0; i < b.N; i++ {
s.temp()
}
}

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

@ -0,0 +1,183 @@
// 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性格xxx" +
"- 查看AI聊天Agent性格" +
"- 设置AI聊天Agent性别xxx" +
"- 查看AI聊天Agent性别" +
"- 重置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 {
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), func(_ *zero.Ctx) {
chat.AgentCharConfig.Chars = chat.AC.AgentChar
})
en.OnPrefix("设置AI聊天Agent性别", chat.EnsureConfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(chat.NewExtraSetStr(&chat.AC.AgentSex), func(_ *zero.Ctx) {
chat.AgentCharConfig.Sex = chat.AC.AgentSex
})
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.ResetAgentCharConfig()
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$", 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模式$", 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语音输出$", 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

@ -3,7 +3,6 @@ package crypter
import (
"github.com/FloatTech/AnimeAPI/airecord"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
@ -12,7 +11,6 @@ import (
func houEncryptHandler(ctx *zero.Ctx) {
text := ctx.State["regex_matched"].([]string)[1]
result := encodeHou(text)
logrus.Infoln("[crypter] 回复内容:", result)
recCfg := airecord.GetConfig()
record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, result)
if record != "" {

View File

@ -36,7 +36,7 @@ var (
lotsList = func() map[string]info {
lotsList, err := getList()
if err != nil {
logrus.Infoln("[drawlots]加载失败:", err)
logrus.Infoln("[drawlots]加载失败:", err, "(如果从未使用过该插件, 这是正常现象)")
} else {
logrus.Infoln("[drawlots]加载", len(lotsList), "个抽签")
}

View File

@ -11,8 +11,6 @@ import (
"sync"
"time"
log "github.com/sirupsen/logrus"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/zbputils/ctxext"
"github.com/pkg/errors"
@ -30,7 +28,7 @@ func init() {
var err1 error
t2s, err1 = gocc.New("t2s")
if err1 != nil {
log.Infof("[guessmusic]:%s", err1)
panic(err1)
}
engine.OnRegex(`^(个人|团队)猜歌(-(.*))?$`, zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByGroup).

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
}

View File

@ -3,8 +3,9 @@ package minecraftobserver
import (
"errors"
"fmt"
"github.com/jinzhu/gorm"
"testing"
"github.com/jinzhu/gorm"
)
func cleanTestData(t *testing.T) {

View File

@ -1,35 +1,36 @@
// Package music QQ音乐、网易云、酷狗、酷我、咪咕 点歌
// Package music 整合多平台音乐点播能力
package music
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/tidwall/gjson"
"github.com/guohuiyuan/music-lib/kugou"
"github.com/guohuiyuan/music-lib/kuwo"
"github.com/guohuiyuan/music-lib/migu"
"github.com/guohuiyuan/music-lib/netease"
"github.com/guohuiyuan/music-lib/qq"
"github.com/pkg/errors"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
var (
longZhuURL = "https://www.hhlqilongzhu.cn/api/joox/juhe_music.php?msg=%v"
)
var platformMap = map[string]func(string) (message.Segment, error){
"咪咕": getMiguMusic,
"酷我": getKuwoMusic,
"酷狗": getKugouMusic,
"网易": getNeteaseMusic,
"qq": getQQMusic,
"": getKuwoMusic, // 默认点歌指向酷我
}
func init() {
control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "点歌",
Help: "- 点歌[xxx]\n" +
Help: "- 点歌[xxx] (默认酷我)\n" +
"- 网易点歌[xxx]\n" +
"- 酷我点歌[xxx]\n" +
"- 酷狗点歌[xxx]\n" +
@ -37,203 +38,146 @@ func init() {
"- qq点歌[xxx]\n",
}).OnRegex(`^(.{0,2})点歌\s?(.{1,25})$`).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
// switch 平台
switch ctx.State["regex_matched"].([]string)[1] {
case "咪咕":
ctx.SendChain(migu(ctx.State["regex_matched"].([]string)[2]))
case "酷我":
ctx.SendChain(kuwo(ctx.State["regex_matched"].([]string)[2]))
case "酷狗":
ctx.SendChain(kugou(ctx.State["regex_matched"].([]string)[2]))
case "网易":
ctx.SendChain(cloud163(ctx.State["regex_matched"].([]string)[2]))
case "qq":
ctx.SendChain(qqmusic(ctx.State["regex_matched"].([]string)[2]))
default: // 默认聚合点歌
ctx.SendChain(longzhu(ctx.State["regex_matched"].([]string)[2]))
matches := ctx.State["regex_matched"].([]string)
platformPrefix := matches[1]
keyword := matches[2]
processFunc, ok := platformMap[platformPrefix]
if !ok {
ctx.SendChain(message.Text("不支持的点播平台:", platformPrefix))
return
}
seg, err := processFunc(keyword)
if err != nil {
ctx.SendChain(message.Text("点歌失败:", err))
return
}
ctx.SendChain(seg)
})
}
// longzhu 聚合平台
func longzhu(keyword string) message.Segment {
data, _ := web.GetData(fmt.Sprintf(longZhuURL, url.QueryEscape(keyword)))
// 假设 data 是包含整个 JSON 数组的字节切片
results := gjson.ParseBytes(data).Array()
for _, result := range results {
if strings.Contains(strings.ToLower(result.Get("title").String()), strings.ToLower(keyword)) {
if musicURL := result.Get("full_track").String(); musicURL != "" {
return message.Record(musicURL)
}
}
}
results = gjson.GetBytes(data, "#.full_track").Array()
if len(results) > 0 {
if musicURL := results[0].String(); musicURL != "" {
return message.Record(musicURL)
}
}
return message.Text("点歌失败, 找不到 ", keyword, " 的相关结果")
}
// migu 返回咪咕音乐卡片
func migu(keyword string) message.Segment {
headers := http.Header{
"Cookie": []string{"audioplayer_exist=1; audioplayer_open=0; migu_cn_cookie_id=3ad476db-f021-4bda-ab91-c485ac3d56a0; Hm_lvt_ec5a5474d9d871cb3d82b846d861979d=1671119573; Hm_lpvt_ec5a5474d9d871cb3d82b846d861979d=1671119573; WT_FPC=id=279ef92eaf314cbb8d01671116477485:lv=1671119583092:ss=1671116477485"},
"csrf": []string{"LWKACV45JSQ"},
"User-Agent": []string{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"},
"Referer": []string{"http://m.music.migu.cn"},
"proxy": []string{"false"},
}
// 搜索音乐信息 第一首歌
search, _ := url.Parse("http://m.music.migu.cn/migu/remoting/scr_search_tag")
search.RawQuery = url.Values{
"keyword": []string{keyword},
"type": []string{"2"},
"pgc": []string{"1"},
"rows": []string{"10"},
}.Encode()
info := gjson.ParseBytes(netGet(search.String(), headers)).Get("musics.0")
// 返回音乐卡片
return message.CustomMusic(
fmt.Sprintf("https://music.migu.cn/v3/music/song/%s", info.Get("copyrightId").String()),
info.Get("mp3").String(),
info.Get("songName").String(),
).Add("content", info.Get("artist").Str).Add("image", info.Get("cover").Str).Add("subtype", "migu")
}
// kuwo 返回酷我音乐卡片
func kuwo(keyword string) message.Segment {
headers := http.Header{
"Cookie": []string{"Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1610284708,1610699237; _ga=GA1.2.1289529848.1591618534; kw_token=LWKACV45JSQ; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1610699468; _gid=GA1.2.1868980507.1610699238; _gat=1"},
"csrf": []string{"LWKACV45JSQ"},
"User-Agent": []string{"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0"},
"Referer": []string{"https://www.kuwo.cn/search/list?key="},
}
// 搜索音乐信息 第一首歌
search, _ := url.Parse("https://www.kuwo.cn/api/www/search/searchMusicBykeyWord")
search.RawQuery = url.Values{
"key": []string{keyword},
"pn": []string{"1"},
"rn": []string{"1"},
"httpsStatus": []string{"1"},
}.Encode()
info := gjson.ParseBytes(netGet(search.String(), headers)).Get("data.list.0")
// 获得音乐直链
music, _ := url.Parse("http://www.kuwo.cn/api/v1/www/music/playUrl")
music.RawQuery = url.Values{
"mid": []string{fmt.Sprintf("%d", info.Get("rid").Int())},
"type": []string{"convert_url3"},
"br": []string{"320kmp3"},
"httpsStatus": []string{"1"},
}.Encode()
audio := gjson.ParseBytes(netGet(music.String(), headers))
// 返回音乐卡片
return message.CustomMusic(
fmt.Sprintf("https://www.kuwo.cn/play_detail/%d", info.Get("rid").Int()),
audio.Get("data.url").Str,
info.Get("name").Str,
).Add("content", info.Get("artist").Str).Add("image", info.Get("pic").Str).Add("subtype", "kuwo")
}
// kugou 返回酷狗音乐卡片
func kugou(keyword string) message.Segment {
stamp := time.Now().UnixNano() / 1e6
hash := md5str(
fmt.Sprintf(
"NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtbitrate=0callback=callback123clienttime=%dclientver=2000dfid=-inputtype=0iscorrection=1isfuzzy=0keyword=%smid=%dpage=1pagesize=30platform=WebFilterprivilege_filter=0srcappid=2919tag=emuserid=-1uuid=%dNVPh5oo715z5DIWAeQlhMDsWXXQV4hwt",
stamp, keyword, stamp, stamp,
),
)
// 搜索音乐信息 第一首歌
h1 := http.Header{
"User-Agent": []string{"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0"},
}
search, _ := url.Parse("https://complexsearch.kugou.com/v2/search/song")
search.RawQuery = url.Values{
"callback": []string{"callback123"},
"keyword": []string{keyword},
"page": []string{"1"},
"pagesize": []string{"30"},
"bitrate": []string{"0"},
"isfuzzy": []string{"0"},
"tag": []string{"em"},
"inputtype": []string{"0"},
"platform": []string{"WebFilter"},
"userid": []string{"-1"},
"clientver": []string{"2000"},
"iscorrection": []string{"1"},
"privilege_filter": []string{"0"},
"srcappid": []string{"2919"},
"clienttime": []string{fmt.Sprintf("%d", stamp)},
"mid": []string{fmt.Sprintf("%d", stamp)},
"uuid": []string{fmt.Sprintf("%d", stamp)},
"dfid": []string{"-"},
"signature": []string{hash},
}.Encode()
res := netGet(search.String(), h1)
info := gjson.ParseBytes(res[12 : len(res)-2]).Get("data.lists.0")
// 获得音乐直链
h2 := http.Header{
"Cookie": []string{"kg_mid=d8e70a262c93d47599c6196c612d6f4f; Hm_lvt_aedee6983d4cfc62f509129360d6bb3d=1610278505,1611631363,1611722252; kg_dfid=33ZWee1kircl0jcJ1h0WF1fX; Hm_lpvt_aedee6983d4cfc62f509129360d6bb3d=1611727348; kg_dfid_collect=d41d8cd98f00b204e9800998ecf8427e"},
"Host": []string{"wwwapi.kugou.com"},
"TE": []string{"Trailers"},
"User-Agent": []string{"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0"},
}
music := "https://wwwapi.kugou.com/yy/index.php?r=play%2Fgetdata&hash=" + info.Get("FileHash").Str + "&album_id=" + info.Get("AlbumID").Str
audio := gjson.ParseBytes(netGet(music, h2)).Get("data")
// 返回音乐卡片
return message.CustomMusic(
"https://www.kugou.com/song/#hash="+audio.Get("hash").Str+"&album_id="+audio.Get("album_id").Str,
strings.ReplaceAll(audio.Get("play_backup_url").Str, "\\/", "/"),
audio.Get("audio_name").Str,
).Add("content", audio.Get("author_name").Str).Add("image", audio.Get("img").Str).Add("subtype", "kugou")
}
// cloud163 返回网易云音乐卡片
func cloud163(keyword string) (msg message.Segment) {
requestURL := "http://music.163.com/api/search/get/web?type=1&limit=1&s=" + url.QueryEscape(keyword)
data, err := web.GetData(requestURL)
func getMiguMusic(keyword string) (message.Segment, error) {
songs, err := migu.Search(keyword)
if err != nil {
msg = message.Text("ERROR: ", err)
return
return message.Segment{}, errors.Wrap(err, "咪咕音乐搜索失败")
}
msg = message.Music("163", gjson.ParseBytes(data).Get("result.songs.0.id").Int())
return
}
if len(songs) == 0 {
return message.Segment{}, errors.New("咪咕音乐未找到相关歌曲:" + keyword)
}
song := songs[0]
// qqmusic 返回QQ音乐卡片
func qqmusic(keyword string) (msg message.Segment) {
requestURL := "https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg?platform=yqq.json&key=" + url.QueryEscape(keyword)
data, err := web.RequestDataWith(web.NewDefaultClient(), requestURL, "GET", "", web.RandUA(), nil)
playURL, err := migu.GetDownloadURL(&song)
if err != nil {
msg = message.Text("ERROR: ", err)
return
return message.Segment{}, errors.Wrap(err, "获取咪咕播放链接失败")
}
msg = message.Music("qq", gjson.ParseBytes(data).Get("data.song.itemlist.0.id").Int())
return
if playURL == "" {
return message.Segment{}, errors.New("获取咪咕播放链接失败:链接为空")
}
return message.CustomMusic(
fmt.Sprintf("https://music.migu.cn/v3/music/song/%s", song.ID),
playURL,
song.Name,
).Add("content", song.Artist).Add("image", song.Cover).Add("subtype", "migu"), nil
}
// md5str 返回字符串 MD5
func md5str(s string) string {
h := md5.New()
h.Write([]byte(s))
result := strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
return result
}
// netGet 返回请求数据
func netGet(url string, header http.Header) []byte {
client := &http.Client{}
request, _ := http.NewRequest("GET", url, nil)
request.Header = header
res, err := client.Do(request)
func getKuwoMusic(keyword string) (message.Segment, error) {
songs, err := kuwo.Search(keyword)
if err != nil {
return nil
return message.Segment{}, errors.Wrap(err, "酷我音乐搜索失败")
}
defer res.Body.Close()
result, _ := io.ReadAll(res.Body)
return result
if len(songs) == 0 {
return message.Segment{}, errors.New("酷我音乐未找到相关歌曲:" + keyword)
}
song := songs[0]
playURL, err := kuwo.GetDownloadURL(&song)
if err != nil {
return message.Segment{}, errors.Wrap(err, "获取酷我播放链接失败")
}
if playURL == "" {
return message.Segment{}, errors.New("获取酷我播放链接失败:链接为空")
}
return message.CustomMusic(
fmt.Sprintf("https://www.kuwo.cn/play_detail/%s", song.ID),
playURL,
song.Name,
).Add("content", song.Artist).Add("image", song.Cover).Add("subtype", "kuwo"), nil
}
func getKugouMusic(keyword string) (message.Segment, error) {
songs, err := kugou.Search(keyword)
if err != nil {
return message.Segment{}, errors.Wrap(err, "酷狗音乐搜索失败")
}
if len(songs) == 0 {
return message.Segment{}, errors.New("酷狗音乐未找到相关歌曲:" + keyword)
}
song := songs[0]
playURL, err := kugou.GetDownloadURL(&song)
if err != nil {
return message.Segment{}, errors.Wrap(err, "获取酷狗播放链接失败")
}
if playURL == "" {
return message.Segment{}, errors.New("获取酷狗播放链接失败:链接为空")
}
return message.CustomMusic(
"https://www.kugou.com/",
playURL,
song.Name,
).Add("content", song.Artist).Add("image", song.Cover).Add("subtype", "kugou"), nil
}
func getNeteaseMusic(keyword string) (message.Segment, error) {
songs, err := netease.Search(keyword)
if err != nil {
return message.Segment{}, errors.Wrap(err, "网易云音乐搜索失败")
}
if len(songs) == 0 {
return message.Segment{}, errors.New("网易云音乐未找到相关歌曲:" + keyword)
}
song := songs[0]
playURL, err := netease.GetDownloadURL(&song)
if err != nil {
return message.Segment{}, errors.Wrap(err, "获取网易云播放链接失败")
}
if playURL == "" {
return message.Segment{}, errors.New("获取网易云播放链接失败:链接为空")
}
return message.CustomMusic(
fmt.Sprintf("https://music.163.com/#/song?id=%s", song.ID),
playURL,
song.Name,
).Add("content", song.Artist).Add("image", song.Cover).Add("subtype", "163"), nil
}
func getQQMusic(keyword string) (message.Segment, error) {
songs, err := qq.Search(keyword)
if err != nil {
return message.Segment{}, errors.Wrap(err, "QQ音乐搜索失败")
}
if len(songs) == 0 {
return message.Segment{}, errors.New("QQ音乐未找到相关歌曲" + keyword)
}
song := songs[0]
playURL, err := qq.GetDownloadURL(&song)
if err != nil {
return message.Segment{}, errors.Wrap(err, "获取QQ音乐播放链接失败")
}
if playURL == "" {
return message.Segment{}, errors.New("获取QQ音乐播放链接失败链接为空")
}
return message.CustomMusic(
fmt.Sprintf("https://y.qq.com/n/ryqq/songDetail/%s", song.ID),
playURL,
song.Name,
).Add("content", song.Artist).Add("image", song.Cover).Add("subtype", "qq"), nil
}

View File

@ -28,7 +28,7 @@ func (repo *RssDomain) syncRss(ctx context.Context) (updated map[int64]*RssClien
feed, err = repo.rssHubClient.FetchFeed(channel.RssHubFeedPath)
// 如果获取失败,则跳过
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub syncRss] fetch path(%+v) error: %v", channel.RssHubFeedPath, err)
logrus.WithContext(ctx).Warnf("[rsshub syncRss] fetch path(%+v) error: %v", channel.RssHubFeedPath, err)
continue
}
rv := convertFeedToRssView(0, channel.RssHubFeedPath, feed)
@ -42,27 +42,27 @@ func (repo *RssDomain) syncRss(ctx context.Context) (updated map[int64]*RssClien
var needUpdate bool
needUpdate, err = repo.checkSourceNeedUpdate(ctx, cv.Source)
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub syncRss] checkSourceNeedUpdate error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub syncRss] checkSourceNeedUpdate error: %v", err)
err = nil
continue
}
// 保存
logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %+v, need update(real): %v", cv.Source, needUpdate)
logrus.WithContext(ctx).Debugf("[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)
logrus.WithContext(ctx).Warnf("[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)
logrus.WithContext(ctx).Warnf("[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)
logrus.WithContext(ctx).Debugf("[rsshub syncRss] cv %s, no new content", cv.Source.RssHubFeedPath)
continue
}
updateChannelView.Sort()
@ -80,7 +80,7 @@ func (repo *RssDomain) checkSourceNeedUpdate(ctx context.Context, source *RssSou
return
}
if sourceInDB == nil {
logrus.WithContext(ctx).Errorf("[rsshub syncRss] source not found: %v", source.RssHubFeedPath)
logrus.WithContext(ctx).Warnf("[rsshub syncRss] source not found: %v", source.RssHubFeedPath)
return
}
source.ID = sourceInDB.ID
@ -102,13 +102,13 @@ func (repo *RssDomain) processContentsUpdate(ctx context.Context, cv *RssClientV
var existed bool
existed, err = repo.processContentItemUpdate(ctx, content)
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err)
logrus.WithContext(ctx).Warnf("[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)
logrus.WithContext(ctx).Debugf("[rsshub syncRss] cv %s, add new content: %v", cv.Source.RssHubFeedPath, content.Title)
}
}
return err
@ -127,7 +127,7 @@ func (repo *RssDomain) processContentItemUpdate(ctx context.Context, content *Rs
// 保存
err = repo.storage.UpsertContent(ctx, content)
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub syncRss] upsert content error: %v", err)
return
}
return

View File

@ -36,11 +36,11 @@ func (c *RssHubClient) FetchFeed(path string) (feed *gofeed.Feed, err error) {
}
}
if err != nil {
logrus.Errorf("[rsshub FetchFeed] fetch feed error: %v", err)
logrus.Warnf("[rsshub FetchFeed] fetch feed error: %v", err)
return nil, err
}
if len(data) == 0 {
logrus.Errorf("[rsshub FetchFeed] fetch feed error: data is empty")
logrus.Warnf("[rsshub FetchFeed] fetch feed error: data is empty")
return nil, errors.New("feed data is empty")
}
feed, err = gofeed.NewParser().Parse(bytes.NewBuffer(data))

View File

@ -33,7 +33,7 @@ func newRssDomain(dbPath string) (*RssDomain, error) {
}
orm, err := gorm.Open("sqlite3", dbPath)
if err != nil {
logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err)
logrus.Warnf("[rsshub NewRssDomain] open db error: %v", err)
panic(err)
}
repo := &RssDomain{
@ -42,7 +42,7 @@ func newRssDomain(dbPath string) (*RssDomain, error) {
}
err = repo.storage.initDB()
if err != nil {
logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err)
logrus.Warnf("[rsshub NewRssDomain] open db error: %v", err)
panic(err)
}
return repo, nil
@ -54,15 +54,15 @@ func (repo *RssDomain) Subscribe(ctx context.Context, gid int64, feedPath string
// 验证
feed, err := repo.rssHubClient.FetchFeed(feedPath)
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] add source error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub Subscribe] add source error: %v", err)
return
}
logrus.WithContext(ctx).Infof("[rsshub Subscribe] try get source success: %v", len(feed.Title))
logrus.WithContext(ctx).Debugf("[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)
logrus.WithContext(ctx).Warnf("[rsshub Subscribe] query source by feedPath error: %v", err)
return
}
// 如果已经存在
@ -76,30 +76,30 @@ func (repo *RssDomain) Subscribe(ctx context.Context, gid int64, feedPath string
// 保存
err = repo.storage.UpsertSource(ctx, rv.Source)
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub Subscribe] save source error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub Subscribe] save source error: %v", err)
return
}
logrus.Infof("[rsshub Subscribe] save/update source success %v", rv.Source.ID)
logrus.Debugf("[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)
logrus.WithContext(ctx).Warnf("[rsshub Subscribe] query subscribe error: %v", err)
return
}
logrus.WithContext(ctx).Infof("[rsshub Subscribe] query subscribe success: %v", subscribe)
logrus.WithContext(ctx).Debugf("[rsshub Subscribe] query subscribe success: %v", subscribe)
// 如果已经存在,直接返回
if subscribe != nil {
isSubExisted = true
logrus.WithContext(ctx).Infof("[rsshub Subscribe] subscribe existed: %v", subscribe)
logrus.WithContext(ctx).Debugf("[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)
logrus.WithContext(ctx).Warnf("[rsshub Subscribe] save subscribe error: %v", err)
return
}
logrus.WithContext(ctx).Infof("[rsshub Subscribe] success: %v", len(rv.Contents))
logrus.WithContext(ctx).Debugf("[rsshub Subscribe] success: %v", len(rv.Contents))
return
}
@ -107,31 +107,31 @@ func (repo *RssDomain) Subscribe(ctx context.Context, gid int64, feedPath string
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)
logrus.WithContext(ctx).Warnf("[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)
logrus.WithContext(ctx).Debugf("[rsshub Subscribe] query source by route success: %v", existedSubscribes)
// 如果不存在订阅关系,直接返回
if !ifExisted || existedSubscribes == nil {
logrus.WithContext(ctx).Infof("[rsshub Subscribe] source existed: %v", ifExisted)
logrus.WithContext(ctx).Debugf("[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)
logrus.WithContext(ctx).Warnf("[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)
logrus.WithContext(ctx).Warnf("[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)
logrus.WithContext(ctx).Warnf("[rsshub Subscribe] delete source error: %v", err)
return errors.New("清除频道信息失败")
}
}
@ -142,11 +142,11 @@ func (repo *RssDomain) Unsubscribe(ctx context.Context, gid int64, feedPath stri
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)
logrus.WithContext(ctx).Warnf("[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))
logrus.WithContext(ctx).Debugf("[rsshub GetSubscribedChannelsByGroupID] query subscribe success: %v", len(channels))
for i, cn := range channels {
rv[i] = &RssClientView{
Source: cn,
@ -162,13 +162,13 @@ func (repo *RssDomain) Sync(ctx context.Context) (groupView map[int64][]*RssClie
// 获取所有频道
updatedViews, err := repo.syncRss(ctx)
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub Sync] sync rss feed error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub Sync] sync rss feed error: %v", err)
return
}
logrus.WithContext(ctx).Infof("[rsshub Sync] updated channels: %v", len(updatedViews))
logrus.WithContext(ctx).Debugf("[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)
logrus.WithContext(ctx).Warnf("[rsshub Sync] get subscribes error: %v", err)
return
}
for _, subscribe := range subscribes {

View File

@ -19,7 +19,7 @@ type repoStorage struct {
func (s *repoStorage) initDB() (err error) {
err = s.orm.AutoMigrate(&RssSource{}, &RssContent{}, &RssSubscribe{}).Error
if err != nil {
logrus.Errorf("[rsshub initDB] error: %v", err)
logrus.Warnf("[rsshub initDB] error: %v", err)
return err
}
return nil
@ -28,7 +28,7 @@ func (s *repoStorage) initDB() (err error) {
// GetSubscribesBySource Impl
func (s *repoStorage) GetSubscribesBySource(ctx context.Context, feedPath string) ([]*RssSubscribe, error) {
logrus.WithContext(ctx).Infof("[rsshub GetSubscribesBySource] feedPath: %s", feedPath)
logrus.WithContext(ctx).Debugf("[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
@ -36,7 +36,7 @@ func (s *repoStorage) GetSubscribesBySource(ctx context.Context, feedPath string
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
logrus.WithContext(ctx).Errorf("[rsshub GetSubscribesBySource] error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub GetSubscribesBySource] error: %v", err)
return nil, err
}
return rs, nil
@ -56,7 +56,7 @@ func (s *repoStorage) GetIfExistedSubscribe(ctx context.Context, gid int64, feed
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, false, nil
}
logrus.WithContext(ctx).Errorf("[rsshub GetIfExistedSubscribe] error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub GetIfExistedSubscribe] error: %v", err)
return nil, false, err
}
if rs.ID == 0 {
@ -76,14 +76,14 @@ func (s *repoStorage) UpsertSource(ctx context.Context, source *RssSource) (err
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)
logrus.WithContext(ctx).Warnf("[rsshub] add source error: %v", err)
return
}
}
return
}
source.ID = querySource.ID
logrus.WithContext(ctx).Infof("[rsshub] update source: %+v", source.UpdatedParsed)
logrus.WithContext(ctx).Debugf("[rsshub] update source: %+v", source.UpdatedParsed)
err = s.orm.Model(&source).Where(&RssSource{ID: source.ID}).
Updates(&RssSource{
Title: source.Title,
@ -94,7 +94,7 @@ func (s *repoStorage) UpsertSource(ctx context.Context, source *RssSource) (err
Mtime: time.Now(),
}).Error
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub] update source error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] update source error: %v", err)
return
}
logrus.Println("[rsshub] add source success: ", source.ID)
@ -109,10 +109,10 @@ func (s *repoStorage) GetSources(ctx context.Context) (sources []RssSource, err
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("source not found")
}
logrus.WithContext(ctx).Errorf("[rsshub] get sources error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] get sources error: %v", err)
return
}
logrus.WithContext(ctx).Infof("[rsshub] get sources success: %d", len(sources))
logrus.WithContext(ctx).Debugf("[rsshub] get sources success: %d", len(sources))
return
}
@ -124,7 +124,7 @@ func (s *repoStorage) GetSourceByRssHubFeedLink(ctx context.Context, rssHubFeedL
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
logrus.WithContext(ctx).Errorf("[rsshub] get source error: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] get source error: %v", err)
return
}
return
@ -134,7 +134,7 @@ func (s *repoStorage) GetSourceByRssHubFeedLink(ctx context.Context, rssHubFeedL
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)
logrus.WithContext(ctx).Warnf("[rsshub] storage.DeleteSource: %v", err)
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("source not found")
}
@ -161,7 +161,7 @@ func (s *repoStorage) UpsertContent(ctx context.Context, content *RssContent) (e
}
err = s.orm.Create(content).Omit("id").Error
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub] storage.UpsertContent: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] storage.UpsertContent: %v", err)
return
}
return
@ -171,7 +171,7 @@ func (s *repoStorage) UpsertContent(ctx context.Context, content *RssContent) (e
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)
logrus.WithContext(ctx).Warnf("[rsshub] storage.DeleteSourceContents: %v", err)
return
}
return
@ -185,7 +185,7 @@ func (s *repoStorage) IsContentHashIDExist(ctx context.Context, hashID string) (
if errors.Is(err, gorm.ErrRecordNotFound) {
return false, nil
}
logrus.WithContext(ctx).Errorf("[rsshub] storage.IsContentHashIDExist: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] storage.IsContentHashIDExist: %v", err)
return false, err
}
return true, nil
@ -204,7 +204,7 @@ func (s *repoStorage) CreateSubscribe(ctx context.Context, gid, rssSourceID int6
}
err = s.orm.Create(&RssSubscribe{GroupID: gid, RssSourceID: rssSourceID}).Omit("id").Error
if err != nil {
logrus.WithContext(ctx).Errorf("[rsshub] storage.CreateSubscribe: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] storage.CreateSubscribe: %v", err)
return
}
return
@ -214,7 +214,7 @@ func (s *repoStorage) CreateSubscribe(ctx context.Context, gid, rssSourceID int6
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)
logrus.WithContext(ctx).Warnf("[rsshub] storage.DeleteSubscribe error: %v", err)
return
}
return
@ -228,7 +228,7 @@ func (s *repoStorage) GetSubscribeByID(ctx context.Context, gid int64, subscribe
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribeByID: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] storage.GetSubscribeByID: %v", err)
return nil, err
}
return
@ -247,7 +247,7 @@ func (s *repoStorage) GetSubscribedChannelsByGroupID(ctx context.Context, gid in
err = nil
return
}
logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribedChannelsByGroupID: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] storage.GetSubscribedChannelsByGroupID: %v", err)
return
}
return
@ -262,7 +262,7 @@ func (s *repoStorage) GetSubscribes(ctx context.Context) (res []*RssSubscribe, e
err = nil
return
}
logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribes: %v", err)
logrus.WithContext(ctx).Warnf("[rsshub] storage.GetSubscribes: %v", err)
return
}
return

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

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

@ -13,7 +13,10 @@
mkGoEnv ? pkgs.mkGoEnv,
gomod2nix ? pkgs.gomod2nix,
}: let
goEnv = mkGoEnv { pwd = ./.; go = pkgs.go_1_20; };
goEnv = mkGoEnv {
pwd = ./.;
go = pkgs.go_1_24;
};
in
pkgs.mkShell {
packages = [

View File

@ -12,7 +12,7 @@
"0409": {
"identity": {
"name": "ZeroBot-Plugin",
"version": "1.10.4.2318"
"version": "1.10.18.2382"
},
"description": "",
"minimum-os": "vista",
@ -36,23 +36,23 @@
"#1": {
"0000": {
"fixed": {
"file_version": "1.10.4.2318",
"product_version": "v1.10.4",
"timestamp": "2025-12-23T00:25:24+08:00"
"file_version": "1.10.18.2382",
"product_version": "v1.10.18",
"timestamp": "2026-02-01T01:08:28+08:00"
},
"info": {
"0409": {
"Comments": "OneBot plugins based on ZeroBot",
"CompanyName": "FloatTech",
"FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin",
"FileVersion": "1.10.4.2318",
"FileVersion": "1.10.18.2382",
"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.4",
"ProductVersion": "v1.10.18",
"SpecialBuild": ""
}
}