Compare commits

...

37 Commits

Author SHA1 Message Date
github-actions[bot]
43b45ce6c5 chore: bump deps (#1172)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-01 20:32:24 +09:00
源文雨
95dd5e6b94 chore: update deps 2025-06-01 20:30:46 +09:00
源文雨
566f6ecfd5 🔖 v1.9.8 2025-06-01 18:53:06 +09:00
github-actions[bot]
5b28ad75b7 chore: bump deps (#1171)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-01 17:58:46 +09:00
源文雨
997857a558 chore: update deps 2025-06-01 17:56:37 +09:00
github-actions[bot]
4269057283 chore: bump deps (#1170)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-01 16:27:17 +09:00
源文雨
f70cab80c2 feat(aichat): add more configs 2025-06-01 15:49:03 +09:00
源文雨
609d819610 chore: sync data 2025-06-01 15:06:20 +09:00
源文雨
961fbb098e 🔖 v1.9.7 2025-05-14 21:54:07 +09:00
github-actions[bot]
42fe124b09 chore(lint): 改进代码样式 (#1167)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-14 15:42:18 +09:00
Dodoj
076b113455 fix(wordcount): 修改分词模块至外部gse仓库 (#1165)
Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
2025-05-13 12:05:24 +00:00
github-actions[bot]
c888936489 chore: bump deps (#1166)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-13 20:58:41 +09:00
源文雨
e1d2dee881 feat: replace jieba with gse 2025-05-13 20:19:34 +09:00
Doordoorjay
39e1f56955 fix: 疯狂星期四 API (#1161) 2025-05-06 18:12:41 +09:00
Nobody6825
4151464bdc chore: use new nixpkgs with overlay which bring back go_1_20 instead of using old nixpkgs (#1162) 2025-05-06 18:11:46 +09:00
github-actions[bot]
0b89312d9d chore(lint): 改进代码样式 (#1159)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-23 18:36:41 +09:00
github-actions[bot]
2c607dedee chore: bump deps (#1158)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-04-23 18:36:20 +09:00
方柳煜
30e9d04f74 feat: 电影查询 (#1155) 2025-04-23 18:35:34 +09:00
himawari
4b90a0659b feat(bilibili): 添加视频下载 (#1157) 2025-04-23 18:33:15 +09:00
github-actions[bot]
109b7661b7 chore(lint): 改进代码样式 (#1151)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-30 23:50:57 +09:00
github-actions[bot]
8da52a2772 chore: bump deps (#1150)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
2025-03-30 23:50:36 +09:00
yexiaoyu
7515983b55 插件:AnimeTrace 动画/Galgame识别 (#1141)
* 插件:AnimeTrace 动画/Galgame识别

* update: 插件:AnimeTrace 动画/Galgame识别
2025-03-30 23:48:49 +09:00
github-actions[bot]
7519ea548d chore: bump deps (#1149)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-30 23:47:42 +09:00
源文雨
6a55d9b279 🔖 v1.9.6 2025-03-30 23:47:12 +09:00
源文雨
d5227f1159 optimize(aichat): use config struct 2025-03-30 23:28:55 +09:00
源文雨
62e9fe69ed feat(aichat): add more funcs 2025-03-30 22:12:48 +09:00
源文雨
2df52161e5 feat(aichat): add OLLaMA & GenAI support 2025-03-30 21:36:31 +09:00
源文雨
a29f4cb1f9 feat(aichat): add sanitize 2025-03-29 17:07:14 +09:00
github-actions[bot]
6a747d2f9d chore(lint): 改进代码样式 (#1144)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-25 12:41:42 +09:00
源文雨
164c71a3a3 chore: remove dependabot 2025-03-25 12:35:46 +09:00
github-actions[bot]
3f1b0ad67b chore: bump deps (#1142)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-25 12:33:20 +09:00
github-actions[bot]
6c6699a5d6 chore(lint): 改进代码样式 (#1143)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-03-25 12:33:02 +09:00
RefactoringHero
e292b69ee5 feat: add plugin Minecraft 服务器状态查询 (#1135) 2025-03-25 12:30:40 +09:00
Nobody6825
e6e6dd4565 chore: pin nixpkgs to preserve dropped go_1_20 (#1139) 2025-03-08 19:33:09 +09:00
github-actions[bot]
28bfc3e71d chore: bump deps (#1133)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-02-22 22:05:07 +09:00
源文雨
d3975cf461 chore: update deps 2025-02-22 21:52:43 +09:00
源文雨
7430c41c1e fix(ci): deprecations 2025-02-22 15:42:03 +09:00
35 changed files with 2519 additions and 337 deletions

View File

@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule:
interval: daily

View File

@@ -21,7 +21,7 @@ jobs:
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@master
with:
version: latest
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -62,7 +62,7 @@ archives:
name_template: "zbp_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format_overrides:
- goos: windows
format: zip
formats: zip
nfpms:
- license: AGPL 3.0

View File

@@ -400,6 +400,18 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 支付宝到账 1
</details>
<details>
<summary>AnimeTrace 动画/Galgame识别</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace"`
基于[AnimeTrace](https://ai.animedb.cn/)API 的识图搜索插件
- [x] Gal识图 | Gal识图 [模型名]
- [x] 动漫识图 | 动漫识图 2 | 动漫识图 [模型名]
</details>
<details>
<summary>触发者撤回时也自动撤回</summary>
@@ -949,6 +961,28 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 符号说明: C5是中央C,后面不写数字,默认接5,Cb6<1,b代表降调,#代表升调,6比5高八度,<1代表音长×2,<3代表音长×8,<-1代表音长×0.5,<-3代表音长×0.125,R是休止符
</details>
<details>
<summary>Minecraft服务器监控&订阅</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/minecraftobserver"`
- [x] mc服务器状态 [服务器IP/URI]
- [x] mc服务器添加订阅 [服务器IP/URI]
- [x] mc服务器取消订阅 [服务器IP/URI]
- [x] mc服务器订阅拉取 (需要插件定时任务配合使用,全局只需要设置一个)
- 使用job插件设置定时, 对话例子如下:
- 记录在"@every 1m"触发的指令
- (机器人回答:您的下一条指令将被记录,在@@every 1m时触发
- mc服务器订阅拉取
</details>
<details>
<summary>Movies猫眼电影查询</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/movies"`
- [x] 今日电影
- [x] 预售电影
</details>
<details>
<summary>摸鱼</summary>
@@ -1544,12 +1578,18 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 设置AI聊天触发概率10
- [x] 设置AI聊天温度80
- [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]
- [x] 设置AI聊天(不)支持系统提示词
- [x] 设置AI聊天接口地址https://xxx
- [x] 设置AI聊天密钥xxx
- [x] 设置AI聊天模型名xxx
- [x] 设置AI聊天系统提示词xxx
- [x] 查看AI聊天系统提示词
- [x] 重置AI聊天系统提示词
- [x] 设置AI聊天系统提示词xxx
- [x] 设置AI聊天分隔符`</think>`(留空则清除)
- [x] 设置AI聊天(不)响应AT
- [x] 设置AI聊天最大长度4096
- [x] 设置AI聊天TopP 0.9
</details>
<details>

2
data

Submodule data updated: ca3652920a...4f751a1cda

View File

@@ -11,6 +11,7 @@
}
),
buildGoApplication ? pkgs.buildGoApplication,
...
}:
buildGoApplication {
pname = "ZeroBot-Plugin";

37
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1705309234,
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -28,11 +28,11 @@
]
},
"locked": {
"lastModified": 1705314449,
"narHash": "sha256-yfQQ67dLejP0FLK76LKHbkzcQqNIrux6MFe32MMFGNQ=",
"lastModified": 1742209644,
"narHash": "sha256-jMy1XqXqD0/tJprEbUmKilTkvbDY/C0ZGSsJJH4TNCE=",
"owner": "nix-community",
"repo": "gomod2nix",
"rev": "30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6",
"rev": "8f3534eb8f6c5c3fce799376dc3b91bae6b11884",
"type": "github"
},
"original": {
@@ -43,11 +43,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1705856552,
"narHash": "sha256-JXfnuEf5Yd6bhMs/uvM67/joxYKoysyE3M2k6T3eWbg=",
"lastModified": 1745391562,
"narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "612f97239e2cc474c13c9dafa0df378058c5ad8d",
"rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7",
"type": "github"
},
"original": {
@@ -57,11 +57,28 @@
"type": "github"
}
},
"nixpkgs-with-go_1_20": {
"locked": {
"lastModified": 1710843028,
"narHash": "sha256-CMbK45c4nSkGvayiEHFkGFH+doGPbgo3AWfecd2t1Fk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "33c51330782cb486764eb598d5907b43dc87b4c2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "33c51330782cb486764eb598d5907b43dc87b4c2",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gomod2nix": "gomod2nix",
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"nixpkgs-with-go_1_20": "nixpkgs-with-go_1_20"
}
},
"systems": {

View File

@@ -1,6 +1,7 @@
{
description = " ZeroBot OneBot ";
inputs.nixpkgs-with-go_1_20.url = "github:NixOS/nixpkgs/33c51330782cb486764eb598d5907b43dc87b4c2";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.gomod2nix.url = "github:nix-community/gomod2nix";
@@ -10,14 +11,25 @@
outputs = {
self,
nixpkgs,
nixpkgs-with-go_1_20,
flake-utils,
gomod2nix,
}: let
...
} @ inputs: let
allSystems = flake-utils.lib.allSystems;
in (
flake-utils.lib.eachSystem allSystems
(system: let
pkgs = nixpkgs.legacyPackages.${system};
old-nixpkgs = nixpkgs-with-go_1_20.legacyPackages.${system};
pkgs = import nixpkgs {
inherit system;
overlays = [
(_: _: {
go_1_20 = old-nixpkgs.go_1_20;
})
];
};
# The current default sdk for macOS fails to compile go projects, so we use a newer one for now.
# This has no effect on other platforms.
@@ -25,11 +37,10 @@
in {
# doCheck will fail at write files
packages = rec {
ZeroBot-Plugin =
(callPackage ./. {
ZeroBot-Plugin = (callPackage ./. (inputs
// {
inherit (gomod2nix.legacyPackages.${system}) buildGoApplication;
})
}))
.overrideAttrs (_: {doCheck = false;});
default = ZeroBot-Plugin;
@@ -42,7 +53,6 @@
pkgs.cacert
];
};
};
devShells.default = callPackage ./shell.nix {
inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix;

21
go.mod
View File

@@ -4,24 +4,25 @@ go 1.20
require (
github.com/Baidu-AIP/golang-sdk v1.1.1
github.com/FloatTech/AnimeAPI v1.7.1-0.20250217140215-4856397458c9
github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024
github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b
github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80
github.com/FloatTech/gg v1.1.3
github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef
github.com/FloatTech/rendercard v0.2.0
github.com/FloatTech/sqlite v1.7.1
github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562
github.com/FloatTech/zbpctrl v1.7.0
github.com/FloatTech/zbputils v1.7.2-0.20250222055844-5d403aa9cecf
github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5
github.com/Tnze/go-mc v1.20.2
github.com/antchfx/htmlquery v1.3.4
github.com/corona10/goimagehash v1.1.0
github.com/davidscholberg/go-durationfmt v0.0.0-20170122144659-64843a2083d3
github.com/disintegration/imaging v1.6.2
github.com/fumiama/ahsai v0.1.0
github.com/fumiama/cron v1.3.0
github.com/fumiama/deepinfra v0.0.0-20250222055014-e969fc5b4ccf
github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1
github.com/fumiama/go-base16384 v1.7.0
github.com/fumiama/go-registry v0.2.7
github.com/fumiama/gotracemoe v0.0.3
@@ -29,7 +30,9 @@ require (
github.com/fumiama/slowdo v0.0.0-20241001074058-27c4fe5259a4
github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce
github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6
github.com/go-ego/gse v0.80.3
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.6.0
github.com/jinzhu/gorm v1.9.16
github.com/jozsefsallai/gophersauce v1.0.1
github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5
@@ -42,7 +45,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/tidwall/gjson v1.18.0
github.com/wcharczuk/go-chart/v2 v2.1.2
github.com/wdvxdr1123/ZeroBot v1.8.0
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5
gitlab.com/gomidi/midi/v2 v2.1.7
golang.org/x/image v0.24.0
golang.org/x/sys v0.30.0
@@ -59,11 +62,10 @@ require (
github.com/faiface/beep v1.1.0 // indirect
github.com/fumiama/go-simple-protobuf v0.2.0 // indirect
github.com/fumiama/gofastTEA v0.0.10 // indirect
github.com/fumiama/imgsz v0.0.4 // indirect
github.com/fumiama/imgsz v0.0.2 // indirect
github.com/gabriel-vasile/mimetype v1.0.4 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/jfreymuth/oggvorbis v1.0.1 // indirect
github.com/jfreymuth/vorbis v1.0.0 // indirect
@@ -84,9 +86,10 @@ require (
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/vcaesar/cedar v0.20.2 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
golang.org/x/exp/shiny v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect
golang.org/x/net v0.33.0 // indirect
modernc.org/libc v1.61.0 // indirect
modernc.org/mathutil v1.6.0 // indirect

37
go.sum
View File

@@ -1,10 +1,10 @@
github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw=
github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/FloatTech/AnimeAPI v1.7.1-0.20250217140215-4856397458c9 h1:tI9GgG8fdMK2WazFiEbMXAXjwMCckIfDaXbig9B6DdA=
github.com/FloatTech/AnimeAPI v1.7.1-0.20250217140215-4856397458c9/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js=
github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024 h1:mrvWpiwfRklt9AyiQjKgDGJjf4YL6FZ3yC+ydbkuF2o=
github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024/go.mod h1:+P3hs+Cvl10/Aj3SNE96TuBvKAXCe+XD1pKphTZyiwk=
github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b h1:H/1xpchTGmdoHqrszH4gjafCyHIhsGSFryAkBNsu8OI=
github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js=
github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 h1:lFD1pd8NkYCrw0QpTX/T5pJ67I7AL5eGxQ4v0r9f81Q=
github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80/go.mod h1:IWoFFqu+0FeaHHQdddyiTRL5z7gJME6qHC96qh0R2sc=
github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4=
github.com/FloatTech/gg v1.1.3/go.mod h1:/9oLP54CMfq4r+71XL26uaFTJ1uL1boAyX67680/1HE=
github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef h1:CJbK/2FRwPuZpeb6M4sWK2d7oXDnBEGhpkQuQrgc91A=
@@ -17,13 +17,15 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ
github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo=
github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE=
github.com/FloatTech/zbputils v1.7.2-0.20250222055844-5d403aa9cecf h1:7LenXdFO5gFJ6YRjCdxOadm+JqQHpkAAHbagVMXLjkg=
github.com/FloatTech/zbputils v1.7.2-0.20250222055844-5d403aa9cecf/go.mod h1:2nILgq7ps2fLsfhns1/L2yCAM2OfIwWbEl28yLztuzk=
github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6 h1:85LGKvgkWMZU065WOIEVaaeHDNYK01CwdNr/m+jzTKw=
github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 h1:bBmmB7he0iVN4m5mcehfheeRUEer/Avo4ujnxI3uCqs=
github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5/go.mod h1:0UcFaCkhp6vZw6l5Dpq0Dp673CoF9GdvA8lTfst0GiU=
github.com/Tnze/go-mc v1.20.2 h1:arHCE/WxLCxY73C/4ZNLdOymRYtdwoXE05ohB7HVN6Q=
github.com/Tnze/go-mc v1.20.2/go.mod h1:geoRj2HsXSkB3FJBuhr7wCzXegRlzWsVXd7h7jiJ6aQ=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d h1:ir/IFJU5xbja5UaBEQLjcvn7aAU01nqU/NUyOBEU+ew=
github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0yifz6XDPZu48aSld8BWwBfr2JKB2bGWiEd4=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
@@ -57,8 +59,8 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4=
github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk=
github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo=
github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY=
github.com/fumiama/deepinfra v0.0.0-20250222055014-e969fc5b4ccf h1:xi3K9hukyF34JTLZTNCwM42gxcWKGJXSVou/U0pTYKg=
github.com/fumiama/deepinfra v0.0.0-20250222055014-e969fc5b4ccf/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY=
github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1 h1:qE3l/y4Y1gMD2NokQ5Nw4NIUjL8ZwYLPIHOExQNu4hM=
github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY=
github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA=
github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs=
@@ -69,8 +71,8 @@ github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoG
github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk=
github.com/fumiama/gotracemoe v0.0.3 h1:iI5EbE9A3UUbfukG6+/soYPjp1S31eCNYf4tw7s6/Jc=
github.com/fumiama/gotracemoe v0.0.3/go.mod h1:tyqahdUzHf0bQIAVY/GYmDWvYYe5ik1ZbhnGYh+zl40=
github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI=
github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E=
github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak=
github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4=
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 h1:sQuR2+N5HurnvsZhiKdEg+Ig354TaqgCQRxd/0KgIOQ=
github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565/go.mod h1:UUEvyLTJ7yoOA/viKG4wEis4ERydM7+Ny6gZUWgkS80=
github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5 h1:jDxsIupsT84A6WHcs6kWbst+KqrRQ8/o0VyoFMnbBOA=
@@ -90,6 +92,8 @@ github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebK
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg=
github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -190,10 +194,13 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4=
github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
github.com/wdvxdr1123/ZeroBot v1.8.0 h1:v7m+0kGtL6XQlUH9O/LzmOntDJs2clzVj93YsAWWMbk=
github.com/wdvxdr1123/ZeroBot v1.8.0/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5 h1:HsMcBsVpYuQv+W8pjX5WdwYROrFQP9c5Pbf4x4adDus=
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
github.com/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=
@@ -208,16 +215,18 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp/shiny v0.0.0-20250305212735-054e65f0b394 h1:bFYqOIMdeiCEdzPJkLiOoMDzW/v3tjW4AA/RmUZYsL8=
golang.org/x/exp/shiny v0.0.0-20250305212735-054e65f0b394/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a h1:sYbmY3FwUWCBTodZL1S3JUuOvaW6kM2o+clDzzDNBWg=
golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a/go.mod h1:Ede7gF0KGoHlj822RtphAHK1jLdrcuRBZg0sF1Q+SPc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=

View File

@@ -5,11 +5,11 @@ schema = 3
version = "v1.1.1"
hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo="
[mod."github.com/FloatTech/AnimeAPI"]
version = "v1.7.1-0.20250217140215-4856397458c9"
hash = "sha256-7TkWoVslfzO/aTx+F7UwttrtBGGMMqe4GHN0aF4JUd0="
version = "v1.7.1-0.20250530055006-50f5c7587c5b"
hash = "sha256-xDXPwphMS26J02q/ysQy3CJPK5B9c7uoqwFt1xFIF5Q="
[mod."github.com/FloatTech/floatbox"]
version = "v0.0.0-20241106130736-5aea0a935024"
hash = "sha256-hSKmkzpNZwXRo0qm4G+1lXkNzWMwV9leYlYLQuzWx3M="
version = "v0.0.0-20250513111443-adba80e84e80"
hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE="
[mod."github.com/FloatTech/gg"]
version = "v1.1.3"
hash = "sha256-7K/R2mKjUHVnoJ3b1wDObJ5Un2Htj59Y97G1Ja1tuPo="
@@ -29,14 +29,17 @@ schema = 3
version = "v1.7.0"
hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU="
[mod."github.com/FloatTech/zbputils"]
version = "v1.7.2-0.20250222055844-5d403aa9cecf"
hash = "sha256-+Qm3/z3M3OzgknjVx5h+Px6WUEIcXjK53ZSLSnRInJ4="
version = "v1.7.2-0.20250601113004-1bee2a7cd4b6"
hash = "sha256-pNL591h1gGP60wKEuvvF3DoshbaphoLze7Pa7gDA9bQ="
[mod."github.com/RomiChan/syncx"]
version = "v0.0.0-20240418144900-b7402ffdebc7"
hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4="
[mod."github.com/RomiChan/websocket"]
version = "v1.4.3-0.20220227141055-9b2c6168c9c5"
hash = "sha256-Adx+gvqB+CCoUXx7ebIaBDjVkav+wS5qZPmaqcApBWA="
[mod."github.com/Tnze/go-mc"]
version = "v1.20.2"
hash = "sha256-Nu4PXNxeARH0itm6yIIplFaywL2yQnPJFksmmuyIptI="
[mod."github.com/adamzy/cedar-go"]
version = "v0.0.0-20170805034717-80a9c64b256d"
hash = "sha256-N19KTxh70IUBqnchFuWkrJD8uuFOIVqv1iSuN3YFIT0="
@@ -74,8 +77,8 @@ schema = 3
version = "v1.3.0"
hash = "sha256-/sN7X8dKXQgv8J+EDzVUB+o+AY9gBC8e1C6sYhaTy1k="
[mod."github.com/fumiama/deepinfra"]
version = "v0.0.0-20250222055014-e969fc5b4ccf"
hash = "sha256-OD/5pId1fCSy6BsXnlgUWUcQqDiz0xpKezmozMwtpBQ="
version = "v0.0.0-20250601112706-0175c95164c1"
hash = "sha256-/8Hufq5n84QHOgS0igYQWo1zxjOBBbrqad2wQfKHBhY="
[mod."github.com/fumiama/go-base16384"]
version = "v1.7.0"
hash = "sha256-vTAsBBYe2ISzb2Nba5E96unodZSkhMcqo6hbwR01nz8="
@@ -92,8 +95,8 @@ schema = 3
version = "v0.0.3"
hash = "sha256-O3cDkVXu5NG1ZtzubxhH+S91zfgu4uH1L+OiSGYSNXQ="
[mod."github.com/fumiama/imgsz"]
version = "v0.0.4"
hash = "sha256-rrGx+v41OEl0ATwL6u5TNcpfkCQbj3jFNnGiQUNu2qs="
version = "v0.0.2"
hash = "sha256-eYUjP1TKWUrsY++rzg4rezOvmvmjADZFBizIIDHnZtY="
[mod."github.com/fumiama/jieba"]
version = "v0.0.0-20221203025406-36c17a10b565"
hash = "sha256-DvDx1pdldkdaSszrbadM/VwqT9TTSmWl6G6a+ysXYEM="
@@ -109,6 +112,9 @@ schema = 3
[mod."github.com/gabriel-vasile/mimetype"]
version = "v1.0.4"
hash = "sha256-5hl9zBo3nkPt8dZfcLoOix8lAKLm3qIkWhopoS4V34E="
[mod."github.com/go-ego/gse"]
version = "v0.80.3"
hash = "sha256-uxTQN4cxE/ZReZqjlIEQ3WYD9w2Ec37LRHQftJXsSZQ="
[mod."github.com/go-ole/go-ole"]
version = "v1.2.6"
hash = "sha256-+oxitLeJxYF19Z6g+6CgmCHJ1Y5D8raMi2Cb3M6nXCs="
@@ -214,27 +220,30 @@ schema = 3
[mod."github.com/tklauser/numcpus"]
version = "v0.6.1"
hash = "sha256-8eFcw4YI0w6+GPhU5xMMQjiio94q/O5PpNO3QsvXve0="
[mod."github.com/vcaesar/cedar"]
version = "v0.20.2"
hash = "sha256-3WblBdkR9AZcvZCKSteBV5kdhahiFHG2dbLWfwrVkwM="
[mod."github.com/wcharczuk/go-chart/v2"]
version = "v2.1.2"
hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM="
[mod."github.com/wdvxdr1123/ZeroBot"]
version = "v1.8.0"
hash = "sha256-3xQ+5NqZpHJdge1vrh0/bttaZt6u2ZiGtdZA0m80NBc="
version = "v1.8.2-0.20250330133859-27c25d9412b5"
hash = "sha256-gT3uFTg5E0Th3r1M1vLzr0QtOjbMusqEjD/ckoBdDFc="
[mod."github.com/yusufpapurcu/wmi"]
version = "v1.2.4"
hash = "sha256-N+YDBjOW59YOsZ2lRBVtFsEEi48KhNQRb63/0ZSU3bA="
[mod."gitlab.com/gomidi/midi/v2"]
version = "v2.1.7"
hash = "sha256-fbgxSMCk7PVII3sNEKuGWbN56fy3eM564Xb+lnYTxRQ="
[mod."golang.org/x/exp"]
version = "v0.0.0-20190306152737-a1d7652674e8"
hash = "sha256-VJ0sxFsqnx2O/NmXamL2F5bQeUw5sizVQ7NLusceK5Q="
[mod."golang.org/x/exp/shiny"]
version = "v0.0.0-20250305212735-054e65f0b394"
hash = "sha256-+xzaSlgRHFa+sGnQG90/72vcJMhletsob/L+KG24P/A="
[mod."golang.org/x/image"]
version = "v0.24.0"
hash = "sha256-nhcznNf4ePM7d0Jy2Si0dpMt7KQfRF5Y5QzMpwFCAVg="
[mod."golang.org/x/mobile"]
version = "v0.0.0-20190415191353-3e0bab5405d6"
hash = "sha256-Ds7JS9muxzDc7WgCncAd0rMSFeBI88/I0dQsk13/56k="
version = "v0.0.0-20231127183840-76ac6878050a"
hash = "sha256-GdXSvrqQiJX6pOqc2Yr8gG0ZWysEE81YRl5qkt3JCMA="
[mod."golang.org/x/net"]
version = "v0.33.0"
hash = "sha256-9swkU9vp6IflUUqAzK+y8PytSmrKLuryidP3RmRfe0w="

View File

@@ -3,13 +3,13 @@
package banner
// Version ...
var Version = "v1.9.5"
var Version = "v1.9.8"
// Copyright ...
var Copyright = "© 2020 - 2025 FloatTech"
// Banner ...
var Banner = "* OneBot + ZeroBot + Golang\n" +
"* Version " + Version + " - 2025-02-22 15:34:28 +0900 JST\n" +
"* Version " + Version + " - 2025-06-01 18:52:48 +0900 JST\n" +
"* Copyright " + Copyright + ". All Rights Reserved.\n" +
"* Project: https://github.com/FloatTech/ZeroBot-Plugin"

171
main.go
View File

@@ -62,90 +62,93 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
_ "github.com/FloatTech/ZeroBot-Plugin/custom" // 自定义插件合集
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai" // ahsai tts
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aifalse" // 服务器监控
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/alipayvoice" // 支付宝到账语音
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/autowithdraw" // 触发者撤回时也自动撤回
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/baiduaudit" // 百度内容审核
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/base16384" // base16384加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/base64gua" // base64卦加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/baseamasiro" // base天城文加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili" // b站相关
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/bookreview" // 哀伤雪刃吧推书记录
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chess" // 国际象棋
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/choose" // 选择困难症帮手
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chouxianghua" // 说抽象话
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chrev" // 英文字符翻转
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/coser" // 三次元小姐姐
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/cpstory" // cp短打
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/dailynews" // 今日早报
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/dish" // 程序员做饭指南
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/drawlots" // 多功能抽签
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/driftbottle" // 漂流瓶
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/emojimix" // 合成emoji
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/emozi" // 颜文字抽象转写
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/event" // 好友申请群聊邀请事件处理
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/font" // 渲染任意文字到图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/fortune" // 运势
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/funny" // 笑话
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/genshin" // 原神抽卡
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/gif" // 制图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/github" // 搜索GitHub仓库
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic" // 猜歌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hitokoto" // 一言
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hs" // 炉石
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hyaku" // 百人一首
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/inject" // 注入指令
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jptingroom" // 日语听力学习材料
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/kfccrazythursday" // 疯狂星期四
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolimi" // 桑帛云 API
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/magicprompt" // magicprompt吟唱提示
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/mcfish" // 钓鱼模拟器
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyucalendar" // 摸鱼人日历
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativesetu" // 本地涩图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nbnhhsh" // 拼音首字母缩写释义工具
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nihongo" // 日语语法学习
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/niuniu" // 牛牛大作战
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/novel" // 铅笔小说网搜索
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nsfw" // nsfw图片识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nwife" // 本地老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/poker" // 抽扑克
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qzone" // qq空间表白墙
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/realcugan" // realcugan清晰术
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/robbery" // 打劫群友的ATRI币
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/score" // 分数
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/setutime" // 来份涩图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/shadiao" // 沙雕app
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/shindan" // 测定
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/steam" // steam相关
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/tarot" // 抽塔罗牌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/tiangou" // 舔狗日记
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/tracemoe" // 搜番
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/translation" // 翻译
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wallet" // 钱包
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wantquotes" // 据意查句
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/warframeapi" // warframeAPI插件
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenxinvilg" // 百度文心AI画图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wife" // 抽老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordcount" // 聊天热词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ygo" // 游戏王相关插件
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/yujn" // 遇见API
_ "github.com/FloatTech/ZeroBot-Plugin/custom" // 自定义插件合集
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai" // ahsai tts
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aifalse" // 服务器监控
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/alipayvoice" // 支付宝到账语音
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace" // AnimeTrace 动画/Galgame识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/autowithdraw" // 触发者撤回时也自动撤回
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/baiduaudit" // 百度内容审核
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/base16384" // base16384加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/base64gua" // base64卦加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/baseamasiro" // base天城文加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili" // b站相关
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/bookreview" // 哀伤雪刃吧推书记录
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chess" // 国际象棋
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/choose" // 选择困难症帮手
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chouxianghua" // 说抽象话
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chrev" // 英文字符翻转
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/coser" // 三次元小姐姐
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/cpstory" // cp短打
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/dailynews" // 今日早报
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/dish" // 程序员做饭指南
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/drawlots" // 多功能抽签
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/driftbottle" // 漂流瓶
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/emojimix" // 合成emoji
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/emozi" // 颜文字抽象转写
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/event" // 好友申请群聊邀请事件处理
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/font" // 渲染任意文字到图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/fortune" // 运势
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/funny" // 笑话
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/genshin" // 原神抽卡
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/gif" // 制图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/github" // 搜索GitHub仓库
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic" // 猜歌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hitokoto" // 一言
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hs" // 炉石
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hyaku" // 百人一首
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/inject" // 注入指令
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jptingroom" // 日语听力学习材料
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/kfccrazythursday" // 疯狂星期四
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolimi" // 桑帛云 API
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/magicprompt" // magicprompt吟唱提示
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/mcfish" // 钓鱼模拟器
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/minecraftobserver" // Minecraft服务器监控&订阅
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/movies" // 电影插件
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyucalendar" // 摸鱼人日历
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativesetu" // 本地涩图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nbnhhsh" // 拼音首字母缩写释义工具
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nihongo" // 日语语法学习
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/niuniu" // 牛牛大作战
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/novel" // 铅笔小说网搜索
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nsfw" // nsfw图片识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nwife" // 本地老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/poker" // 抽扑克
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qzone" // qq空间表白墙
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/realcugan" // realcugan清晰术
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/robbery" // 打劫群友的ATRI币
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/score" // 分数
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/setutime" // 来份涩图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/shadiao" // 沙雕app
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/shindan" // 测定
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/steam" // steam相关
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/tarot" // 抽塔罗牌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/tiangou" // 舔狗日记
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/tracemoe" // 搜番
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/translation" // 翻译
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wallet" // 钱包
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wantquotes" // 据意查句
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/warframeapi" // warframeAPI插件
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenxinvilg" // 百度文心AI画图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wife" // 抽老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordcount" // 聊天热词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ygo" // 游戏王相关插件
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/yujn" // 遇见API
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wtf" // 鬼东西

153
plugin/aichat/cfg.go Normal file
View File

@@ -0,0 +1,153 @@
package aichat
import (
"strconv"
"strings"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/chat"
"github.com/fumiama/deepinfra"
"github.com/fumiama/deepinfra/model"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
var cfg = newconfig()
type config struct {
ModelName string
Type int
MaxN uint
TopP float32
SystemP string
API string
Key string
Separator string
NoReplyAT bool
NoSystemP bool
}
func newconfig() config {
return config{
ModelName: model.ModelDeepDeek,
SystemP: chat.SystemPrompt,
API: deepinfra.OpenAIDeepInfra,
}
}
func (c *config) isvalid() bool {
return c.ModelName != "" && c.API != "" && c.Key != ""
}
func ensureconfig(ctx *zero.Ctx) bool {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return false
}
if !cfg.isvalid() {
err := c.GetExtra(&cfg)
if err != nil {
logrus.Warnln("ERROR: get extra err:", err)
}
if !cfg.isvalid() {
cfg = newconfig()
}
}
return true
}
func newextrasetstr(ptr *string) func(ctx *zero.Ctx) {
return func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
*ptr = args
err := c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}
func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) {
return func(ctx *zero.Ctx) {
args := ctx.State["regex_matched"].([]string)
isno := args[1] == "不"
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
*ptr = isno
err := c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}
func newextrasetuint(ptr *uint) func(ctx *zero.Ctx) {
return func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
n, err := strconv.ParseUint(args, 10, 64)
if err != nil {
ctx.SendChain(message.Text("ERROR: parse args err: ", err))
return
}
*ptr = uint(n)
err = c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}
func newextrasetfloat32(ptr *float32) func(ctx *zero.Ctx) {
return func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
n, err := strconv.ParseFloat(args, 32)
if err != nil {
ctx.SendChain(message.Text("ERROR: parse args err: ", err))
return
}
*ptr = float32(n)
err = c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
}
}

View File

@@ -3,11 +3,8 @@ package aichat
import (
"math/rand"
"os"
"strconv"
"strings"
"sync/atomic"
"unsafe"
"github.com/fumiama/deepinfra"
"github.com/fumiama/deepinfra/model"
@@ -16,7 +13,6 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/process"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/chat"
@@ -24,63 +20,39 @@ import (
)
var (
api *deepinfra.API
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
// en data [8 temp] [8 rate] LSB
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Extra: control.ExtraFromString("aichat"),
Brief: "OpenAI聊天",
Help: "- 设置AI聊天触发概率10\n" +
"- 设置AI聊天温度80\n" +
"- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" +
"- 设置AI聊天(不)支持系统提示词\n" +
"- 设置AI聊天接口地址https://xxx\n" +
"- 设置AI聊天密钥xxx\n" +
"- 设置AI聊天模型名xxx\n" +
"- 查看AI聊天系统提示词\n" +
"- 重置AI聊天系统提示词\n" +
"- 设置AI聊天系统提示词xxx\n" +
"- 设置AI聊天分隔符</think>(留空则清除)\n" +
"- 设置AI聊天(不)响应AT",
"- 设置AI聊天(不)响应AT\n" +
"- 设置AI聊天最大长度4096\n" +
"- 设置AI聊天TopP 0.9",
PrivateDataFolder: "aichat",
})
)
var (
modelname = model.ModelDeepDeek
systemprompt = chat.SystemPrompt
sepstr = ""
noreplyat = false
)
var apitypes = map[string]uint8{
"OpenAI": 0,
"OLLaMA": 1,
"GenAI": 2,
}
func init() {
mf := en.DataFolder() + "model.txt"
sf := en.DataFolder() + "system.txt"
pf := en.DataFolder() + "sep.txt"
nf := en.DataFolder() + "NoReplyAT"
if file.IsExist(mf) {
data, err := os.ReadFile(mf)
if err != nil {
logrus.Warnln("read model", err)
} else {
modelname = string(data)
}
}
if file.IsExist(sf) {
data, err := os.ReadFile(sf)
if err != nil {
logrus.Warnln("read system", err)
} else {
systemprompt = string(data)
}
}
if file.IsExist(pf) {
data, err := os.ReadFile(pf)
if err != nil {
logrus.Warnln("read sep", err)
} else {
sepstr = string(data)
}
}
noreplyat = file.IsExist(nf)
en.OnMessage(func(ctx *zero.Ctx) bool {
return ctx.ExtractPlainText() != "" && (!noreplyat || (noreplyat && !ctx.Event.IsToMe))
en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool {
return ctx.ExtractPlainText() != "" &&
(!cfg.NoReplyAT || (cfg.NoReplyAT && !ctx.Event.IsToMe))
}).SetBlock(false).Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
@@ -99,24 +71,11 @@ func init() {
if ctx.Event.IsToMe {
ctx.Block()
}
key := ""
err := c.GetExtra(&key)
if err != nil {
logrus.Warnln("ERROR: get extra err:", err)
return
}
if key == "" {
if cfg.Key == "" {
logrus.Warnln("ERROR: get extra err: empty key")
return
}
var x deepinfra.API
y := &x
if api == nil {
x = deepinfra.NewAPI(deepinfra.APIDeepInfra, key)
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&api)), unsafe.Pointer(&x))
} else {
y = api
}
if temp <= 0 {
temp = 70 // default setting
}
@@ -124,15 +83,45 @@ func init() {
temp = 100
}
data, err := y.Request(chat.Ask(model.NewOpenAI(
modelname, sepstr,
float32(temp)/100, 0.9, 4096,
), gid, systemprompt))
if err != nil {
logrus.Warnln("[niniqun] post err:", err)
x := deepinfra.NewAPI(cfg.API, cfg.Key)
var mod model.Protocol
maxn := cfg.MaxN
if maxn == 0 {
maxn = 4096
}
topp := cfg.TopP
if topp == 0 {
topp = 0.9
}
switch cfg.Type {
case 0:
mod = model.NewOpenAI(
cfg.ModelName, cfg.Separator,
float32(temp)/100, topp, maxn,
)
case 1:
mod = model.NewOLLaMA(
cfg.ModelName, cfg.Separator,
float32(temp)/100, topp, maxn,
)
case 2:
mod = model.NewGenAI(
cfg.ModelName,
float32(temp)/100, topp, maxn,
)
default:
logrus.Warnln("[aichat] unsupported AI type", cfg.Type)
return
}
txt := strings.Trim(data, "\n  ")
data, err := x.Request(chat.Ask(mod, gid, cfg.SystemP, cfg.NoSystemP))
if err != nil {
logrus.Warnln("[aichat] post err:", err)
return
}
txt := chat.Sanitize(strings.Trim(data, "\n  "))
if len(txt) > 0 {
chat.Reply(gid, txt)
nick := zero.BotConfig.NickName[rand.Intn(len(zero.BotConfig.NickName))]
@@ -221,7 +210,7 @@ func init() {
}
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天密钥", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
en.OnPrefix("设置AI聊天接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
@@ -232,82 +221,52 @@ func init() {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
err := c.SetExtra(&args)
typ, ok := apitypes[args]
if !ok {
ctx.SendChain(message.Text("ERROR: 未知类型 ", args))
return
}
cfg.Type = int(typ)
err := c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天模型名", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
en.OnPrefix("设置AI聊天接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.API))
en.OnPrefix("设置AI聊天密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.Key))
en.OnPrefix("设置AI聊天模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.ModelName))
en.OnPrefix("设置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.SystemP))
en.OnFullMatch("查看AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
ctx.SendChain(message.Text(cfg.SystemP))
})
en.OnFullMatch("重置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
modelname = args
err := os.WriteFile(mf, []byte(args), 0644)
cfg.SystemP = chat.SystemPrompt
err := c.SetExtra(&cfg)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
ctx.SendChain(message.Text("ERROR: set extra err: ", err))
return
}
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天系统提示词", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
ctx.SendChain(message.Text("ERROR: empty args"))
return
}
systemprompt = args
err := os.WriteFile(sf, []byte(args), 0644)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("成功"))
})
en.OnFullMatch("重置AI聊天系统提示词", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
systemprompt = chat.SystemPrompt
_ = os.Remove(sf)
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天分隔符", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := strings.TrimSpace(ctx.State["args"].(string))
if args == "" {
sepstr = ""
_ = os.Remove(pf)
ctx.SendChain(message.Text("清除成功"))
return
}
sepstr = args
err := os.WriteFile(pf, []byte(args), 0644)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("设置成功"))
})
en.OnRegex("^设置AI聊天(不)?响应AT$", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := ctx.State["regex_matched"].([]string)
isno := args[1] == "不"
if isno {
f, err := os.Create(nf)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
defer f.Close()
_, err = f.WriteString("PLACEHOLDER")
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
noreplyat = true
} else {
_ = os.Remove(nf)
noreplyat = false
}
ctx.SendChain(message.Text("成功"))
})
en.OnPrefix("设置AI聊天分隔符", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetstr(&cfg.Separator))
en.OnRegex("^设置AI聊天(不)?响应AT$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetbool(&cfg.NoReplyAT))
en.OnRegex("^设置AI聊天(不)?支持系统提示词$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetbool(&cfg.NoSystemP))
en.OnPrefix("设置AI聊天最大长度", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetuint(&cfg.MaxN))
en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
Handle(newextrasetfloat32(&cfg.TopP))
}

145
plugin/animetrace/main.go Normal file
View File

@@ -0,0 +1,145 @@
// Package animetrace AnimeTrace 动画/Galgame识别
package animetrace
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"image"
"image/jpeg"
"mime/multipart"
"strings"
"github.com/FloatTech/floatbox/web"
"github.com/FloatTech/imgfactory"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/disintegration/imaging"
"github.com/tidwall/gjson"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
func init() {
engine := control.Register("animetrace", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "AnimeTrace 动画/Galgame识别插件",
Help: "- Gal识图\n- 动漫识图\n- 动漫识图 2\n- 动漫识图 [模型名]\n- Gal识图 [模型名]",
})
engine.OnPrefix("gal识图", zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := ctx.State["args"].(string)
var model string
switch strings.TrimSpace(args) {
case "":
model = "full_game_model_kira" // 默认使用的模型
default:
model = args // 自定义设置模型
}
processImageRecognition(ctx, model)
})
engine.OnPrefix("动漫识图", zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true).Handle(func(ctx *zero.Ctx) {
args := ctx.State["args"].(string)
var model string
switch strings.TrimSpace(args) {
case "":
model = "anime_model_lovelive"
case "2":
model = "pre_stable"
default:
model = args
}
processImageRecognition(ctx, model)
})
}
// 处理图片识别
func processImageRecognition(ctx *zero.Ctx, model string) {
urls := ctx.State["image_url"].([]string)
if len(urls) == 0 {
return
}
imageData, err := imgfactory.Load(urls[0])
if err != nil {
ctx.Send(message.Text("下载图片失败: ", err))
return
}
// ctx.Send(message.Text(model))
respBody, err := createAndSendMultipartRequest("https://api.animetrace.com/v1/search", imageData, map[string]string{
"is_multi": "0",
"model": model,
"ai_detect": "0",
})
if err != nil {
ctx.Send(message.Text("识别请求失败: ", err))
return
}
code := gjson.Get(string(respBody), "code").Int()
if code != 0 {
ctx.Send(message.Text("错误: ", gjson.Get(string(respBody), "zh_message").String()))
return
}
dataArray := gjson.Get(string(respBody), "data").Array()
if len(dataArray) == 0 {
ctx.Send(message.Text("未识别到任何角色"))
return
}
var sk message.Message
sk = append(sk, ctxext.FakeSenderForwardNode(ctx, message.Text("共识别到 ", len(dataArray), " 个角色,可能是以下来源")))
for _, value := range dataArray {
boxArray := value.Get("box").Array()
imgWidth, imgHeight := imageData.Bounds().Dx(), imageData.Bounds().Dy() // 你可以从 `imageData.Bounds()` 获取
box := []int{
int(boxArray[0].Float() * float64(imgWidth)),
int(boxArray[1].Float() * float64(imgHeight)),
int(boxArray[2].Float() * float64(imgWidth)),
int(boxArray[3].Float() * float64(imgHeight)),
}
croppedImg := imaging.Crop(imageData, image.Rect(box[0], box[1], box[2], box[3]))
var buf bytes.Buffer
if err := imaging.Encode(&buf, croppedImg, imaging.JPEG, imaging.JPEGQuality(80)); err != nil {
ctx.Send(message.Text("图片编码失败: ", err))
continue
}
base64Str := base64.StdEncoding.EncodeToString(buf.Bytes())
var sb strings.Builder
value.Get("character").ForEach(func(_, character gjson.Result) bool {
sb.WriteString(fmt.Sprintf("《%s》的角色 %s\n", character.Get("work").String(), character.Get("character").String()))
return true
})
sk = append(sk, ctxext.FakeSenderForwardNode(ctx, message.Image("base64://"+base64Str), message.Text(sb.String())))
}
ctx.SendGroupForwardMessage(ctx.Event.GroupID, sk)
}
// 发送图片识别请求
func createAndSendMultipartRequest(url string, img image.Image, formFields map[string]string) ([]byte, error) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// 直接编码图片
part, err := writer.CreateFormFile("file", "image.jpg")
if err != nil {
return nil, errors.New("创建文件字段失败: " + err.Error())
}
if err := jpeg.Encode(part, img, &jpeg.Options{Quality: 80}); err != nil {
return nil, errors.New("图片编码失败: " + err.Error())
}
// 写入其他字段
for key, value := range formFields {
if err := writer.WriteField(key, value); err != nil {
return nil, errors.New("写入表单字段失败 (" + key + "): " + err.Error())
}
}
if err := writer.Close(); err != nil {
return nil, errors.New("关闭 multipart writer 失败: " + err.Error())
}
return web.PostData(url, writer.FormDataContentType(), body)
}

View File

@@ -2,25 +2,31 @@
package bilibili
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"regexp"
"strings"
"time"
bz "github.com/FloatTech/AnimeAPI/bilibili"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/pkg/errors"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
enableHex = 0x10
unableHex = 0x7fffffff_fffffffd
enableHex = 0x10
unableHex = 0x7fffffff_fffffffd
bilibiliparseReferer = "https://www.bilibili.com"
)
var (
@@ -33,6 +39,7 @@ var (
searchDynamicRe = regexp.MustCompile(searchDynamic)
searchArticleRe = regexp.MustCompile(searchArticle)
searchLiveRoomRe = regexp.MustCompile(searchLiveRoom)
cachePath string
)
// 插件主体
@@ -42,6 +49,9 @@ func init() {
Brief: "b站链接解析",
Help: "例:- t.bilibili.com/642277677329285174\n- bilibili.com/read/cv17134450\n- bilibili.com/video/BV13B4y1x7pS\n- live.bilibili.com/22603245 ",
})
cachePath = en.DataFolder() + "cache/"
_ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
en.OnRegex(`((b23|acg).tv|bili2233.cn)\\?/[0-9a-zA-Z]+`).SetBlock(true).Limit(limit.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
u := ctx.State["regex_matched"].([]string)[0]
@@ -126,6 +136,12 @@ func handleVideo(ctx *zero.Ctx) {
}
}
ctx.SendChain(msg...)
downLoadMsg, err := getVideoDownload(cfg, card, cachePath)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(downLoadMsg...)
}
func handleDynamic(ctx *zero.Ctx) {
@@ -189,3 +205,47 @@ func getVideoSummary(cookiecfg *bz.CookieConfig, card bz.Card) (msg []message.Se
}
return
}
func getVideoDownload(cookiecfg *bz.CookieConfig, card bz.Card, cachePath string) (msg []message.Segment, err error) {
var (
data []byte
videoDownload bz.VideoDownload
stderr bytes.Buffer
)
today := time.Now().Format("20060102")
videoFile := fmt.Sprintf("%s%s%s.mp4", cachePath, card.BvID, today)
if file.IsExist(videoFile) {
msg = append(msg, message.Video("file:///"+file.BOTPATH+"/"+videoFile))
return
}
data, err = web.RequestDataWithHeaders(web.NewDefaultClient(), bz.SignURL(fmt.Sprintf(bz.VideoDownloadURL, card.BvID, card.CID)), "GET", func(req *http.Request) error {
if cookiecfg != nil {
cookie := ""
cookie, err = cookiecfg.Load()
if err != nil {
return err
}
req.Header.Add("cookie", cookie)
}
req.Header.Set("User-Agent", ua)
return nil
}, nil)
if err != nil {
return
}
err = json.Unmarshal(data, &videoDownload)
if err != nil {
return
}
headers := fmt.Sprintf("User-Agent: %s\nReferer: %s", ua, bilibiliparseReferer)
// 限制最多下载8分钟视频
cmd := exec.Command("ffmpeg", "-ss", "0", "-t", "480", "-headers", headers, "-i", videoDownload.Data.Durl[0].URL, "-c", "copy", videoFile)
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
err = errors.Errorf("未配置ffmpeg%v", stderr)
return
}
msg = append(msg, message.Video("file:///"+file.BOTPATH+"/"+videoFile))
return
}

View File

@@ -2,10 +2,12 @@ package bilibili
import (
"encoding/json"
"fmt"
"time"
bz "github.com/FloatTech/AnimeAPI/bilibili"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/web"
"github.com/wdvxdr1123/ZeroBot/message"
)
@@ -303,7 +305,10 @@ func liveCard2msg(card bz.RoomCard) (msg []message.Segment) {
// videoCard2msg 视频卡片转消息
func videoCard2msg(card bz.Card) (msg []message.Segment, err error) {
var mCard bz.MemberCard
var (
mCard bz.MemberCard
onlineTotal bz.OnlineTotal
)
msg = make([]message.Segment, 0, 16)
mCard, err = bz.GetMemberCard(card.Owner.Mid)
msg = append(msg, message.Text("标题: ", card.Title, "\n"))
@@ -313,16 +318,25 @@ func videoCard2msg(card bz.Card) (msg []message.Segment, err error) {
}
} else {
if err != nil {
err = nil
msg = append(msg, message.Text("UP主: ", card.Owner.Name, "\n"))
} else {
msg = append(msg, message.Text("UP主: ", card.Owner.Name, " 粉丝: ", bz.HumanNum(mCard.Fans), "\n"))
}
}
msg = append(msg, message.Text("播放: ", bz.HumanNum(card.Stat.View), " 弹幕: ", bz.HumanNum(card.Stat.Danmaku)))
msg = append(msg, message.Image(card.Pic))
msg = append(msg, message.Text("\n点赞: ", bz.HumanNum(card.Stat.Like), " 投币: ", bz.HumanNum(card.Stat.Coin), "\n",
"收藏: ", bz.HumanNum(card.Stat.Favorite), " 分享: ", bz.HumanNum(card.Stat.Share), "\n",
data, err := web.GetData(fmt.Sprintf(bz.OnlineTotalURL, card.BvID, card.CID))
if err != nil {
return
}
err = json.Unmarshal(data, &onlineTotal)
if err != nil {
return
}
msg = append(msg, message.Text("👀播放: ", bz.HumanNum(card.Stat.View), " 💬弹幕: ", bz.HumanNum(card.Stat.Danmaku),
"\n👍点赞: ", bz.HumanNum(card.Stat.Like), " 💰投币: ", bz.HumanNum(card.Stat.Coin),
"\n📁收藏: ", bz.HumanNum(card.Stat.Favorite), " 🔗分享: ", bz.HumanNum(card.Stat.Share),
"\n📝简介: ", card.Desc,
"\n🏄 总共 ", onlineTotal.Data.Total, " 人在观看,", onlineTotal.Data.Count, " 人在网页端观看\n",
bz.VURL, card.BvID, "\n\n"))
return
}

View File

@@ -2,7 +2,8 @@
package kfccrazythursday
import (
"github.com/FloatTech/floatbox/binary"
"encoding/json"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
@@ -11,9 +12,15 @@ import (
)
const (
crazyURL = "http://api.jixs.cc/api/wenan-fkxqs/index.php"
crazyURL = "https://api.pearktrue.cn/api/kfc/"
)
type crazyResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Text string `json:"text"`
}
func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
@@ -26,6 +33,18 @@ func init() {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(binary.BytesToString(data)))
var resp crazyResponse
if err := json.Unmarshal(data, &resp); err != nil {
ctx.SendChain(message.Text("JSON解析失败: ", err))
return
}
if resp.Code != 200 {
ctx.SendChain(message.Text("API返回错误: ", resp.Msg))
return
}
ctx.SendChain(message.Text(resp.Text))
})
}

View File

@@ -156,7 +156,7 @@ func init() { // 插件主体
ctx.SendChain(message.Text("全员自闭结束~"))
})
// 禁言
engine.OnMessage(zero.NewPattern().Text("^禁言").At().Text("(\\d+)\\s*(.*)").AsRule(), zero.OnlyGroup, zero.AdminPermission).SetBlock(true).
engine.OnMessage(zero.NewPattern(nil).Text("^禁言").At().Text("(\\d+)\\s*(.*)").AsRule(), zero.OnlyGroup, zero.AdminPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
parsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
duration := math.Str2Int64(parsed[2].Text()[1])
@@ -650,7 +650,7 @@ func init() { // 插件主体
if rsp.RetCode == 0 {
ctx.SendChain(message.Text(option, "成功"))
} else {
ctx.SendChain(message.Text(option, "失败, 信息: ", rsp.Msg, "解释: ", rsp.Wording))
ctx.SendChain(message.Text(option, "失败, 信息: ", rsp.Message, "解释: ", rsp.Wording))
}
})
engine.OnCommand("精华列表", zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
@@ -699,7 +699,7 @@ func init() { // 插件主体
if rsp.RetCode == 0 {
ctx.SendChain(message.Text("取消成功"))
} else {
ctx.SendChain(message.Text("取消失败, 信息: ", rsp.Msg, "解释: ", rsp.Wording))
ctx.SendChain(message.Text("取消失败, 信息: ", rsp.Message, "解释: ", rsp.Wording))
}
})
}

View File

@@ -0,0 +1,301 @@
// Package minecraftobserver 通过mc服务器地址获取服务器状态信息并绘制图片发送到QQ群
package minecraftobserver
import (
"fmt"
"strings"
"time"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
zbpCtxExt "github.com/FloatTech/zbputils/ctxext"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
name = "minecraftobserver"
)
var (
// 注册插件
engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
// 默认不启动
DisableOnDefault: false,
Brief: "Minecraft服务器状态查询/订阅",
// 详细帮助
Help: "- mc服务器状态 [服务器IP/URI]\n" +
"- mc服务器添加订阅 [服务器IP/URI]\n" +
"- mc服务器取消订阅 [服务器IP/URI]\n" +
"- mc服务器订阅拉取 (需要插件定时任务配合使用,全局只需要设置一个)" +
"-----------------------\n" +
"使用job插件设置定时, 例:" +
"记录在\"@every 1m\"触发的指令\n" +
"(机器人回答:您的下一条指令将被记录,在@@every 1m时触发" +
"mc服务器订阅拉取",
// 插件数据存储路径
PrivateDataFolder: name,
}).ApplySingle(zbpCtxExt.DefaultSingle)
)
func init() {
// 状态查询
engine.OnRegex("^[mM][cC]服务器状态 (.+)$").SetBlock(true).Handle(func(ctx *zero.Ctx) {
// 关键词查找
addr := ctx.State["regex_matched"].([]string)[1]
resp, err := getMinecraftServerStatus(addr)
if err != nil {
ctx.Send(message.Text("服务器状态获取失败... 错误信息: ", err))
return
}
status := resp.genServerSubscribeSchema(addr, 0)
textMsg, iconBase64 := status.generateServerStatusMsg()
var msg message.Message
if iconBase64 != "" {
msg = append(msg, message.Image(iconBase64))
}
msg = append(msg, message.Text(textMsg))
if id := ctx.Send(msg); id.ID() == 0 {
// logrus.Errorln(logPrefix + "Send failed")
return
}
})
// 添加订阅
engine.OnRegex(`^[mM][cC]服务器添加订阅\s*(.+)$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
// 关键词查找
addr := ctx.State["regex_matched"].([]string)[1]
status, err := getMinecraftServerStatus(addr)
if err != nil {
ctx.Send(message.Text("服务器信息初始化失败,请检查服务器是否可用!\n错误信息: ", err))
return
}
targetID, targetType := warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID)
err = dbInstance.newSubscribe(addr, targetID, targetType)
if err != nil {
ctx.Send(message.Text("订阅添加失败... 错误信息: ", err))
return
}
// 插入数据库(首条,需要更新状态)
err = dbInstance.updateServerStatus(status.genServerSubscribeSchema(addr, 0))
if err != nil {
ctx.Send(message.Text("服务器状态更新失败... 错误信息: ", err))
return
}
if sid := ctx.Send(message.Text(fmt.Sprintf("服务器 %s 订阅添加成功", addr))); sid.ID() == 0 {
// logrus.Errorln(logPrefix + "Send failed")
return
}
// 成功后立即发送一次状态
textMsg, iconBase64 := status.genServerSubscribeSchema(addr, 0).generateServerStatusMsg()
var msg message.Message
if iconBase64 != "" {
msg = append(msg, message.Image(iconBase64))
}
msg = append(msg, message.Text(textMsg))
if id := ctx.Send(msg); id.ID() == 0 {
// logrus.Errorln(logPrefix + "Send failed")
return
}
})
// 删除
engine.OnRegex(`^[mM][cC]服务器取消订阅\s*(.+)$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
addr := ctx.State["regex_matched"].([]string)[1]
// 通过群组id和服务器地址获取服务器状态
targetID, targetType := warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID)
err := dbInstance.deleteSubscribe(addr, targetID, targetType)
if err != nil {
ctx.Send(message.Text("取消订阅失败...", fmt.Sprintf("错误信息: %v", err)))
return
}
ctx.Send(message.Text("取消订阅成功"))
})
// 查看当前渠道的所有订阅
engine.OnRegex(`^[mM][cC]服务器订阅列表$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
subList, err := dbInstance.getSubscribesByTarget(warpTargetIDAndType(ctx.Event.GroupID, ctx.Event.UserID))
if err != nil {
ctx.Send(message.Text("获取订阅列表失败... 错误信息: ", err))
return
}
if len(subList) == 0 {
ctx.Send(message.Text("当前没有订阅哦"))
return
}
stringBuilder := strings.Builder{}
stringBuilder.WriteString("[订阅列表]\n")
for _, v := range subList {
stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", v.ServerAddr))
}
if sid := ctx.Send(message.Text(stringBuilder.String())); sid.ID() == 0 {
// logrus.Errorln(logPrefix + "Send failed")
return
}
})
// 查看全局订阅情况(仅限管理员私聊可用)
engine.OnRegex(`^[mM][cC]服务器全局订阅列表$`, zero.OnlyPrivate, zero.SuperUserPermission, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
subList, err := dbInstance.getAllSubscribes()
if err != nil {
ctx.Send(message.Text("获取全局订阅列表失败... 错误信息: ", err))
return
}
if len(subList) == 0 {
ctx.Send(message.Text("当前一个订阅都没有哦"))
return
}
userID := ctx.Event.UserID
userName := ctx.CardOrNickName(userID)
msg := make(message.Message, 0)
// 按照群组or用户分组来定
groupSubMap := make(map[int64][]serverSubscribe)
userSubMap := make(map[int64][]serverSubscribe)
for _, v := range subList {
switch v.TargetType {
case targetTypeGroup:
groupSubMap[v.TargetID] = append(groupSubMap[v.TargetID], v)
case targetTypeUser:
userSubMap[v.TargetID] = append(userSubMap[v.TargetID], v)
default:
}
}
// 群
for k, v := range groupSubMap {
stringBuilder := strings.Builder{}
stringBuilder.WriteString(fmt.Sprintf("[群 %d]存在以下订阅:\n", k))
for _, sub := range v {
stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", sub.ServerAddr))
}
msg = append(msg, message.CustomNode(userName, userID, stringBuilder.String()))
}
// 个人
for k, v := range userSubMap {
stringBuilder := strings.Builder{}
stringBuilder.WriteString(fmt.Sprintf("[用户 %d]存在以下订阅:\n", k))
for _, sub := range v {
stringBuilder.WriteString(fmt.Sprintf("服务器地址: %s\n", sub.ServerAddr))
}
msg = append(msg, message.CustomNode(userName, userID, stringBuilder.String()))
}
// 合并发送
ctx.SendPrivateForwardMessage(ctx.Event.UserID, msg)
})
// 状态变更通知,全局触发,逐个服务器检查,检查到变更则逐个发送通知
engine.OnRegex(`^[mM][cC]服务器订阅拉取$`, getDB).SetBlock(true).Handle(func(ctx *zero.Ctx) {
serverList, err := dbInstance.getAllSubscribes()
if err != nil {
su := zero.BotConfig.SuperUsers[0]
// 如果订阅列表获取失败,通知管理员
ctx.SendPrivateMessage(su, message.Text(logPrefix, "获取订阅列表失败..."))
return
}
// logrus.Debugln(logPrefix+"global get ", len(serverList), " subscribe(s)")
serverMap := make(map[string][]serverSubscribe)
for _, v := range serverList {
serverMap[v.ServerAddr] = append(serverMap[v.ServerAddr], v)
}
changedCount := 0
for subAddr, oneServerSubList := range serverMap {
// 查询当前存储的状态
storedStatus, sErr := dbInstance.getServerStatus(subAddr)
if sErr != nil {
// logrus.Errorln(logPrefix+fmt.Sprintf("getServerStatus ServerAddr(%s) error: ", subAddr), sErr)
continue
}
isChanged, changedNotifyMsg, sErr := singleServerScan(storedStatus)
if sErr != nil {
// logrus.Errorln(logPrefix+"singleServerScan error: ", sErr)
continue
}
if !isChanged {
continue
}
changedCount++
// 发送变化信息
for _, subInfo := range oneServerSubList {
time.Sleep(100 * time.Millisecond)
if subInfo.TargetType == targetTypeUser {
ctx.SendPrivateMessage(subInfo.TargetID, changedNotifyMsg)
} else if subInfo.TargetType == targetTypeGroup {
m, ok := control.Lookup(name)
if !ok {
continue
}
if !m.IsEnabledIn(subInfo.TargetID) {
continue
}
ctx.SendGroupMessage(subInfo.TargetID, changedNotifyMsg)
}
}
}
})
}
// singleServerScan 单个服务器状态扫描
func singleServerScan(oldSubStatus *serverStatus) (changed bool, notifyMsg message.Message, err error) {
notifyMsg = make(message.Message, 0)
newSubStatus := &serverStatus{}
// 获取服务器状态 & 检查是否需要更新
rawServerStatus, err := getMinecraftServerStatus(oldSubStatus.ServerAddr)
if err != nil {
// logrus.Warnln(logPrefix+"getMinecraftServerStatus error: ", err)
err = nil
// 计数器没有超限,增加计数器并跳过
if cnt, ts := addPingServerUnreachableCounter(oldSubStatus.ServerAddr, time.Now()); cnt < pingServerUnreachableCounterThreshold &&
time.Since(ts) < pingServerUnreachableCounterTimeThreshold {
// logrus.Warnln(logPrefix+"server ", oldSubStatus.ServerAddr, " unreachable, counter: ", cnt, " ts:", ts)
return
}
// 不可达计数器已经超限,则更新服务器状态
// 深拷贝设置PingDelay为不可达
newSubStatus = oldSubStatus.deepCopy()
newSubStatus.PingDelay = pingDelayUnreachable
} else {
newSubStatus = rawServerStatus.genServerSubscribeSchema(oldSubStatus.ServerAddr, oldSubStatus.ID)
}
if newSubStatus == nil {
// logrus.Errorln(logPrefix + "newSubStatus is nil")
return
}
// 检查是否有订阅信息变化
if oldSubStatus.isServerStatusSpecChanged(newSubStatus) {
// logrus.Warnf(logPrefix+"server subscribe spec changed: (%+v) -> (%+v)", oldSubStatus, newSubStatus)
changed = true
// 更新数据库
err = dbInstance.updateServerStatus(newSubStatus)
if err != nil {
// logrus.Errorln(logPrefix+"updateServerSubscribeStatus error: ", err)
return
}
// 纯文本信息
notifyMsg = append(notifyMsg, message.Text(formatSubStatusChangeText(oldSubStatus, newSubStatus)))
// 如果有图标变更
if oldSubStatus.FaviconMD5 != newSubStatus.FaviconMD5 {
// 有图标变更
notifyMsg = append(notifyMsg, message.Text("\n-----[图标变更]-----\n"))
// 旧图标
notifyMsg = append(notifyMsg, message.Text("[旧]\n"))
if oldSubStatus.FaviconRaw != "" {
notifyMsg = append(notifyMsg, message.Image(oldSubStatus.FaviconRaw.toBase64String()))
} else {
notifyMsg = append(notifyMsg, message.Text("(空)\n"))
}
// 新图标
notifyMsg = append(notifyMsg, message.Text("[新]\n"))
if newSubStatus.FaviconRaw != "" {
notifyMsg = append(notifyMsg, message.Image(newSubStatus.FaviconRaw.toBase64String()))
} else {
notifyMsg = append(notifyMsg, message.Text("(空)\n"))
}
}
notifyMsg = append(notifyMsg, message.Text("\n-------最新状态-------\n"))
// 服务状态
textMsg, iconBase64 := newSubStatus.generateServerStatusMsg()
if iconBase64 != "" {
notifyMsg = append(notifyMsg, message.Image(iconBase64))
}
notifyMsg = append(notifyMsg, message.Text(textMsg))
}
// 逻辑到达这里,说明状态已经变更 or 无变更且服务器可达,重置不可达计数器
resetPingServerUnreachableCounter(oldSubStatus.ServerAddr)
return
}

View File

@@ -0,0 +1,127 @@
package minecraftobserver
import (
"fmt"
"github.com/wdvxdr1123/ZeroBot/message"
"testing"
)
func Test_singleServerScan(t *testing.T) {
initErr := initializeDB("data/minecraftobserver/" + dbPath)
if initErr != nil {
t.Fatalf("initializeDB() error = %v", initErr)
}
if dbInstance == nil {
t.Fatalf("initializeDB() got = %v, want not nil", dbInstance)
}
t.Run("状态变更", func(t *testing.T) {
cleanTestData(t)
newSS1 := &serverStatus{
ServerAddr: "cn.nekoland.top",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "",
}
err := dbInstance.updateServerStatus(newSS1)
if err != nil {
t.Fatalf("upsertServerStatus() error = %v", err)
}
err = dbInstance.newSubscribe("cn.nekoland.top", 123456, 1)
if err != nil {
t.Fatalf("getServerSubscribeByTargetGroupAndAddr() error = %v", err)
}
changed, msg, err := singleServerScan(newSS1)
if err != nil {
t.Fatalf("singleServerScan() error = %v", err)
}
if !changed {
t.Fatalf("singleServerScan() got = %v, want true", changed)
}
if len(msg) == 0 {
t.Fatalf("singleServerScan() got = %v, want not empty", msg)
}
fmt.Printf("msg: %v\n", msg)
})
t.Run("可达 -> 不可达", func(t *testing.T) {
cleanTestData(t)
newSS1 := &serverStatus{
ServerAddr: "dx.123213213123123.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "",
PingDelay: 123,
}
err := dbInstance.updateServerStatus(newSS1)
if err != nil {
t.Fatalf("upsertServerStatus() error = %v", err)
}
err = dbInstance.newSubscribe("dx.123213213123123.net", 123456, 1)
if err != nil {
t.Fatalf("getServerSubscribeByTargetGroupAndAddr() error = %v", err)
}
var msg message.Message
changed, _, err := singleServerScan(newSS1)
if err != nil {
t.Fatalf("singleServerScan() error = %v", err)
}
if changed {
t.Fatalf("singleServerScan() got = %v, want false", changed)
}
// 第二次
changed, _, err = singleServerScan(newSS1)
if err != nil {
t.Fatalf("singleServerScan() error = %v", err)
}
if changed {
t.Fatalf("singleServerScan() got = %v, want false", changed)
}
// 第三次
changed, msg, err = singleServerScan(newSS1)
if err != nil {
t.Fatalf("singleServerScan() error = %v", err)
}
if !changed {
t.Fatalf("singleServerScan() got = %v, want true", changed)
}
if len(msg) == 0 {
t.Fatalf("singleServerScan() got = %v, want not empty", msg)
}
fmt.Printf("msg: %v\n", msg)
})
t.Run("不可达 -> 可达", func(t *testing.T) {
cleanTestData(t)
newSS1 := &serverStatus{
ServerAddr: "cn.nekoland.top",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "",
PingDelay: pingDelayUnreachable,
}
err := dbInstance.updateServerStatus(newSS1)
if err != nil {
t.Fatalf("upsertServerStatus() error = %v", err)
}
err = dbInstance.newSubscribe("cn.nekoland.top", 123456, 1)
if err != nil {
t.Fatalf("newSubscribe() error = %v", err)
}
changed, msg, err := singleServerScan(newSS1)
if err != nil {
t.Fatalf("singleServerScan() error = %v", err)
}
if !changed {
t.Fatalf("singleServerScan() got = %v, want true", changed)
}
if len(msg) == 0 {
t.Fatalf("singleServerScan() got = %v, want not empty", msg)
}
fmt.Printf("msg: %v\n", msg)
})
}

View File

@@ -0,0 +1,253 @@
package minecraftobserver
import (
"crypto/md5"
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/Tnze/go-mc/chat"
"github.com/google/uuid"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
)
// ====================
// DB Schema
// serverStatus 服务器状态
type serverStatus struct {
// ID 主键
ID int64 `json:"id" gorm:"column:id;primary_key:pk_id;auto_increment;default:0"`
// 服务器地址
ServerAddr string `json:"server_addr" gorm:"column:server_addr;default:'';unique_index:udx_server_addr"`
// 服务器描述
Description string `json:"description" gorm:"column:description;default:null;type:CLOB"`
// 在线玩家
Players string `json:"players" gorm:"column:players;default:''"`
// 版本
Version string `json:"version" gorm:"column:version;default:''"`
// FaviconMD5 Favicon MD5
FaviconMD5 string `json:"favicon_md5" gorm:"column:favicon_md5;default:''"`
// FaviconRaw 原始数据
FaviconRaw icon `json:"favicon_raw" gorm:"column:favicon_raw;default:null;type:CLOB"`
// 延迟,不可达时为-1
PingDelay int64 `json:"ping_delay" gorm:"column:ping_delay;default:-1"`
// 更新时间
LastUpdate int64 `json:"last_update" gorm:"column:last_update;default:0"`
}
// serverSubscribe 订阅信息
type serverSubscribe struct {
// ID 主键
ID int64 `json:"id" gorm:"column:id;primary_key:pk_id;auto_increment;default:0"`
// 服务器地址
ServerAddr string `json:"server_addr" gorm:"column:server_addr;default:'';unique_index:udx_ait"`
// 推送目标id
TargetID int64 `json:"target_id" gorm:"column:target_id;default:0;unique_index:udx_ait"`
// 类型 1群组 2个人
TargetType int64 `json:"target_type" gorm:"column:target_type;default:0;unique_index:udx_ait"`
// 更新时间
LastUpdate int64 `json:"last_update" gorm:"column:last_update;default:0"`
}
const (
// pingDelayUnreachable 不可达
pingDelayUnreachable = -1
)
// isServerStatusSpecChanged 检查是否有状态变化
func (ss *serverStatus) isServerStatusSpecChanged(newStatus *serverStatus) (res bool) {
res = false
if ss == nil || newStatus == nil {
res = false
return
}
// 描述变化、版本变化、Favicon变化
if ss.Description != newStatus.Description || ss.Version != newStatus.Version || ss.FaviconMD5 != newStatus.FaviconMD5 {
res = true
return
}
// 状态由不可达变为可达 or 反之
if (ss.PingDelay == pingDelayUnreachable && newStatus.PingDelay != pingDelayUnreachable) ||
(ss.PingDelay != pingDelayUnreachable && newStatus.PingDelay == pingDelayUnreachable) {
res = true
return
}
return
}
// deepCopy 深拷贝
func (ss *serverStatus) deepCopy() (dst *serverStatus) {
if ss == nil {
return
}
dst = &serverStatus{}
*dst = *ss
return
}
// generateServerStatusMsg 生成服务器状态消息
func (ss *serverStatus) generateServerStatusMsg() (msg string, iconBase64 string) {
var msgBuilder strings.Builder
if ss == nil {
return
}
msgBuilder.WriteString(ss.Description)
msgBuilder.WriteString("\n")
msgBuilder.WriteString("服务器地址:")
msgBuilder.WriteString(ss.ServerAddr)
msgBuilder.WriteString("\n")
// 版本
msgBuilder.WriteString("版本:")
msgBuilder.WriteString(ss.Version)
msgBuilder.WriteString("\n")
// Ping
if ss.PingDelay < 0 {
msgBuilder.WriteString("Ping延迟超时\n")
} else {
msgBuilder.WriteString("Ping延迟")
msgBuilder.WriteString(fmt.Sprintf("%d 毫秒\n", ss.PingDelay))
msgBuilder.WriteString("在线人数:")
msgBuilder.WriteString(ss.Players)
}
// 图标
if ss.FaviconRaw != "" && ss.FaviconRaw.checkPNG() {
iconBase64 = ss.FaviconRaw.toBase64String()
}
msg = msgBuilder.String()
return
}
// DB Schema End
// ====================
// Ping & List Response DTO
// serverPingAndListResp 服务器状态数据传输对象 From mc server response
type serverPingAndListResp struct {
Description chat.Message
Players struct {
Max int
Online int
Sample []struct {
ID uuid.UUID
Name string
}
}
Version struct {
Name string
Protocol int
}
Favicon icon
Delay time.Duration
}
// icon should be a PNG image that is Base64 encoded
// (without newlines: \n, new lines no longer work since 1.13)
// and prepended with "data:image/png;base64,".
type icon string
// func (i icon) toImage() (icon image.Image, err error) {
// const prefix = "data:image/png;base64,"
// if !strings.HasPrefix(string(i), prefix) {
// return nil, errors.Errorf("server icon should prepended with %s", prefix)
// }
// base64png := strings.TrimPrefix(string(i), prefix)
// r := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64png))
// icon, err = png.Decode(r)
// return
//}
// checkPNG 检查是否为PNG
func (i icon) checkPNG() bool {
const prefix = "data:image/png;base64,"
return strings.HasPrefix(string(i), prefix)
}
// toBase64String 转换为base64字符串
func (i icon) toBase64String() string {
return "base64://" + strings.TrimPrefix(string(i), "data:image/png;base64,")
}
// genServerSubscribeSchema 将DTO转换为DB Schema
func (dto *serverPingAndListResp) genServerSubscribeSchema(addr string, id int64) *serverStatus {
if dto == nil {
return nil
}
faviconMD5 := md5.Sum(helper.StringToBytes(string(dto.Favicon)))
return &serverStatus{
ID: id,
ServerAddr: addr,
Description: dto.Description.ClearString(),
Version: dto.Version.Name,
Players: fmt.Sprintf("%d/%d", dto.Players.Online, dto.Players.Max),
FaviconMD5: hex.EncodeToString(faviconMD5[:]),
FaviconRaw: dto.Favicon,
PingDelay: dto.Delay.Milliseconds(),
LastUpdate: time.Now().Unix(),
}
}
// Ping & List Response DTO End
// ====================
// ====================
// Biz Model
const (
logPrefix = "[minecraft observer] "
)
// warpTargetIDAndType 转换消息信息到订阅的目标ID和类型
func warpTargetIDAndType(groupID, userID int64) (int64, int64) {
// 订阅
var targetID int64
var targetType int64
if groupID == 0 {
targetType = targetTypeUser
targetID = userID
} else {
targetType = targetTypeGroup
targetID = groupID
}
return targetID, targetType
}
// formatSubStatusChangeText 格式化状态变更文本
func formatSubStatusChangeText(oldStatus, newStatus *serverStatus) string {
var msgBuilder strings.Builder
if oldStatus == nil || newStatus == nil {
return ""
}
// 变更通知
msgBuilder.WriteString("[Minecraft服务器状态变更通知]\n")
// 地址
msgBuilder.WriteString(fmt.Sprintf("服务器地址: %v\n", oldStatus.ServerAddr))
// 描述
if oldStatus.Description != newStatus.Description {
msgBuilder.WriteString("\n-----[描述变更]-----\n")
msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v\n", oldStatus.Description))
msgBuilder.WriteString(fmt.Sprintf("[新]\n%v\n", newStatus.Description))
}
// 版本
if oldStatus.Version != newStatus.Version {
msgBuilder.WriteString("\n-----[版本变更]-----\n")
msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v\n", oldStatus.Version))
msgBuilder.WriteString(fmt.Sprintf("[新]\n%v\n", newStatus.Version))
}
// 状态由不可达变为可达,反之
if oldStatus.PingDelay == pingDelayUnreachable && newStatus.PingDelay != pingDelayUnreachable {
msgBuilder.WriteString("\n-----[Ping延迟]-----\n")
msgBuilder.WriteString("[旧]\n超时\n")
msgBuilder.WriteString(fmt.Sprintf("[新]\n%v毫秒\n", newStatus.PingDelay))
}
if oldStatus.PingDelay != pingDelayUnreachable && newStatus.PingDelay == pingDelayUnreachable {
msgBuilder.WriteString("\n-----[Ping延迟]-----\n")
msgBuilder.WriteString(fmt.Sprintf("[旧]\n%v毫秒\n", oldStatus.PingDelay))
msgBuilder.WriteString("[新]\n超时\n")
}
return msgBuilder.String()
}
// Biz Model End
// ====================

View File

@@ -0,0 +1,63 @@
package minecraftobserver
import (
"encoding/json"
"time"
"github.com/RomiChan/syncx"
"github.com/Tnze/go-mc/bot"
)
var (
// pingServerUnreachableCounter Ping服务器不可达计数器防止bot本体网络抖动导致误报
pingServerUnreachableCounter = syncx.Map[string, pingServerUnreachableCounterDef]{}
// 计数器阈值
pingServerUnreachableCounterThreshold = int64(3)
// 时间阈值
pingServerUnreachableCounterTimeThreshold = time.Minute * 30
)
type pingServerUnreachableCounterDef struct {
count int64
firstUnreachableTime time.Time
}
func addPingServerUnreachableCounter(addr string, ts time.Time) (int64, time.Time) {
key := addr
get, ok := pingServerUnreachableCounter.Load(key)
if !ok {
pingServerUnreachableCounter.Store(key, pingServerUnreachableCounterDef{
count: 1,
firstUnreachableTime: ts,
})
return 1, ts
}
// 存在则更新,时间戳不变
pingServerUnreachableCounter.Store(key, pingServerUnreachableCounterDef{
count: get.count + 1,
firstUnreachableTime: get.firstUnreachableTime,
})
return get.count + 1, get.firstUnreachableTime
}
func resetPingServerUnreachableCounter(addr string) {
key := addr
pingServerUnreachableCounter.Delete(key)
}
// getMinecraftServerStatus 获取Minecraft服务器状态
func getMinecraftServerStatus(addr string) (*serverPingAndListResp, error) {
var s serverPingAndListResp
resp, delay, err := bot.PingAndListTimeout(addr, time.Second*5)
if err != nil {
// logrus.Errorln(logPrefix+"PingAndList error: ", err)
return nil, err
}
err = json.Unmarshal(resp, &s)
if err != nil {
// logrus.Errorln(logPrefix+"Parse json response fail: ", err)
return nil, err
}
s.Delay = delay
return &s, nil
}

View File

@@ -0,0 +1,27 @@
package minecraftobserver
import (
"fmt"
"testing"
)
func Test_PingListInfo(t *testing.T) {
t.Run("normal", func(t *testing.T) {
resp, err := getMinecraftServerStatus("cn.nekoland.top")
if err != nil {
t.Fatalf("getMinecraftServerStatus() error = %v", err)
}
msg, iconBase64 := resp.genServerSubscribeSchema("cn.nekoland.top", 123456).generateServerStatusMsg()
fmt.Printf("msg: %v\n", msg)
fmt.Printf("iconBase64: %v\n", iconBase64)
})
t.Run("不可达", func(t *testing.T) {
ss, err := getMinecraftServerStatus("dx.123213213123123.net")
if err == nil {
t.Fatalf("getMinecraftServerStatus() error = %v", err)
}
if ss != nil {
t.Fatalf("getMinecraftServerStatus() got = %v, want nil", ss)
}
})
}

View File

@@ -0,0 +1,220 @@
package minecraftobserver
import (
"errors"
"os"
"sync"
"time"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/jinzhu/gorm"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
dbPath = "minecraft_observer"
targetTypeGroup = 1
targetTypeUser = 2
)
var (
// 数据库连接失败
errDBConn = errors.New("数据库连接失败")
// 参数错误
errParam = errors.New("参数错误")
)
type db struct {
sdb *gorm.DB
statusLock sync.RWMutex
subscribeLock sync.RWMutex
}
// initializeDB 初始化数据库
func initializeDB(dbpath string) error {
if _, err := os.Stat(dbpath); err != nil || os.IsNotExist(err) {
// 生成文件
f, err := os.Create(dbpath)
if err != nil {
return err
}
defer f.Close()
}
gdb, err := gorm.Open("sqlite3", dbpath)
if err != nil {
// logrus.Errorln(logPrefix+"initializeDB ERROR: ", err)
return err
}
gdb.AutoMigrate(&serverStatus{}, &serverSubscribe{})
dbInstance = &db{
sdb: gdb,
statusLock: sync.RWMutex{},
subscribeLock: sync.RWMutex{},
}
return nil
}
var (
// dbInstance 数据库实例
dbInstance *db
// 开启并检查数据库链接
getDB = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
var err = initializeDB(engine.DataFolder() + dbPath)
if err != nil {
// logrus.Errorln(logPrefix+"initializeDB ERROR: ", err)
ctx.SendChain(message.Text("[mc-ob] ERROR: ", err))
return false
}
return true
})
)
// 通过群组id和服务器地址获取状态
func (d *db) getServerStatus(addr string) (*serverStatus, error) {
if d == nil {
return nil, errDBConn
}
if addr == "" {
return nil, errParam
}
var ss serverStatus
if err := d.sdb.Model(&ss).Where("server_addr = ?", addr).First(&ss).Error; err != nil {
// logrus.Errorln(logPrefix+"getServerStatus ERROR: ", err)
return nil, err
}
return &ss, nil
}
// 更新服务器状态
func (d *db) updateServerStatus(ss *serverStatus) (err error) {
if d == nil {
return errDBConn
}
d.statusLock.Lock()
defer d.statusLock.Unlock()
if ss == nil || ss.ServerAddr == "" {
return errParam
}
ss.LastUpdate = time.Now().Unix()
ss2 := ss.deepCopy()
if err = d.sdb.Where(&serverStatus{ServerAddr: ss.ServerAddr}).Assign(ss2).FirstOrCreate(ss).Debug().Error; err != nil {
// logrus.Errorln(logPrefix, fmt.Sprintf("updateServerStatus %v ERROR: %v", ss, err))
return
}
return
}
func (d *db) delServerStatus(addr string) (err error) {
if d == nil {
return errDBConn
}
if addr == "" {
return errParam
}
d.statusLock.Lock()
defer d.statusLock.Unlock()
if err = d.sdb.Where("server_addr = ?", addr).Delete(&serverStatus{}).Error; err != nil {
// logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
return
}
return
}
// 新增订阅
func (d *db) newSubscribe(addr string, targetID, targetType int64) (err error) {
if d == nil {
return errDBConn
}
if targetID == 0 || (targetType != 1 && targetType != 2) {
// logrus.Errorln(logPrefix+"newSubscribe ERROR: 参数错误 ", targetID, " ", targetType)
return errParam
}
d.subscribeLock.Lock()
defer d.subscribeLock.Unlock()
// 如果已经存在,需要报错
existedRec := &serverSubscribe{}
err = d.sdb.Model(&serverSubscribe{}).Where("server_addr = ? and target_id = ? and target_type = ?", addr, targetID, targetType).First(existedRec).Error
if err != nil && !gorm.IsRecordNotFoundError(err) {
// logrus.Errorln(logPrefix+"newSubscribe ERROR: ", err)
return
}
if existedRec.ID != 0 {
return errors.New("已经存在的订阅")
}
ss := &serverSubscribe{
ServerAddr: addr,
TargetID: targetID,
TargetType: targetType,
LastUpdate: time.Now().Unix(),
}
if err = d.sdb.Model(&ss).Create(ss).Error; err != nil {
// logrus.Errorln(logPrefix+"newSubscribe ERROR: ", err)
return
}
return
}
// 删除订阅
func (d *db) deleteSubscribe(addr string, targetID int64, targetType int64) (err error) {
if d == nil {
return errDBConn
}
if addr == "" || targetID == 0 || targetType == 0 {
return errParam
}
d.subscribeLock.Lock()
defer d.subscribeLock.Unlock()
// 检查是否存在
if err = d.sdb.Model(&serverSubscribe{}).Where("server_addr = ? and target_id = ? and target_type = ?", addr, targetID, targetType).First(&serverSubscribe{}).Error; err != nil {
if gorm.IsRecordNotFoundError(err) {
return errors.New("未找到订阅")
}
// logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
return
}
if err = d.sdb.Where("server_addr = ? and target_id = ? and target_type = ?", addr, targetID, targetType).Delete(&serverSubscribe{}).Error; err != nil {
// logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
return
}
// 扫描是否还有订阅,如果没有则删除服务器状态
var cnt int
err = d.sdb.Model(&serverSubscribe{}).Where("server_addr = ?", addr).Count(&cnt).Error
if err != nil {
// logrus.Errorln(logPrefix+"deleteSubscribe ERROR: ", err)
return
}
if cnt == 0 {
_ = d.delServerStatus(addr)
}
return
}
// 获取所有订阅
func (d *db) getAllSubscribes() (subs []serverSubscribe, err error) {
if d == nil {
return nil, errDBConn
}
subs = []serverSubscribe{}
if err = d.sdb.Find(&subs).Error; err != nil {
// logrus.Errorln(logPrefix+"getAllSubscribes ERROR: ", err)
return
}
return
}
// 获取渠道对应的订阅列表
func (d *db) getSubscribesByTarget(targetID, targetType int64) (subs []serverSubscribe, err error) {
if d == nil {
return nil, errDBConn
}
subs = []serverSubscribe{}
if err = d.sdb.Model(&serverSubscribe{}).Where("target_id = ? and target_type = ?", targetID, targetType).Find(&subs).Error; err != nil {
// logrus.Errorln(logPrefix+"getSubscribesByTarget ERROR: ", err)
return
}
return
}

View File

@@ -0,0 +1,317 @@
package minecraftobserver
import (
"errors"
"fmt"
"github.com/jinzhu/gorm"
"testing"
)
func cleanTestData(t *testing.T) {
err := dbInstance.sdb.Delete(&serverStatus{}).Where("id > 0").Error
if err != nil {
t.Fatalf("cleanTestData() error = %v", err)
}
err = dbInstance.sdb.Delete(&serverSubscribe{}).Where("id > 0").Error
if err != nil {
t.Fatalf("cleanTestData() error = %v", err)
}
}
func Test_DAO(t *testing.T) {
initErr := initializeDB("data/minecraftobserver/" + dbPath)
if initErr != nil {
t.Fatalf("initializeDB() error = %v", initErr)
}
if dbInstance == nil {
t.Fatalf("initializeDB() got = %v, want not nil", dbInstance)
}
t.Run("insert", func(t *testing.T) {
cleanTestData(t)
newSS1 := &serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
}
newSS2 := &serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.8",
FaviconMD5: "1234567",
}
err := dbInstance.updateServerStatus(newSS1)
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
err = dbInstance.updateServerStatus(newSS2)
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
// check insert
queryResult, err := dbInstance.getServerStatus("dx.zhaomc.net")
if err != nil {
t.Fatalf("getServerStatus() error = %v", err)
}
if queryResult == nil {
t.Fatalf("getServerStatus() got = %v, want not nil", queryResult)
}
if queryResult.Version != "1.16.8" {
t.Fatalf("getServerStatus() got = %v, want 1.16.8", queryResult.Version)
}
err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeUser)
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
// check insert
res, err := dbInstance.getAllSubscribes()
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
if len(res) != 2 {
t.Fatalf("getAllServer() got = %v, want 2", len(res))
}
// 检查是否符合预期
if res[0].ServerAddr != "dx.zhaomc.net" {
t.Fatalf("getAllServer() got = %v, want dx.zhaomc.net", res[0].ServerAddr)
}
if res[0].TargetType != targetTypeGroup {
t.Fatalf("getAllServer() got = %v, want %v", res[0].TargetType, targetTypeGroup)
}
if res[1].ServerAddr != "dx.zhaomc.net" {
t.Fatalf("getAllServer() got = %v, want dx.zhaomc.net", res[1].ServerAddr)
}
if res[1].TargetType != targetTypeUser {
t.Fatalf("getAllServer() got = %v, want %v", res[1].TargetType, targetTypeUser)
}
// 顺带验证一下 byTarget
res2, err := dbInstance.getSubscribesByTarget(123456, targetTypeGroup)
if err != nil {
t.Fatalf("getSubscribesByTarget() error = %v", err)
}
if len(res2) != 1 {
t.Fatalf("getSubscribesByTarget() got = %v, want 1", len(res2))
}
})
// 重复添加订阅
t.Run("insert dup", func(t *testing.T) {
cleanTestData(t)
newSS := &serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
}
err := dbInstance.updateServerStatus(newSS)
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err == nil {
t.Fatalf("getAllServer() error = %v", err)
}
fmt.Printf("insert dup error: %+v", err)
})
t.Run("update", func(t *testing.T) {
cleanTestData(t)
newSS := &serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
}
err := dbInstance.updateServerStatus(newSS)
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
err = dbInstance.updateServerStatus(&serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "更新测试",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
})
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
// check update
queryResult2, err := dbInstance.getServerStatus("dx.zhaomc.net")
if err != nil {
t.Errorf("getAllServer() error = %v", err)
}
if queryResult2.Description != "更新测试" {
t.Errorf("getAllServer() got = %v, want 更新测试", queryResult2.Description)
}
})
t.Run("delete status", func(t *testing.T) {
cleanTestData(t)
newSS := &serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
}
err := dbInstance.updateServerStatus(newSS)
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
// check insert
queryResult, err := dbInstance.getServerStatus("dx.zhaomc.net")
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
if queryResult == nil {
t.Fatalf("getAllServer() got = %v, want not nil", queryResult)
}
err = dbInstance.delServerStatus("dx.zhaomc.net")
if err != nil {
t.Fatalf("deleteServerStatus() error = %v", err)
}
// check delete
_, err = dbInstance.getServerStatus("dx.zhaomc.net")
if !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("getAllServer() error = %v", err)
}
})
// 删除订阅
t.Run("delete subscribe", func(t *testing.T) {
cleanTestData(t)
newSS := &serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
}
err := dbInstance.updateServerStatus(newSS)
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
err = dbInstance.deleteSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("deleteSubscribe() error = %v", err)
}
// check delete
_, err = dbInstance.getServerStatus("dx.zhaomc.net")
if !errors.Is(err, gorm.ErrRecordNotFound) {
t.Fatalf("getAllServer() error = %v", err)
}
})
// 重复删除订阅
t.Run("delete subscribe dup", func(t *testing.T) {
cleanTestData(t)
err := dbInstance.updateServerStatus(&serverStatus{
ServerAddr: "dx.zhaomc.net",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
})
if err != nil {
t.Errorf("upsertServerStatus() error = %v", err)
}
err = dbInstance.newSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("newSubscribe() error = %v", err)
}
err = dbInstance.newSubscribe("dx.zhaomc.net123", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("newSubscribe() error = %v", err)
}
err = dbInstance.updateServerStatus(&serverStatus{
ServerAddr: "dx.zhaomc.net123",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
})
if err != nil {
t.Fatalf("updateServerStatus() error = %v", err)
}
err = dbInstance.newSubscribe("dx.zhaomc.net4567", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("newSubscribe() error = %v", err)
}
err = dbInstance.updateServerStatus(&serverStatus{
ServerAddr: "dx.zhaomc.net4567",
Description: "测试服务器",
Players: "1/20",
Version: "1.16.5",
FaviconMD5: "1234567",
})
if err != nil {
t.Fatalf("updateServerStatus() error = %v", err)
}
// 检查是不是3个
allSub, err := dbInstance.getAllSubscribes()
if err != nil {
t.Fatalf("getAllSubscribes() error = %v", err)
}
if len(allSub) != 3 {
t.Fatalf("getAllSubscribes() got = %v, want 3", len(allSub))
}
err = dbInstance.deleteSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err != nil {
t.Fatalf("deleteSubscribe() error = %v", err)
}
err = dbInstance.deleteSubscribe("dx.zhaomc.net", 123456, targetTypeGroup)
if err == nil {
t.Fatalf("deleteSubscribe() error = %v", err)
}
fmt.Println("delete dup error: ", err)
// 检查其他的没有被删
allSub, err = dbInstance.getAllSubscribes()
if err != nil {
t.Fatalf("getAllSubscribes() error = %v", err)
}
// 检查是否符合预期
if len(allSub) != 2 {
t.Fatalf("getAllSubscribes() got = %v, want 2", len(allSub))
}
// 状态
_, err = dbInstance.getServerStatus("dx.zhaomc.net")
if !gorm.IsRecordNotFoundError(err) {
t.Fatalf("getAllServer() error = %v", err)
}
status1, err := dbInstance.getServerStatus("dx.zhaomc.net123")
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
status2, err := dbInstance.getServerStatus("dx.zhaomc.net4567")
if err != nil {
t.Fatalf("getAllServer() error = %v", err)
}
if status1 == nil || status2 == nil {
t.Fatalf("getAllServer() want not nil")
}
})
}

435
plugin/movies/main.go Normal file
View File

@@ -0,0 +1,435 @@
// Package movies 电影查询
package movies
import (
"encoding/json"
"image"
"net/http"
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"time"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/web"
"github.com/FloatTech/gg"
"github.com/FloatTech/imgfactory"
"github.com/FloatTech/rendercard"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
apiURL = "https://m.maoyan.com/ajax/"
ua = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36"
)
var (
mu sync.RWMutex
todayPic = make([][]byte, 2)
lasttime time.Time
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "电影查询",
Help: "- 今日电影\n" +
"- 预售电影",
PrivateDataFolder: "movies",
})
)
func init() {
en.OnFullMatch("今日电影").SetBlock(true).Handle(func(ctx *zero.Ctx) {
if todayPic != nil && time.Since(lasttime) < 12*time.Hour {
ctx.SendChain(message.ImageBytes(todayPic[0]))
return
}
lasttime = time.Now()
movieComingList, err := getMovieList("今日电影")
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
if len(movieComingList) == 0 {
ctx.SendChain(message.Text("没有今日电影"))
return
}
pic, err := drawOnListPic(movieComingList)
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
todayPic[0] = pic
ctx.SendChain(message.ImageBytes(pic))
})
en.OnFullMatch("预售电影").SetBlock(true).Handle(func(ctx *zero.Ctx) {
if todayPic[1] != nil && time.Since(lasttime) < 12*time.Hour {
ctx.SendChain(message.ImageBytes(todayPic[1]))
return
}
lasttime = time.Now()
movieComingList, err := getMovieList("预售电影")
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
if len(movieComingList) == 0 {
ctx.SendChain(message.Text("没有预售电影"))
return
}
pic, err := drawComListPic(movieComingList)
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
todayPic[1] = pic
ctx.SendChain(message.ImageBytes(pic))
})
}
type movieInfo struct {
ID int64 `json:"id"` // 电影ID
Img string `json:"img"` // 海报
Nm string `json:"nm"` // 名称
Dir string `json:"dir"` // 导演
Star string `json:"star"` // 演员
OriLang string `json:"oriLang"` // 原语言
Cat string `json:"cat"` // 类型
Version string `json:"version"` // 电影格式
Rt string `json:"rt"` // 上映时间
ShowInfo string `json:"showInfo"` // 今日上映信息
ComingTitle string `json:"comingTitle"` // 预售信息
Sc float64 `json:"sc"` // 评分
Wish int64 `json:"wish"` // 观看人数
Watched int64 `json:"watched"` // 观看数
}
type movieOnList struct {
MovieList []movieInfo `json:"movieList"`
}
type comingList struct {
MovieList []movieInfo `json:"coming"`
}
type movieShow struct {
MovieInfo movieInfo `json:"detailMovie"`
}
type cardInfo struct {
Avatar image.Image
TopLeftText string
BottomLeftText []string
RightText string
Rank string
}
func getMovieList(mode string) (movieList []movieInfo, err error) {
var data []byte
if mode == "今日电影" {
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"movieOnInfoList", "", "GET", ua, nil)
if err != nil {
return
}
var parsed movieOnList
err = json.Unmarshal(data, &parsed)
if err != nil {
return
}
movieList = parsed.MovieList
} else {
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"comingList?token=", "", "GET", ua, nil)
if err != nil {
return
}
var parsed comingList
err = json.Unmarshal(data, &parsed)
if err != nil {
return
}
movieList = parsed.MovieList
}
if len(movieList) == 0 {
return
}
for i, info := range movieList {
movieID := strconv.FormatInt(info.ID, 10)
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"detailmovie?movieId="+movieID, "", "GET", ua, nil)
if err != nil {
return
}
var movieInfo movieShow
err = json.Unmarshal(data, &movieInfo)
if err != nil {
return
}
if mode != "今日电影" {
movieInfo.MovieInfo.ComingTitle = movieList[i].ComingTitle
}
movieList[i] = movieInfo.MovieInfo
}
// 整理数据,进行排序
sort.Slice(movieList, func(i, j int) bool {
if movieList[i].Sc != movieList[j].Sc {
return movieList[i].Sc > movieList[j].Sc
}
if mode == "今日电影" {
return movieList[i].Watched > movieList[j].Watched
}
return movieList[i].Wish > movieList[j].Wish
})
return movieList, nil
}
func drawOnListPic(lits []movieInfo) (data []byte, err error) {
rankinfo := make([]*cardInfo, len(lits))
wg := &sync.WaitGroup{}
wg.Add(len(lits))
for i := 0; i < len(lits); i++ {
go func(i int) {
info := lits[i]
defer wg.Done()
img, err := avatar(&info)
if err != nil {
return
}
movieType := "2D"
if info.Version != "" {
movieType = info.Version
}
watched := ""
switch {
case info.Watched > 100000000:
watched = strconv.FormatFloat(float64(info.Watched)/100000000, 'f', 2, 64) + "亿"
case info.Watched > 10000:
watched = strconv.FormatFloat(float64(info.Watched)/10000, 'f', 2, 64) + "万"
default:
watched = strconv.FormatInt(info.Watched, 10)
}
rankinfo[i] = &cardInfo{
TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")",
BottomLeftText: []string{
"导演:" + info.Dir,
"演员:" + info.Star,
"标签:" + info.Cat,
"语言: " + info.OriLang + " 类型: " + movieType,
"上映时间: " + info.Rt,
},
RightText: watched + "人已看",
Avatar: img,
Rank: strconv.FormatFloat(info.Sc, 'f', 1, 64),
}
}(i)
}
wg.Wait()
fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
if err != nil {
return
}
img, err := drawRankingCard(fontbyte, "今日电影", rankinfo)
if err != nil {
return
}
data, err = imgfactory.ToBytes(img)
return
}
func drawComListPic(lits []movieInfo) (data []byte, err error) {
rankinfo := make([]*cardInfo, len(lits))
wg := &sync.WaitGroup{}
wg.Add(len(lits))
for i := 0; i < len(lits); i++ {
go func(i int) {
info := lits[i]
defer wg.Done()
img, err := avatar(&info)
if err != nil {
return
}
movieType := "2D"
if info.Version != "" {
movieType = info.Version
}
wish := ""
switch {
case info.Wish > 100000000:
wish = strconv.FormatFloat(float64(info.Wish)/100000000, 'f', 2, 64) + "亿"
case info.Wish > 10000:
wish = strconv.FormatFloat(float64(info.Wish)/10000, 'f', 2, 64) + "万"
default:
wish = strconv.FormatInt(info.Wish, 10)
}
rankinfo[i] = &cardInfo{
TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")",
BottomLeftText: []string{
"导演:" + info.Dir,
"演员:" + info.Star,
"标签:" + info.Cat,
"语言: " + info.OriLang + " 类型: " + movieType,
"上映时间: " + info.Rt + " 播放时间: " + info.ComingTitle,
},
RightText: wish + "人期待",
Avatar: img,
Rank: strconv.Itoa(i + 1),
}
}(i)
}
wg.Wait()
fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
if err != nil {
return
}
img, err := drawRankingCard(fontbyte, "预售电影", rankinfo)
if err != nil {
return
}
data, err = imgfactory.ToBytes(img)
return
}
func drawRankingCard(fontdata []byte, title string, rankinfo []*cardInfo) (img image.Image, err error) {
line := len(rankinfo)
const lineh = 130
const w = 800
h := 64 + (lineh+14)*line + 20 - 14
canvas := gg.NewContext(w, h)
canvas.SetRGBA255(255, 255, 255, 255)
canvas.Clear()
cardh, cardw := lineh, 770
cardspac := 14
hspac, wspac := 64.0, 16.0
r := 16.0
wg := &sync.WaitGroup{}
wg.Add(line)
cardimgs := make([]image.Image, line)
for i := 0; i < line; i++ {
go func(i int) {
defer wg.Done()
card := gg.NewContext(w, cardh)
card.NewSubPath()
card.MoveTo(wspac+float64(cardh)/2, 0)
card.LineTo(wspac+float64(cardw)-r, 0)
card.DrawArc(wspac+float64(cardw)-r, r, r, gg.Radians(-90), gg.Radians(0))
card.LineTo(wspac+float64(cardw), float64(cardh)-r)
card.DrawArc(wspac+float64(cardw)-r, float64(cardh)-r, r, gg.Radians(0), gg.Radians(90))
card.LineTo(wspac+float64(cardh)/2, float64(cardh))
card.DrawArc(wspac+r, float64(cardh)-r, r, gg.Radians(90), gg.Radians(180))
card.LineTo(wspac, r)
card.DrawArc(wspac+r, r, r, gg.Radians(180), gg.Radians(270))
card.ClosePath()
card.ClipPreserve()
avatar := rankinfo[i].Avatar
PicH := cardh - 20
picW := int(float64(avatar.Bounds().Dx()) * float64(PicH) / float64(avatar.Bounds().Dy()))
card.DrawImageAnchored(imgfactory.Size(avatar, picW, PicH).Image(), int(wspac)+10+picW/2, cardh/2, 0.5, 0.5)
card.ResetClip()
card.SetRGBA255(0, 0, 0, 127)
card.Stroke()
card.SetRGBA255(240, 210, 140, 200)
card.DrawRoundedRectangle(wspac+float64(cardw-8-250), (float64(cardh)-50)/2, 250, 50, 25)
card.Fill()
card.SetRGB255(rendercard.RandJPColor())
card.DrawRoundedRectangle(wspac+float64(cardw-8-60), (float64(cardh)-50)/2, 60, 50, 25)
card.Fill()
cardimgs[i] = card.Image()
}(i)
}
canvas.SetRGBA255(0, 0, 0, 255)
err = canvas.ParseFontFace(fontdata, 32)
if err != nil {
return
}
canvas.DrawStringAnchored(title, w/2, 64/2, 0.5, 0.5)
err = canvas.ParseFontFace(fontdata, 22)
if err != nil {
return
}
wg.Wait()
for i := 0; i < line; i++ {
canvas.DrawImageAnchored(cardimgs[i], w/2, int(hspac)+((cardh+cardspac)*i), 0.5, 0)
canvas.DrawStringAnchored(rankinfo[i].TopLeftText, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*3/16), 0, 0.5)
}
// canvas.SetRGBA255(63, 63, 63, 255)
err = canvas.ParseFontFace(fontdata, 14)
if err != nil {
return
}
for i := 0; i < line; i++ {
for j, text := range rankinfo[i].BottomLeftText {
canvas.DrawStringAnchored(text, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*6/16)+float64(j*16), 0, 0.5)
}
}
canvas.SetRGBA255(0, 0, 0, 255)
err = canvas.ParseFontFace(fontdata, 20)
if err != nil {
return
}
for i := 0; i < line; i++ {
canvas.DrawStringAnchored(rankinfo[i].RightText, w-wspac-8-60-8, hspac+float64((cardspac+cardh)*i+cardh/2), 1, 0.5)
}
canvas.SetRGBA255(255, 255, 255, 255)
err = canvas.ParseFontFace(fontdata, 28)
if err != nil {
return
}
for i := 0; i < line; i++ {
canvas.DrawStringAnchored(rankinfo[i].Rank, w-wspac-8-30, hspac+float64((cardspac+cardh)*i+cardh/2), 0.5, 0.5)
}
img = canvas.Image()
return
}
// avatar 获取电影海报,图片大且多,存本地增加响应速度
func avatar(movieInfo *movieInfo) (pic image.Image, err error) {
mu.Lock()
defer mu.Unlock()
aimgfile := filepath.Join(en.DataFolder(), movieInfo.Nm+"("+strconv.FormatInt(movieInfo.ID, 10)+").jpg")
if file.IsNotExist(aimgfile) {
err = file.DownloadTo(movieInfo.Img, aimgfile)
if err != nil {
return urlToImg(movieInfo.Img)
}
}
f, err := os.Open(filepath.Join(file.BOTPATH, aimgfile))
if err != nil {
return urlToImg(movieInfo.Img)
}
defer f.Close()
pic, _, err = image.Decode(f)
return
}
func urlToImg(url string) (img image.Image, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
img, _, err = image.Decode(resp.Body)
return
}

View File

@@ -308,7 +308,7 @@ func init() {
}
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(msg))
})
en.OnMessage(zero.NewPattern().Text(`^(?:.*使用(.*))??jj`).At().AsRule(),
en.OnMessage(zero.NewPattern(nil).Text(`^(?:.*使用(.*))??jj`).At().AsRule(),
zero.OnlyGroup).SetBlock(true).Limit(func(ctx *zero.Ctx) *rate.Limiter {
lt := jjLimiter.Load(fmt.Sprintf("%d_%d", ctx.Event.GroupID, ctx.Event.UserID))
ctx.State["jj_last_touch"] = lt.LastTouch()

View File

@@ -32,7 +32,7 @@ type favorability struct {
func init() {
// 好感度系统
engine.OnMessage(zero.NewPattern().Text(`^查好感度`).At().AsRule(), zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
engine.OnMessage(zero.NewPattern(nil).Text(`^查好感度`).At().AsRule(), zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
patternParsed := ctx.State[zero.KeyPattern].([]zero.PatternParsed)
fiancee, _ := strconv.ParseInt(patternParsed[1].At(), 10, 64)
@@ -49,7 +49,7 @@ func init() {
)
})
// 礼物系统
engine.OnMessage(zero.NewPattern().Text(`^买礼物给`).At().AsRule(), zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
engine.OnMessage(zero.NewPattern(nil).Text(`^买礼物给`).At().AsRule(), zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID

View File

@@ -93,7 +93,7 @@ func init() {
ctx.SendChain(message.Text("设置成功"))
})
// 单身技能
engine.OnMessage(zero.NewPattern().Text(`^(娶|嫁)`).At().AsRule(), zero.OnlyGroup, getdb, checkSingleDog).SetBlock(true).Limit(ctxext.LimitByUser).
engine.OnMessage(zero.NewPattern(nil).Text(`^(娶|嫁)`).At().AsRule(), zero.OnlyGroup, getdb, checkSingleDog).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
@@ -168,7 +168,7 @@ func init() {
)
})
// NTR技能
engine.OnMessage(zero.NewPattern().Text(`^当`).At().Text(`的小三`).AsRule(), zero.OnlyGroup, getdb, checkMistress).SetBlock(true).Limit(ctxext.LimitByUser).
engine.OnMessage(zero.NewPattern(nil).Text(`^当`).At().Text(`的小三`).AsRule(), zero.OnlyGroup, getdb, checkMistress).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
@@ -254,7 +254,7 @@ func init() {
)
})
// 做媒技能
engine.OnMessage(zero.NewPattern().Text(`做媒`).At().At().AsRule(), zero.OnlyGroup, zero.AdminPermission, getdb, checkMatchmaker).SetBlock(true).Limit(ctxext.LimitByUser).
engine.OnMessage(zero.NewPattern(nil).Text(`做媒`).At().At().AsRule(), zero.OnlyGroup, zero.AdminPermission, getdb, checkMatchmaker).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID

View File

@@ -2,11 +2,10 @@
package thesaurus
import (
"bytes"
"math/rand"
"strings"
"github.com/fumiama/jieba"
"github.com/go-ego/gse"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
@@ -55,11 +54,8 @@ func init() {
ctx.SendChain(message.Text("成功!"))
})
go func() {
data, err := engine.GetLazyData("dict.txt", false)
if err != nil {
panic(err)
}
seg, err := jieba.LoadDictionary(bytes.NewReader(data))
var seg gse.Segmenter
err := seg.LoadDictEmbed()
if err != nil {
panic(err)
}
@@ -102,10 +98,10 @@ func init() {
ctx.SendChain(message.Text(r.Reply))
}
})
engine.OnMessage(zero.OnlyToMe, canmatch(tDERE), match(chatListD, seg)).
engine.OnMessage(zero.OnlyToMe, canmatch(tDERE), match(chatListD, &seg)).
SetBlock(false).
Handle(randreply(sm.D))
engine.OnMessage(zero.OnlyToMe, canmatch(tKAWA), match(chatListK, seg)).
engine.OnMessage(zero.OnlyToMe, canmatch(tKAWA), match(chatListK, &seg)).
SetBlock(false).
Handle(randreply(sm.K))
}()
@@ -122,7 +118,7 @@ const (
tKAWA
)
func match(l []string, seg *jieba.Segmenter) zero.Rule {
func match(l []string, seg *gse.Segmenter) zero.Rule {
return func(ctx *zero.Ctx) bool {
return ctxext.JiebaSimilarity(0.66, seg, func(ctx *zero.Ctx) string {
return ctx.ExtractPlainText()

View File

@@ -11,6 +11,12 @@ import (
"sync"
"time"
"github.com/go-ego/gse"
"github.com/golang/freetype"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/wcharczuk/go-chart/v2"
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file"
@@ -18,17 +24,16 @@ import (
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
"github.com/golang/freetype"
"github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/wcharczuk/go-chart/v2"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
)
var (
re = regexp.MustCompile(`^[一-龥]+$`)
stopwords []string
seg gse.Segmenter
)
func init() {
@@ -39,6 +44,11 @@ func init() {
PublicDataFolder: "WordCount",
})
cachePath := engine.DataFolder() + "cache/"
// 读取gse内置中文词典
err := seg.LoadDictEmbed()
if err != nil {
panic(err)
}
_ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
@@ -120,12 +130,14 @@ func init() {
if tex == "" {
continue
}
for _, t := range ctx.GetWordSlices(tex).Get("slices").Array() {
tex := strings.TrimSpace(t.Str)
i := sort.SearchStrings(stopwords, tex)
if re.MatchString(tex) && (i >= len(stopwords) || stopwords[i] != tex) {
segments := seg.Segment(helper.StringToBytes(tex))
words := gse.ToSlice(segments, true)
for _, word := range words {
word = strings.TrimSpace(word)
i := sort.SearchStrings(stopwords, word)
if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) {
mapmu.Lock()
messageMap[tex]++
messageMap[word]++
mapmu.Unlock()
}
}

View File

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

View File

@@ -12,7 +12,7 @@
"0409": {
"identity": {
"name": "ZeroBot-Plugin",
"version": "1.9.5.2188"
"version": "1.9.8.2223"
},
"description": "",
"minimum-os": "vista",
@@ -36,23 +36,23 @@
"#1": {
"0000": {
"fixed": {
"file_version": "1.9.5.2188",
"product_version": "v1.9.5",
"timestamp": "2025-02-22T15:34:40+08:00"
"file_version": "1.9.8.2223",
"product_version": "v1.9.8",
"timestamp": "2025-06-01T18:52:57+08:00"
},
"info": {
"0409": {
"Comments": "OneBot plugins based on ZeroBot",
"CompanyName": "FloatTech",
"FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin",
"FileVersion": "1.9.5.2188",
"FileVersion": "1.9.8.2223",
"InternalName": "",
"LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.",
"LegalTrademarks": "",
"OriginalFilename": "ZBP.EXE",
"PrivateBuild": "",
"ProductName": "ZeroBot-Plugin",
"ProductVersion": "v1.9.5",
"ProductVersion": "v1.9.8",
"SpecialBuild": ""
}
}