Compare commits

..

102 Commits

Author SHA1 Message Date
源文雨
6a44331edd 🔖 v1.5.1-beta2 2022-09-11 10:32:35 +08:00
源文雨
4e78f43649 优化 2022-09-10 13:29:04 +08:00
源文雨
ca7ac08808 优化输出 2022-09-10 13:24:15 +08:00
源文雨
3351450d88 优化显示 2022-09-10 13:21:17 +08:00
源文雨
fa82bdec79 event master check default off 2022-09-10 13:14:43 +08:00
源文雨
0860a105ed 优化代码结构 2022-09-10 11:58:48 +08:00
Jiang-Red
b834307178 新插件event, 好友申请群聊邀请事件处理 (#403) 2022-09-10 11:01:24 +08:00
源文雨
fafe24f1e4 💩👌 make lint happy 2022-09-09 09:54:33 +08:00
源文雨
6f1ec04ac8 Revert "修正moegoe节之Readme (#402)" (#404)
This reverts commit 263278d71d.
2022-09-09 09:49:13 +08:00
Stardust·减
263278d71d 修正moegoe节之Readme (#402)
* Update README.md
2022-09-08 00:04:51 +08:00
源文雨
9ee5b044b4 🐛 fix engine hook 2022-09-07 00:35:08 +08:00
源文雨
21bfa14b55 更改 ban 时间为 10 分 2022-09-06 21:35:31 +08:00
源文雨
0e2d717aa6 添加ban人记忆 2022-09-06 21:24:49 +08:00
源文雨
f5d2bd78b9 添加ban人记忆 2022-09-06 21:24:19 +08:00
源文雨
ef9f764fa0 添加ban人记忆 2022-09-06 21:20:32 +08:00
源文雨
3cfe1a6960 format 2022-09-06 20:33:05 +08:00
源文雨
9387a48b5b format 2022-09-06 20:32:50 +08:00
源文雨
3812150669 edit help 2022-09-06 20:30:50 +08:00
源文雨
4ca100b0bf fix 2022-09-06 20:27:37 +08:00
源文雨
43dee54678 fix 2022-09-06 20:26:21 +08:00
源文雨
14786a94bf 优化 2022-09-06 20:11:40 +08:00
源文雨
98a652f440 优化 2022-09-06 20:02:14 +08:00
源文雨
a004ed8064 优化 2022-09-06 20:00:59 +08:00
源文雨
37b0edde38 add help 2022-09-06 19:51:11 +08:00
源文雨
fd9f19562f 优化 2022-09-06 19:47:26 +08:00
源文雨
acb7b42ded fix 2022-09-06 19:38:16 +08:00
源文雨
f1e4a523ce 优化显示 2022-09-06 19:35:03 +08:00
源文雨
676ea312a3 精简代码 2022-09-06 19:31:01 +08:00
源文雨
6acd930455 优化代码结构 & fix job regex 2022-09-06 19:19:04 +08:00
AkiraXie
340db0c644 improve antiabuse and add testcase (#398)
* improve antiabuse and add testcase

* fix managers link and add managers_test

* make linter happy

* add ttl.cache
2022-09-05 21:44:22 +08:00
方柳煜
f3d66b6abb 重构了qqwife的逻辑 (#395)
* Update and rename qqmapwife.go to command.go

* Add files via upload

* Update command.go

* Update command.go
2022-09-05 18:52:22 +08:00
源文雨
6b8ae2e864 🔖 v1.5.1-beta1 2022-09-01 21:29:49 +08:00
AkiraXie
664cb2f2f9 antiabuse only to me (#393) 2022-08-30 14:21:57 +08:00
AkiraXie
486292434e add anti_abuse plugin (#392)
add anti_abuse plugin
2022-08-30 11:08:00 +08:00
himawari
e5f186ace1 Feature danmakusuki (#388)
*  添加查弹幕

* 🎨 添加注释

* 🎨 优化代码

* 🎨 修lint

Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com>
2022-08-27 12:13:15 +08:00
himawari
0c637d4c8e 添加ahsai和breakrepeat (#384)
*  添加repeatbreak和ahsai

* 📝 添加readme说明
2022-08-27 12:09:28 +08:00
方柳煜
0943023a19 维护guessmusic和qqwife插件 (#385)
* 优化error输出,新增猜歌结束后分享歌曲

* 添加反并发,防止被多娶

* Update main.go
2022-08-26 18:44:47 +08:00
莫思潋
941d6d2884 feat: 抽塔罗牌附加解析 (#383) 2022-08-22 15:33:25 +08:00
fumiama
169af4ccce Merge branch 'master' of https://github.com/FloatTech/ZeroBot-Plugin 2022-08-22 13:35:40 +08:00
fumiama
6f1d0f8c02 fix gif panic 2022-08-22 11:53:52 +08:00
方柳煜
dd970b9a91 更换tts的语音api至原神语音api (#382) 2022-08-21 13:38:35 +08:00
fumiama
b997cb650e 🎨 优化 moegoe regex 2022-08-20 10:53:20 +08:00
fumiama
dd3ccf1d2f 🐛 moegoe Chinese 2022-08-20 09:26:15 +08:00
Stardust·减
52a7977461 新增VITS中文支持 (#378) 2022-08-20 09:20:39 +08:00
fumiama
70713c0719 🎨 优化 ERROR 格式 2022-08-16 15:15:29 +08:00
fumiama
b4d6e1ef04 🎨 update deps 2022-08-16 14:01:32 +08:00
fumiama
5295701541 fix aireply help 2022-08-16 11:51:01 +08:00
fumiama
43b9cea801 🔖 v1.5.0 2022-08-16 11:16:52 +08:00
fumiama
926d48af81 revert 2022-08-16 10:42:41 +08:00
尐酱油
962c48ca36 gif系列,新增,更新旧的素材 (#373) 2022-08-16 10:40:28 +08:00
fumiama
03fc569f87 更新 /用法 2022-08-16 10:38:16 +08:00
fumiama
e5b712ff39 🎨 优化 moegoe KR 2022-08-16 10:35:32 +08:00
fumiama
c7f7d0409e 优化 moegoe regex 2022-08-15 10:31:24 +08:00
fumiama
e4ec7ea5bb 🐛 moegoe regex 2022-08-14 20:45:53 +08:00
fumiama
4bf734778b edit README 2022-08-14 20:11:43 +08:00
fumiama
461325c8ca add plugin moegoe 2022-08-14 19:33:07 +08:00
源文雨
efa2ec6aac Merge branch 'master' of https://github.com/FloatTech/ZeroBot-Plugin 2022-08-11 19:23:12 +08:00
源文雨
eb8be40f77 fix: non-win /用法 (#370) 2022-08-11 19:22:58 +08:00
himawari
ae7e3c9c89 添加bilibilipush和完善gif文档 (#369)
*  添加bilibilipush和完善gif文档

* 🎨 修lint

* 🎨 日语语法标签匹配日文

* 🎨 优化发送逻辑和命令

* 🎨 错误处理

* 🎨 错误处理
2022-08-11 13:08:42 +08:00
莫思潋
4d68cc9fce fix: ImgFactory->Factory (#367)
* 🎨 改进代码样式

* fix: ImgFactory->Factory

* 🎨 改进代码样式

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-08-06 21:48:55 +08:00
源文雨
0226e2739a 优化 /服务列表 2022-08-06 15:48:22 +08:00
尐酱油
caa163a879 增加gif制图,菜单没写 (#363)
* Update context.go

* Update gif.go

* 🎨 改进代码样式

* Update png.go

* Update run.go

* 🎨 改进代码样式

* 修正素材地址

* Update run.go

* 呃,,,没改完整

* 继续修!!!

* Update png.go

* 🎨 改进代码样式

* Update gif.go

* 🎨 改进代码样式

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-08-06 00:06:16 +08:00
莫思潋
0e29010897 feat:将牌阵结果解析纳入转发列表 (#359) 2022-08-04 13:40:53 +08:00
源文雨
e9a8b99f4f 💩👌 make lint happy 2022-08-03 22:41:00 +08:00
莫思潋
27c637ba66 feat:小阿卡纳塔罗牌 (#346) 2022-07-29 15:26:04 +08:00
源文雨
305db3395c fix: 限速后使用功能直接导致插件崩溃 (#344) 2022-07-28 13:26:25 +08:00
himawari
3da37f0a3e Feature nihongo (#339)
*  添加日语语法学习

* 🎨 添加err
2022-07-24 23:28:30 +08:00
方柳煜
c30c9192f4 修复猜歌已知问题 (#340)
* Update main.go

* Update struct.go

* Update main.go

* Update main.go
2022-07-24 12:22:05 +08:00
源文雨
ceb3df513d 🔖 v1.5.0-beta5 2022-07-22 15:39:51 +08:00
WhitePaper
83372c4209 新增英文字符翻转插件 (#336)
*  新增英文字符翻转插件

* 🎨 代码规范优化

* 🎨 优化代码规范
2022-07-22 15:32:09 +08:00
himawari
20d5e014b7 🐛 支持json卡片 (#335)
Co-authored-by: xiaoguofan <1776620359@qq.com>
2022-07-22 11:50:38 +08:00
himawari
1c9a7eb158 🎨 修改bilibili,bilibiliparse插件结构,添加bilibili动态直播专栏解析 (#319)
* 🎨 修改bilibili,bilibiliparse插件结构,添加bilibili动态直播专栏解析

* 💩 修改大小写

* 🎨 正则全局,错误处理

* 🎨 使用json.NewDecoder()

* 💚 空使用

* 💚 修lint
2022-07-21 12:54:52 +08:00
方柳煜
31555cbf2f 修复群老婆闹离婚成功但实际没离问题 (#330) 2022-07-20 14:34:43 +08:00
方柳煜
4a1d4644ed 取消多个API依赖以提高稳定性,优化用户本地/API歌单可读性和自定义性 (#315) 2022-07-19 11:28:05 +08:00
源文雨
9182d214af ⬆️ update zb 2022-07-15 12:38:02 +08:00
源文雨
45e3383031 🔖 v1.5.0-beta4 2022-07-13 12:24:43 +08:00
himawari
fc91b69b55 添加新表情包 (#306)
*  添加新表情包

* 🐛 处理正则冲突

* 🎨 去掉反射
2022-07-12 12:25:45 +08:00
源文雨
13b6614dc2 🔖 v1.5.0-beta3 2022-07-08 21:40:51 +08:00
源文雨
fec5e4d73f 🐛 all sil 2022-07-08 21:34:23 +08:00
源文雨
585add29fc 🎨 edit README 2022-07-08 21:27:48 +08:00
源文雨
1e48e59cb7 #2: 全局启用、沉默 2022-07-08 21:20:24 +08:00
源文雨
41911af09a 🐛 b14 encode panic 2022-07-07 00:26:46 +08:00
源文雨
37b95e9559 🐛 edit README 2022-07-06 16:02:54 +08:00
源文雨
a7901745e8 🐛 response condition 2022-07-06 15:57:00 +08:00
源文雨
da9f085a50 🐛 create db panic 2022-07-06 14:59:03 +08:00
方柳煜
192c2a8bd6 修复网易云点歌API失效(405)问题 (#303)
* Update main.go

* Update selecter.go

* Update struct.go

* Update main.go
2022-07-06 13:44:24 +08:00
源文雨
e6c00e9b68 Merge branch 'master' of https://github.com/FloatTech/ZeroBot-Plugin 2022-07-05 13:45:46 +08:00
源文雨
1faa5aa5d8 control 增加 /响应 /沉默 2022-07-05 13:45:39 +08:00
源文雨
7e7191536a 🔖 v1.5.0-beta2 2022-07-03 18:28:18 +08:00
时雨◎星空
16ff1975ff 修复猜歌插件创建config.json错误 (#296) 2022-07-03 18:20:04 +08:00
源文雨
b32c86863e edit main 2022-07-03 10:24:33 +08:00
源文雨
d9b361f765 🔖 v1.5.0-beta1 2022-07-03 09:52:01 +08:00
Fox_white
5b2810e6c5 为猜歌插件添加config (#285)
*  猜歌添加config

* 📚 猜歌帮助信息更新

* 📚 猜歌帮助信息更新

*  猜歌错误信息继承

*  错误处理

*  错误处理ep2

* 🐛 猜歌插件bug修正和命名修改

*  猜歌插件不再使用gjson

* Update struct.go

* 修复-动漫2无法下载歌曲问题

* 🐛 Resolve Compilation error

* 🐛 Resolve Lint

* 🐛 Resolve Lint

Co-authored-by: 方柳煜 <101934327+fangliuyu@users.noreply.github.com>
2022-07-03 09:25:14 +08:00
github-actions[bot]
15a5b347e8 🎨 改进代码样式 2022-06-29 12:58:24 +00:00
方柳煜
477ffa825b 优化qqwife插件,新增猜歌插件 (#283)
* 猜歌插件

* 猜歌插件

* 猜歌插件

* 优化重置花名册指令

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update main.go

* Update README.md
2022-06-29 20:57:58 +08:00
Jiang-Red
84363b1513 删除无法使用的zaobao (#278)
* drop zaobao

* edit readme

* drop zaobao
2022-06-24 21:00:55 +08:00
方柳煜
01bf6f049b 修复存在误解的文案 (#272)
* 修复存在误解的文案

* Update qqmapwife.go
2022-06-19 20:51:47 +08:00
莫思潋
a8d93de000 feat:塔罗牌阵 (#268)
* 优化在两个命令中使用空格分隔的体验
- fortune的设置底图功能
- b14的加密功能

* 优化四个插件中使用空格分隔的体验
- 加密
- 哔哩哔哩推送
- 藏头诗
- 运势

* 优化并修正了上一个commit
- 加上了因为复制粘贴疏忽又没有注意测试遗漏的`?`
- 调整藏头诗和加密的正则触发,使其不必多此一举
- 删去了未被发现的测试代码

* - 删去了遗漏的Trim

* 优化了更多插件中使用空格的体验
- 优化了music bilibili image_finder 中使用空格的体验
- 补上了plugin_bilibili中未实现的vup开头触发
- 为plugin_bilibili_parse输出的消息加上一个换行符,优化排版

* - 调整funny
- 补回readme中bilibili_push的注释说明

* - 简化funny说明

* - 用暴力的办法使抽多张塔罗牌不重复
- 逆位的图片现在可以倒过来了

* - 解塔罗牌

* - 修改readme

* 大幅减少全局变量,简化抽塔罗牌输出

* - 更新了塔罗牌阵

* - 顺lint心意
- 添加判断避免重复装填

* 调整塔罗牌阵消息
- 用图片说明牌阵信息避免被封

* 调整DoOnceOnSuccess,减少冗余

* 🎨 改进代码样式

* 调整发送牌阵信息

* 🎨 改进代码样式

* 改回base64

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-06-18 16:51:11 +08:00
github-actions[bot]
2da5de3743 🎨 改进代码样式 2022-06-18 04:06:33 +00:00
方柳煜
bf6bac7be6 修复花名册会显示单身贵族的问题 (#269)
* 修复花名册会显示单身贵族的问题

* Update qqmapwife.go

* Update qqmapwife.go
2022-06-18 12:06:07 +08:00
源文雨
394d2e4a08 edit README 2022-06-17 11:59:26 +08:00
himawari
0dd447e270 添加txt转midi,优化midi转txt (#267)
* 🐛 长字符串发文件

*  添加txt转midi

*  音色自定义

*  添加windows脚本说明

* 🐛 修复某些midi读取不了的问题

*  多音轨转换

Co-authored-by: haibaraguo <haibaraguo@yeahka.com>
2022-06-17 11:46:09 +08:00
110 changed files with 8878 additions and 2541 deletions

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ plugins/*.dll
.vscode
go-zero*
nohup.out
zerobot
zerobot
ZeroBot-Plugin*

189
README.md
View File

@@ -80,6 +80,14 @@ zerobot [-c config.json] [-h] [-s config.json] [-t token] [-u url] [-n nickname]
<details>
<summary>插件控制</summary>
- [x] /响应 (在发送的群/用户开始工作)
- [x] /沉默 (在发送的群/用户停止工作)
- [x] /全局响应 (在所有位置开始工作,无视单独的沉默)
- [x] /全局沉默 (在所有本应沉默的位置停止工作,显式指定启用的位置不受影响)
- [x] /启用 xxx (在发送的群/用户启用xxx)
- [x] /禁用 xxx (在发送的群/用户禁用xxx)
@@ -114,7 +122,7 @@ zerobot [-c config.json] [-h] [-s config.json] [-t token] [-u url] [-n nickname]
<details>
<summary>动态加载插件</summary>
`import _ github.com/FloatTech/ZeroBot-Plugin-Dynamic/dyloader`
`import _ "github.com/FloatTech/ZeroBot-Plugin-Dynamic/dyloader"`
- 本功能需要`cgo`,故已分离出主线。详见[ZeroBot-Plugin-Dynamic](https://github.com/FloatTech/ZeroBot-Plugin-Dynamic)
@@ -142,7 +150,7 @@ zerobot [-c config.json] [-h] [-s config.json] [-t token] [-u url] [-n nickname]
<details>
<summary>睡眠管理</summary>
`import _ github.com/FloatTech/ZeroBot-Plugin/plugin/sleep_manage`
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/sleep_manage"`
- [x] 早安 | 晚安
@@ -321,7 +329,14 @@ print("run[CQ:image,file="+j["img"]+"]")
</details>
### *中优先级*
<details>
<summary>ahsai tts</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai"`
- [x] 使[ 伊織弓鶴 | 紲星あかり | 結月ゆかり | 京町セイカ |東北きりたん | 東北イタコ | ついなちゃん標準語 | ついなちゃん関西弁 | 音街ウナ | 琴葉茜 | 吉田くん | 民安ともえ | 桜乃そら | 月読アイ | 琴葉葵 | 東北ずん子 | 月読ショウタ | 水奈瀬コウ ]说(日语)
</details>
<details>
<summary>AIfalse</summary>
@@ -373,17 +388,37 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 查成分 [xxx]
- [x] 设置b站cookie SESSDATA=82da790d,1663822823,06ecf\*31
- [x] 查弹幕 [xxx] 2 (最后一个参数是页码)
- [x] 设置b站cookie SESSDATA=82da790d,1663822823,06ecf\*31 (最好把cookie设全)
- [x] 更新vup
</details>
<details>
<summary>b站视频链接解析</summary>
<summary>b站动态、专栏、视频、直播解析</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili_parse"`
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili"`
- [x] https://www.bilibili.com/video/BV1xx411c7BF | https://www.bilibili.com/video/av1605 | https://b23.tv/I8uzWCA | https://www.bilibili.com/video/bv1xx411c7BF
- [x] t.bilibili.com/642277677329285174 | bilibili.com/read/cv17134450 | bilibili.com/video/BV13B4y1x7pS | live.bilibili.com/22603245
</details>
<details>
<summary>b站动态、直播推送,需要配合job一起使用</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili"`
- [x] 添加b站订阅[uid|name]
- [x] 取消b站订阅[uid|name]
- [x] 取消b站动态订阅[uid|name]
- [x] 取消b站直播订阅[uid|name]
- [x] b站推送列表
- [x] 拉取b站推送 (使用job执行定时任务------记录在"@every 10s"触发的指令)
</details>
<details>
@@ -395,6 +430,14 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 随机书评
</details>
<details>
<summary>打断复读</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/breakrepeat"`
- [x] (打断三次以上的复读)
</details>
<details>
<summary>藏头诗</summary>
@@ -405,6 +448,14 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 藏尾诗[xxx]
</details>
<details>
<summary>英文字符翻转</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/char_reverser"`
- [x] 翻转 [英文字符串]
</details>
<details>
<summary>选择困难症帮手</summary>
@@ -443,7 +494,7 @@ print("run[CQ:image,file="+j["img"]+"]")
<details>
<summary>DeepDanbooru二次元图标签识别</summary>
`import _ github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru`
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru"`
- [x] 鉴赏图片[图片]
@@ -459,8 +510,6 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 教你一篇小作文[作文]
- [x] [回复]查重
</details>
<details>
<summary>漂流瓶</summary>
@@ -507,6 +556,18 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] xxx疫情
</details>
<details>
<summary>好友申请及群聊邀请事件处理</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/event"`
- [x] [开启|关闭]自动同意[申请|邀请|主人]
- [x] [同意|拒绝][申请|邀请][flag]
- flag跟随事件一起发送, 默认同意主人的事件
</details>
<details>
<summary>渲染任意文字到图片</summary>
@@ -518,7 +579,7 @@ print("run[CQ:image,file="+j["img"]+"]")
<details>
<summary>每日运势</summary>
`import _ github.com/FloatTech/ZeroBot-Plugin/plugin/fortune`
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/fortune"`
- [x] 运势 | 抽签
@@ -536,7 +597,7 @@ print("run[CQ:image,file="+j["img"]+"]")
<details>
<summary>原神抽卡</summary>
`import _ github.com/FloatTech/ZeroBot-Plugin/plugin/genshin`
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/genshin"`
- [x] 切换原神卡池
@@ -566,6 +627,40 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] >github -p [xxx]
</details>
<details>
<summary>猜歌</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/guessmusic"`
- 猜歌插件该插件依赖ffmpeg
- [x] 设置猜歌缓存歌库路径 [绝对路径]
- [x] 设置猜歌[本地/Api] [true/false]
- [x] 登录网易云
-不登陆也能用API有几率返回400
- [x] 添加歌单 [网易云歌单ID] [歌单名称]
- 注:[歌单名称]可为空,默认原标题
- [x] 删除歌单 [网易云歌单ID/API歌单名称]
- [x] 获取歌单列表
- [x] [网易云歌单ID/API歌单名称]歌单信息
- [x] [个人/团队]猜歌
-默认歌库为网易云ACG动画榜
- 可在后面添加[-歌单名称]进行指定歌单猜歌
- 歌单的歌曲命名规则为:歌名 - 歌手 - 其他(歌曲出处之类)
</details>
<details>
<summary>炉石</summary>
@@ -650,12 +745,26 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 团队听音练习
- [x] *.mid (解析上传的mid文件)
- [x] *.mid (midi 转 txt)
- [x] 注: 该插件需要安装timidity,安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh
- [x] midi制作*.txt (txt 转 midi)
- [x] 设置音色40 (0~127)
- [x] 注: 该插件需要安装timidity, linux安装脚本可参考 https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh, windows安装脚本可参考 https://gitcode.net/anto_july/midi/-/raw/master/timidity.bat?inline=false, windows需要管理员模式运行
- [x] 符号说明: C5是中央C,后面不写数字,默认接5,Cb6<1,b代表降调,#代表升调,6比5高八度,<1代表音长×2,<3代表音长×8,<-1代表音长×0.5,<-3代表音长×0.125,R是休止符
</details>
<details>
<summary>日韩 VITS 模型拟声</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moegoe"`
- [x] 让[宁宁|爱瑠|芳乃|茉子|丛雨|小春|七海]说(日语)
- [x] 让[수아|미미르|아린|연화|유화|선배]说(韩语)
</details>
<details>
<summary>摸鱼</summary>
@@ -742,6 +851,14 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] ?? [缩写]
</details>
<details>
<summary>日语语法学习</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/nihongo"`
- [x] 日语语法 [xxx] (使用tag随机)
</details>
<details>
<summary>小说</summary>
@@ -764,7 +881,7 @@ print("run[CQ:image,file="+j["img"]+"]")
<details>
<summary>浅草寺求签</summary>
`import _ github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji`
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji"`
- [x] 求签 | 占卜
@@ -855,9 +972,10 @@ print("run[CQ:image,file="+j["img"]+"]")
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/tarot"`
- [x] 抽塔罗牌
- [x] 抽n张塔罗牌
- [x][塔罗牌|大阿卡纳|小阿卡纳]
- [x] 抽n张[塔罗牌|大阿卡纳|小阿卡纳]
- [x] 解塔罗牌[牌名]
- [x] [塔罗|大阿卡纳|小阿卡纳|混合]牌阵[圣三角|时间之流|四要素|五牌阵|吉普赛十字|马蹄|六芒星]
</details>
<details>
@@ -877,6 +995,10 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 团队猜单词
- [x] 团队六阶猜单词
- [x] 团队七阶猜单词
</details>
<details>
<summary>翻译</summary>
@@ -921,23 +1043,6 @@ print("run[CQ:image,file="+j["img"]+"]")
- [x] 更新gal
</details>
<details>
<summary>早报</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/zaobao"`
- api早上8点更新推荐定时在8点30后。配合插件`job`中的记录在"cron"触发的指令使用
- [x] /启用 zaobao
- [x] /禁用 zaobao
```
记录在"00 9 * * *"触发的指令
今日早报
```
</details>
<details>
<summary>舔狗日记</summary>
@@ -966,24 +1071,6 @@ print("run[CQ:image,file="+j["img"]+"]")
- 注:由于需要科学,默认注释。
</details>
<details>
<summary>b站推送</summary>
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili_push"`
- [x] 添加b站订阅[uid]
- [x] 取消b站订阅[uid]
- [x] 取消b站动态订阅[uid]
- [x] 取消b站直播订阅[uid]
- [x] b站推送列表
-由于需要安装Chrome默认注释具体看[这里](https://www.yuque.com/xiangrikuidezhongzi/zerobot/qrwxth)
</details>
### *低优先级*

View File

@@ -1,13 +0,0 @@
package main
import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/driver"
)
type zbpcfg struct {
Z zero.Config `json:"zero"`
W []*driver.WSClient `json:"ws"`
}
var config zbpcfg

40
go.mod
View File

@@ -3,15 +3,19 @@ module github.com/FloatTech/ZeroBot-Plugin
go 1.18
require (
github.com/FloatTech/AnimeAPI v1.4.1-0.20220613042537-0adf8c5616ec
github.com/FloatTech/sqlite v0.3.2
github.com/FloatTech/zbpctrl v1.4.1-0.20220610074608-425160596f27
github.com/FloatTech/zbputils v1.4.1-0.20220613042833-33e22060e8d9
github.com/Coloured-glaze/gg v1.3.4
github.com/FloatTech/AnimeAPI v1.5.1-0.20220901132657-2585bbc03bf6
github.com/FloatTech/floatbox v0.0.0-20220822040527-f059031fec44
github.com/FloatTech/sqlite v0.3.3
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b
github.com/FloatTech/zbpctrl v1.4.1-0.20220715042842-93f081cb0133
github.com/FloatTech/zbputils v1.5.1-0.20220906163359-77457cb8fb65
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c
github.com/antchfx/htmlquery v1.2.5
github.com/corona10/goimagehash v1.0.3
github.com/fogleman/gg v1.3.0
github.com/fumiama/ahsai v0.1.0
github.com/fumiama/cron v1.3.0
github.com/fumiama/go-base16384 v1.5.3
github.com/fumiama/go-base16384 v1.5.5
github.com/fumiama/go-registry v0.1.6
github.com/fumiama/gofastTEA v0.0.10
github.com/fumiama/gotracemoe v0.0.3
@@ -21,31 +25,33 @@ require (
github.com/jozsefsallai/gophersauce v1.0.1
github.com/lucas-clemente/quic-go v0.27.2
github.com/mroth/weightedrand v0.4.1
github.com/pkg/errors v0.8.1
github.com/pkg/errors v0.9.1
github.com/pkumza/numcn v1.0.0
github.com/shirou/gopsutil/v3 v3.22.3
github.com/sirupsen/logrus v1.8.1
github.com/tidwall/gjson v1.14.1
github.com/sirupsen/logrus v1.9.0
github.com/tidwall/gjson v1.14.2
github.com/wcharczuk/go-chart/v2 v2.1.0
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220610070647-9eeffcb277ee
gitlab.com/gomidi/midi v1.23.7
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220715040337-ef4327320c40
gitlab.com/gomidi/midi/v2 v2.0.17
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
)
require (
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c // indirect
github.com/RomiChan/websocket v1.4.3-0.20220123145318-307a86b127bc // indirect
github.com/antchfx/xpath v1.2.1 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // indirect
github.com/faiface/beep v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gabriel-vasile/mimetype v1.0.4 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hajimehoshi/oto v0.7.1 // indirect
github.com/jfreymuth/oggvorbis v1.0.1 // indirect
github.com/jfreymuth/vorbis v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect
@@ -64,14 +70,16 @@ require (
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 // indirect
golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 // indirect
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.1 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
modernc.org/libc v1.16.8 // indirect
modernc.org/libc v1.16.19 // indirect
modernc.org/mathutil v1.4.1 // indirect
modernc.org/memory v1.1.1 // indirect
)

108
go.sum
View File

@@ -8,14 +8,21 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/FloatTech/AnimeAPI v1.4.1-0.20220613042537-0adf8c5616ec h1:Hl/BxaQoQFEJUIGmdhR0NyBvZChb7qkfCrF916bNxnI=
github.com/FloatTech/AnimeAPI v1.4.1-0.20220613042537-0adf8c5616ec/go.mod h1:MrCZ7P6WvF0JWHMeedAx6fVm5BP1s6/RUy0r0Jvqrds=
github.com/FloatTech/sqlite v0.3.2 h1:iTg2ZKnzjjZAdlSN3hXmpCBn15odc4Ud484OoM3yXGA=
github.com/FloatTech/sqlite v0.3.2/go.mod h1:VFtLofV5qxw5eBneZRbWwD451SLSm50o9J3J43iB1iw=
github.com/FloatTech/zbpctrl v1.4.1-0.20220610074608-425160596f27 h1:C+D30vpxfgbJetTFXWAHzuU8GydbFb/A8Kv6E3PdRS4=
github.com/FloatTech/zbpctrl v1.4.1-0.20220610074608-425160596f27/go.mod h1:5FDkrlVaQCxUfeqH7XJPTfej0q+y9fzImhvZI4ofu9Y=
github.com/FloatTech/zbputils v1.4.1-0.20220613042833-33e22060e8d9 h1:IeUs08sUqdR/g8DfxPNTSBfsE1g0OtmffLNgKzbrVZ4=
github.com/FloatTech/zbputils v1.4.1-0.20220613042833-33e22060e8d9/go.mod h1:A9AeVHZsv5chyw8p4fDI0cHnEOfMpmsTLoLWqUT7TO4=
github.com/Coloured-glaze/gg v1.3.4 h1:l31zIF/HaVwkzjrj+A56RGQoSKyKuR1IWtIrqXGFStI=
github.com/Coloured-glaze/gg v1.3.4/go.mod h1:Ih5NLNNDHOy3RJbB0EPqGTreIzq/H02TGThIagh8HJg=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/FloatTech/AnimeAPI v1.5.1-0.20220901132657-2585bbc03bf6 h1:B4KmWFHA6zXjyvcubwV8MrxbKGLAudiVUGyxE9KojTc=
github.com/FloatTech/AnimeAPI v1.5.1-0.20220901132657-2585bbc03bf6/go.mod h1:R8jADxRo02AEuV6kOm+IcBGm7p/jqzngxOzK4y9EJRI=
github.com/FloatTech/floatbox v0.0.0-20220822040527-f059031fec44 h1:gnNO7tpxXpTuLuqIK9ekZScvDKt3jwHR+/rkDU838Nw=
github.com/FloatTech/floatbox v0.0.0-20220822040527-f059031fec44/go.mod h1:hK2HTQddBomA/sY2oD7VeW0GA5BxlIoYqcAOnamw1EU=
github.com/FloatTech/sqlite v0.3.3 h1:cSlpv+DcIehqaUG2YyjW0Twh396FoGsybpEpFZnX3Jg=
github.com/FloatTech/sqlite v0.3.3/go.mod h1:i33d92OtR8jcp5fBUvQtospf27+MkfUxnGwnZ95E/dA=
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b h1:tvciXWq2nuvTbFeJGLDNIdRX3BI546D3O7k7vrVueZw=
github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/FloatTech/zbpctrl v1.4.1-0.20220715042842-93f081cb0133 h1:nP9NI4I+vtwAbiU7wCJwjuzCzMZ/yJYg8h3667HGnv0=
github.com/FloatTech/zbpctrl v1.4.1-0.20220715042842-93f081cb0133/go.mod h1:72BnjyBwQWUC8mqM9dPk5ZrjxXCilQCVp+jfgHATNdw=
github.com/FloatTech/zbputils v1.5.1-0.20220906163359-77457cb8fb65 h1:ztINgRoUNLQBOJ8g0IVNdkf1DrRUioRoSIflqvPSOao=
github.com/FloatTech/zbputils v1.5.1-0.20220906163359-77457cb8fb65/go.mod h1:ZT91eCYR6y1HbLRTO5EB7o8K7qEPbCsX7vjhYIuMijc=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c h1:cNPOdTNiVwxLpROLjXCgbIPvdkE+BwvxDvgmdYmWx6Q=
github.com/RomiChan/syncx v0.0.0-20220404072119-d7ea0ae15a4c/go.mod h1:KqZzu7slNKROh3TSYEH/IUMG6f4M+1qubZ5e52QypsE=
@@ -36,6 +43,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/corona10/goimagehash v1.0.3 h1:NZM518aKLmoNluluhfHGxT3LGOnrojrxhGn63DR/CZA=
github.com/corona10/goimagehash v1.0.3/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -48,17 +56,19 @@ github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 h1:BBade+Jl
github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4/go.mod h1:H7chHJglrhPPzetLdzBleF8d22WYOv7UM/lEKYiwlKM=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c=
github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
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/go-base16384 v1.5.3 h1:IUZUlm2ajJB1nEJzauP6yD5IeJoVHyBEkzKJf9O82zs=
github.com/fumiama/go-base16384 v1.5.3/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-base16384 v1.5.5 h1:gsJW5GVfD0gV18/DnntWU4vDnaCvT5ysK0m2jkCqhU0=
github.com/fumiama/go-base16384 v1.5.5/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM=
github.com/fumiama/go-registry v0.1.6 h1:Ee/tXCCIR/xt8celhbbw0W/xDMdhAXLwy2YFBB/LWFk=
github.com/fumiama/go-registry v0.1.6/go.mod h1:dIUVbiOgfk9oZcsgwDvNLC72i+ctibVukSXR/9bLviI=
github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoGhhQ=
@@ -69,8 +79,13 @@ github.com/fumiama/sqlite3 v1.14.6 h1:+e+iygyiDXQJVi7xeXIviBvR7hAc5y20WA9hRwfKn1
github.com/fumiama/sqlite3 v1.14.6/go.mod h1:Xx9a2/OtHuy9pBjow0N+bE/RhNeZ7zZz5xh25vqbA5A=
github.com/gabriel-vasile/mimetype v1.0.4 h1:uBejfH8l3/2f+5vjl1e4xIaSyNEhRBZ5N/ij7ohpNd8=
github.com/gabriel-vasile/mimetype v1.0.4/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
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-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
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=
@@ -126,8 +141,18 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.7.1 h1:I7maFPz5MBCwiutOrz++DLdbr4rTzBsbBuV2VpgU9kk=
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/icza/bitio v1.0.0/go.mod h1:0jGnlLAx8MKMr9VGnn/4YrvZiprkvBelsVIbA9Jjr9A=
github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jfreymuth/oggvorbis v1.0.1 h1:NT0eXBgE2WHzu6RT/6zcb2H10Kxj6Fm3PccT0LE6bqw=
github.com/jfreymuth/oggvorbis v1.0.1/go.mod h1:NqS+K+UXKje0FUYUPosyQ+XTVvjmVjps1aEZH1sumIk=
github.com/jfreymuth/vorbis v1.0.0 h1:SmDf783s82lIjGZi8EGUUaS7YxPHgRj4ZXW/h7rUi7U=
github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -148,6 +173,7 @@ github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lucas-clemente/quic-go v0.27.2 h1:zsMwwniyybb8B/UDNXRSYee7WpQJVOcjQEGgpw2ikXs=
github.com/lucas-clemente/quic-go v0.27.2/go.mod h1:vXgO/11FBSKM+js1NxoaQ/bPtVFYfB7uxhfHXyMhl1A=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
@@ -163,9 +189,12 @@ github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU=
github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -189,8 +218,9 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkumza/numcn v1.0.0 h1:ZT5cf9IJkUZgRgEtCiNNykk0RwsrKXSTsvDHOwUTzgE=
github.com/pkumza/numcn v1.0.0/go.mod h1:QSeH+al9dWCd8di5HZM/ZqHqhZmUKfph572e9Ev/ETc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -229,18 +259,19 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
@@ -253,14 +284,12 @@ github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49u
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/wcharczuk/go-chart/v2 v2.1.0 h1:tY2slqVQ6bN+yHSnDYwZebLQFkphK4WNrVwnt7CJZ2I=
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220610070647-9eeffcb277ee h1:b2f+KLhZv+BCMQZuwMJvhKQOrz5YXzOduHC3G1DjQR0=
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220610070647-9eeffcb277ee/go.mod h1:LJ+VOf523i3IrykuLK53UEeWqnAclRL5d2wGT4sS4Zk=
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220715040337-ef4327320c40 h1:j+cmfkjc8WcBKiOsvMzcIgAuPhiTs7FBeiRn/QLVFhA=
github.com/wdvxdr1123/ZeroBot v1.5.2-0.20220715040337-ef4327320c40/go.mod h1:LJ+VOf523i3IrykuLK53UEeWqnAclRL5d2wGT4sS4Zk=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
gitlab.com/gomidi/midi v1.23.7 h1:I6qKoIk9s9dcX+pNf0jC+tziCzJFn82bMpuntRkLeik=
gitlab.com/gomidi/midi v1.23.7/go.mod h1:3ohtNOhqoSakkuLG/Li1OI6I3J1c2LErnJF5o/VBq1c=
gitlab.com/gomidi/midi/v2 v2.0.17 h1:kf16wNwFFOskl0trvarOwMuZUQICdIGn37LP9QqIRuo=
gitlab.com/gomidi/midi/v2 v2.0.17/go.mod h1:quTyMKSQ4Klevxu6gY4gy2USbeZra0fV5SalndmPfsY=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -276,13 +305,19 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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/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.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd h1:9NbNcTg//wfC5JskFW4Z3sqwVnjmJKHxLAol1bW2qgw=
golang.org/x/image v0.0.0-20220601225756-64ec528b34cd/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
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/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -306,8 +341,8 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 h1:N9Vc/rorQUDes6B9CNdIxAn5jODGj2wzfrei2x4wNj4=
golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -324,12 +359,14 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -350,8 +387,9 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 h1:z8Hj/bl9cOV2grsOpEaQFUaly0JWN3i97mo3jXKJNp0=
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 h1:9vYwv7OjYaky/tlAeD7C4oC9EsPTlaFl1H2jS++V+ME=
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -438,9 +476,6 @@ modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.20/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.35.22/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
modernc.org/ccgo/v3 v3.11.0/go.mod h1:dGNposbDp9TOZ/1KBxghxtUp/bzErD0/0QW4hhSaBMI=
@@ -482,11 +517,8 @@ modernc.org/ccgo/v3 v3.13.1/go.mod h1:aBYVOUfIlcSnrsRVU8VRS35y2DIfpgkmVkYZ0tpIXi
modernc.org/ccgo/v3 v3.15.9/go.mod h1:md59wBwDT2LznX/OTCPoVS6KIsdRgY8xqQwBV+hkTH0=
modernc.org/ccgo/v3 v3.15.10/go.mod h1:wQKxoFn0ynxMuCLfFD09c8XPUCc8obfchoVR9Cn0fI8=
modernc.org/ccgo/v3 v3.15.12/go.mod h1:VFePOWoCd8uDGRJpq/zfJ29D0EVzMSyID8LCMWYbX6I=
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
modernc.org/libc v1.11.0/go.mod h1:2lOfPmj7cz+g1MrPNmX65QCzVxgNq2C5o0jdLY2gAYg=
@@ -529,10 +561,8 @@ modernc.org/libc v1.14.1/go.mod h1:npFeGWjmZTjFeWALQLrvklVmAxv4m80jnG3+xI8FdJk=
modernc.org/libc v1.14.2/go.mod h1:MX1GBLnRLNdvmK9azU9LCxZ5lMyhrbEMK8rG3X/Fe34=
modernc.org/libc v1.14.3/go.mod h1:GPIvQVOVPizzlqyRX3l756/3ppsAgg1QgPxjr5Q4agQ=
modernc.org/libc v1.14.5/go.mod h1:2PJHINagVxO4QW/5OQdRrvMYo+bm5ClpUFfyXCYl9ak=
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
modernc.org/libc v1.16.8 h1:Ux98PaOMvolgoFX/YwusFOHBnanXdGRmWgI8ciI2z4o=
modernc.org/libc v1.16.8/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
modernc.org/libc v1.16.19 h1:S8flPn5ZeXx6iw/8yNa986hwTQDrY8RXU7tObZuAozo=
modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=

View File

@@ -10,7 +10,7 @@ import (
var (
info = [...]string{
"* OneBot + ZeroBot + Golang",
"* Version 1.4.1 - 2022-06-16 13:31:06 +0800 CST",
"* Version 1.5.1-beta2 - 2022-09-11 10:32:06 +0800 CST",
"* Copyright © 2020 - 2022 FloatTech. All Rights Reserved.",
"* Project: https://github.com/FloatTech/ZeroBot-Plugin",
}

136
main.go
View File

@@ -1,3 +1,4 @@
// Package main ZeroBot-Plugin main file
package main
import (
@@ -25,6 +26,8 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/antiabuse" // 违禁词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chat" // 基础词库
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/sleep_manage" // 统计睡眠时间
@@ -55,68 +58,72 @@ import (
// vvvvvvvvvvvvvv //
// vvvv //
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ai_false" // 服务器监控
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/b14" // base16384加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/baidu" // 百度一下
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili" // 查询b站用户信息
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili_parse" // b站视频链接解析
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/book_review" // 哀伤雪刃吧推书记录
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/cangtoushi" // 藏头诗
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/choose" // 选择困难症帮手
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chouxianghua" // 说抽象话
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/coser" // 三次元小姐姐
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/cpstory" // cp短打
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/drift_bottle" // 漂流瓶
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/emojimix" // 合成emoji
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/epidemic" // 城市疫情查询
_ "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/hs" // 炉石
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hyaku" // 百人一首
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/image_finder" // 关键字搜图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/inject" // 注入指令
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/juejuezi" // 绝绝子生成器
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu_calendar" // 摸鱼人日历
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativesetu" // 本地涩图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativewife" // 本地老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nbnhhsh" // 拼音首字母缩写释义工具
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/novel" // 铅笔小说网搜索
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nsfw" // nsfw图片识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/scale" // 叔叔的AI二次元图片放大
_ "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/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/vtb_quotation" // vtb语录
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wangyiyun" // 网易云音乐热评
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/word_count" // 聊天热词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/zaobao" // 早报
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai" // ahsai tts
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ai_false" // 服务器监控
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/b14" // base16384加解密
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/baidu" // 百度一下
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili" // b站相关
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/book_review" // 哀伤雪刃吧推书记录
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/breakrepeat" // 打断复读
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/cangtoushi" // 藏头诗
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/char_reverser" // 英文字符翻转
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/choose" // 选择困难症帮手
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/chouxianghua" // 说抽象话
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/coser" // 三次元小姐姐
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/cpstory" // cp短打
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/drift_bottle" // 漂流瓶
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/emojimix" // 合成emoji
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/epidemic" // 城市疫情查询
_ "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/hs" // 炉石
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/hyaku" // 百人一首
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/image_finder" // 关键字搜图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/inject" // 注入指令
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/jandan" // 煎蛋网无聊图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/juejuezi" // 绝绝子生成器
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/lolicon" // lolicon 随机图片
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moegoe" // 日韩 VITS 模型拟声
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu_calendar" // 摸鱼人日历
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativesetu" // 本地涩图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nativewife" // 本地老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nbnhhsh" // 拼音首字母缩写释义工具
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nihongo" // 日语语法学习
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/novel" // 铅笔小说网搜索
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/nsfw" // nsfw图片识别
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/omikuji" // 浅草寺求签
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/qqwife" // 一群一天一夫一妻制群老婆
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/scale" // 叔叔的AI二次元图片放大
_ "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/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/vtb_quotation" // vtb语录
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wangyiyun" // 网易云音乐热评
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/word_count" // 聊天热词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wtf" // 鬼东西
// _ "github.com/FloatTech/ZeroBot-Plugin/plugin/bilibili_push" // b站推送
// ^^^^ //
// ^^^^^^^^^^^^^^ //
@@ -152,7 +159,7 @@ import (
// //
// //
// -----------------------以下为内置依赖,勿动------------------------ //
"github.com/FloatTech/zbputils/process"
"github.com/FloatTech/floatbox/process"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/driver"
@@ -160,6 +167,13 @@ import (
// -----------------------以上为内置依赖,勿动------------------------ //
)
type zbpcfg struct {
Z zero.Config `json:"zero"`
W []*driver.WSClient `json:"ws"`
}
var config zbpcfg
func init() {
sus := make([]int64, 0, 16)
// 解析命令行参数

96
plugin/ahsai/ahsai.go Normal file
View File

@@ -0,0 +1,96 @@
// Package ahsai AH Soft フリーテキスト音声合成 demo API
package ahsai
import (
"fmt"
"math/rand"
"os"
"sort"
"strconv"
"time"
"github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
ahsaitts "github.com/fumiama/ahsai"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
var (
namelist = [...]string{"伊織弓鶴", "紲星あかり", "結月ゆかり", "京町セイカ", "東北きりたん", "東北イタコ", "ついなちゃん標準語", "ついなちゃん関西弁", "音街ウナ", "琴葉茜", "吉田くん", "民安ともえ", "桜乃そら", "月読アイ", "琴葉葵", "東北ずん子", "月読ショウタ", "水奈瀬コウ"}
namesort = func() []string {
nl := namelist[:]
sort.Strings(nl)
return nl
}()
)
func init() {
engine := control.Register("ahsai", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "ahsai tts\n- 使[伊織弓鶴|紲星あかり|結月ゆかり|京町セイカ|東北きりたん|東北イタコ|ついなちゃん標準語|ついなちゃん関西弁|音街ウナ|琴葉茜|吉田くん|民安ともえ|桜乃そら|月読アイ|琴葉葵|東北ずん子|月読ショウタ|水奈瀬コウ]说(日语)\n",
PrivateDataFolder: "ahsai",
})
cachePath := engine.DataFolder() + "cache/"
_ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
engine.OnRegex("^使(.{0,10})说([A-Za-z\\s\\d\u3005\u3040-\u30ff\u4e00-\u9fff\uff11-\uff19\uff21-\uff3a\uff41-\uff5a\uff66-\uff9d\\pP]+)$", selectName).SetBlock(true).Handle(func(ctx *zero.Ctx) {
ctx.SendChain(message.Text("少女祈祷中..."))
uid := ctx.Event.UserID
today := time.Now().Format("20060102150405")
ahsaiFile := cachePath + strconv.FormatInt(uid, 10) + today + "ahsai.wav"
s := ahsaitts.NewSpeaker()
err := s.SetName(ctx.State["ahsainame"].(string))
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
u, err := s.Speak(ctx.State["ahsaitext"].(string))
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
err = ahsaitts.SaveOggToFile(u, ahsaiFile)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + ahsaiFile))
})
}
func selectName(ctx *zero.Ctx) bool {
regexMatched := ctx.State["regex_matched"].([]string)
ctx.State["ahsaitext"] = regexMatched[2]
name := regexMatched[1]
index := sort.SearchStrings(namesort, name)
if index < len(namelist) && namesort[index] == name {
ctx.State["ahsainame"] = name
return true
}
speaktext := ""
for i, v := range namelist {
speaktext += fmt.Sprintf("%d. %s\n", i, v)
}
ctx.SendChain(message.Text("输入的音源为空, 请输入音源序号\n", speaktext))
next, cancel := zero.NewFutureEvent("message", 999, false, ctx.CheckSession(), zero.RegexRule(`\d{0,2}`)).Repeat()
defer cancel()
for {
select {
case <-time.After(time.Second * 10):
ctx.State["ahsainame"] = namelist[rand.Intn(len(namelist))]
ctx.SendChain(message.Text("时间太久啦!", zero.BotConfig.NickName[0], "帮你选择", ctx.State["ahsainame"]))
return true
case c := <-next:
msg := c.Event.Message.ExtractPlainText()
num, _ := strconv.Atoi(msg)
if num < 0 || num >= len(namelist) {
ctx.SendChain(message.Text("序号非法!"))
continue
}
ctx.State["ahsainame"] = namelist[num]
return true
}
}
}

View File

@@ -50,34 +50,34 @@ func init() { // 插件主体
Handle(func(ctx *zero.Ctx) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("ERROR:no such plugin"))
ctx.SendChain(message.Text("ERROR: no such plugin"))
return
}
m, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if ctx.State["regex_matched"].([]string)[2] == "分钟" {
m *= 60
}
if m >= 65536 || m <= 0 {
ctx.SendChain(message.Text("ERROR:interval too big"))
ctx.SendChain(message.Text("ERROR: interval too big"))
return
}
n, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[3], 10, 64)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if n >= 65536 || n <= 0 {
ctx.SendChain(message.Text("ERROR:burst too big"))
ctx.SendChain(message.Text("ERROR: burst too big"))
return
}
ctxext.SetDefaultLimiterManagerParam(time.Duration(m)*time.Second, int(n))
err = c.SetData(0, (m&0xffff)|((n<<16)&0xffff0000))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("设置默认限速为每", m, "秒触发", n, "次"))

View File

@@ -1,9 +1,12 @@
package aireply
import (
"fmt"
"net/url"
"regexp"
"strconv"
"sync"
"time"
"github.com/pkumza/numcn"
log "github.com/sirupsen/logrus"
@@ -11,171 +14,246 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/AnimeAPI/aireply"
"github.com/FloatTech/AnimeAPI/tts"
"github.com/FloatTech/AnimeAPI/tts/baidutts"
"github.com/FloatTech/AnimeAPI/tts/mockingbird"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
)
const ttsServiceName = "tts"
const (
ttsServiceName = "tts"
cnapi = "http://233366.proxy.nscc-gz.cn:8888?speaker=%s&text=%s"
// testString = "这是测试语言......"
)
var re = regexp.MustCompile(`(\-|\+)?\d+(\.\d+)?`)
// 每个角色的测试文案
var testRecord = map[string]string{
"派蒙": "哎,又是看不懂的东西。我完全不知道这些奇怪的问题和实验,能得到什么结果…",
"凯亚": "真是个急性子啊你。",
"安柏": "最初的鸟儿是不会飞翔的,飞翔是它们勇敢跃入峡谷的奖励。",
"丽莎": "嗨,小可爱,你是新来的助理吗?",
"琴": "蒲公英骑士,琴,申请入队。",
"香菱": "我是来自璃月的厨师香菱,最擅长的是做各种捞…捞,料理…哎呀,练了那么多次,还是会紧张,嘿。",
"枫原万叶": "飘摇风雨中,带刀归来赤脚行。",
"迪卢克": "在黎明来临之前,总要有人照亮黑暗。",
"温迪": "若你困于无风之地,我将为你奏响高天之歌。",
"可莉": "西风骑士团,火花骑士,可莉,前来报到!…呃—后面该说什么词来着?可莉背不下来啦...",
"早柚": "终末番,早柚,参上。 呼——",
"托马": "初次见面,异乡的旅人,你的名字我可是早就听说了。只要你不嫌弃,我托马,从今天起就是你的朋友了。",
"芭芭拉": "芭芭拉,闪耀登场~治疗就交给我吧,不会让你失望的!",
"优菈": "沉沦是很容易的一件事,但我仍想冻住这股潮流。",
"云堇": "曲高未必人不识,自有知音和清词。",
"钟离": "人间归离复归离,借一浮生逃浮生。",
"魈": "三眼五显仙人,魈,听召,前来守护",
"凝光": "就算古玩价值连城,给人的快乐,也只有刚拥有的一瞬",
"雷电将军": "浮世千百年来风景依旧,人之在世却如白露与泡影。",
"北斗": "不知道如何向前的话,总之先迈出第一步,后面的道路就会自然而然地展开了。",
"甘雨": "这项工作,该划掉了。",
"七七": "椰羊的奶,好喝!比一般的羊奶,好喝!",
"刻晴": "劳逸结合是不错,但也别放松过头。",
"神里绫华": "若知是梦何须醒,不比真如一相会。",
"雷泽": "你是朋友。我和你一起狩猎。",
"神里绫人": "此前听绫华屡次提起阁下,不料公务繁忙,直至今日才有机会相见。",
"罗莎莉亚": "哪怕如今你已经走上截然不同的道路,也不要否认从前的自己,从前的每一个你都是你脚下的基石,不要害怕过去,不要畏惧与它抗衡。",
"阿贝多": "用自己的双脚丈量土地,将未知变为知识。",
"八重神子": "我的神明,就托付给你了。",
"宵宫": "即使只是片刻的火花,也能在仰望黑夜的人心中留下久久不灭的美丽光芒。",
"荒泷一斗": "更好地活下去,绝不该靠牺牲同类换取,应该是,一起更好地活着,才对。",
"九条裟罗": "想要留住雪花。但在手心里,它只会融化的更快。",
"夜兰": "线人来信了,嗯,看来又出现了新的变数。",
"珊瑚宫心海": "成为了现任人神巫女之后,我也慢慢习惯了这样的生活,更重要的是我也因此和你相遇了,不是吗?",
"五郎": "海祇岛反抗军大将,五郎,前来助阵!",
"达达利亚": "许下的诺言就好好遵守,做错了事情就承担责任,这才是家人应有的样子吧。",
"莫娜": "正是因为无法更改,无可违逆,只能接受,命运才会被称之为命运。",
"班尼特": "只要有大家在,伤口就不会痛!",
"申鹤": "不知道你是喜欢人间的灯火,还是山林的月光?",
"行秋": "有时明月无人夜,独向昭潭制恶龙。",
"烟绯": "律法即是约束,也是工具。",
"久岐忍": "有麻烦事要处理的话,直接告诉我就好,我来摆平。",
"辛焱": "马上就要演出了,你也一起来嗨吗?",
"砂糖": "我是砂糖,炼金术的…研究员。",
"胡桃": "阴阳有序,命运无常,死亡难以预测,却也有它的规矩。",
"重云": "我名重云,家族久居璃月,世代以驱邪除魔为业。",
"菲谢尔": "我即断罪之皇女,真名为菲谢尔。应命运的召唤降临在此间——哎?你也是,异世界的旅人吗…?",
"诺艾尔": "我是诺艾尔,西风骑士团的女仆,从今天起会陪你一起去冒险。",
"迪奥娜": "猫尾酒馆的招牌调酒师,迪奥娜,我的出场费可是很贵的。",
"鹿野院平藏": "我叫鹿野院平藏,是天领奉行里破案最多最快的侦探……",
}
var (
re = regexp.MustCompile(`(\-|\+)?\d+(\.\d+)?`)
soundList = [...]string{
"派蒙", "凯亚", "安柏", "丽莎", "琴",
"香菱", "枫原万叶", "迪卢克", "温迪", "可莉",
"早柚", "托马", "芭芭拉", "优菈", "云堇",
"钟离", "魈", "凝光", "雷电将军", "北斗",
"甘雨", "七七", "刻晴", "神里绫华", "雷泽",
"神里绫人", "罗莎莉亚", "阿贝多", "八重神子", "宵宫",
"荒泷一斗", "九条裟罗", "夜兰", "珊瑚宫心海", "五郎",
"达达利亚", "莫娜", "班尼特", "申鹤", "行秋",
"烟绯", "久岐忍", "辛焱", "砂糖", "胡桃",
"重云", "菲谢尔", "诺艾尔", "迪奥娜", "鹿野院平藏",
}
)
type ttsInstances struct {
sync.RWMutex
m map[string]tts.TTS
l []string
defaultSoundMode string
soundMode []string
}
func (t *ttsInstances) List() []string {
t.RLock()
cl := make([]string, len(t.l))
_ = copy(cl, t.l)
t.RUnlock()
return cl
func list(list []string, num int) string {
s := ""
for i, value := range list {
s += value
if (i+1)%num == 0 {
s += "\n"
} else {
s += " | "
}
}
return s
}
func init() {
t := &ttsInstances{
m: map[string]tts.TTS{
"百度女声": baidutts.NewBaiduTTS(0),
"百度男声": baidutts.NewBaiduTTS(1),
"百度度逍遥": baidutts.NewBaiduTTS(3),
"百度度丫丫": baidutts.NewBaiduTTS(4),
"拟声鸟阿梓": nil,
"拟声鸟文静": nil,
"拟声鸟药水哥": nil,
},
l: []string{"拟声鸟阿梓", "拟声鸟文静", "拟声鸟药水哥", "百度女声", "百度男声", "百度度逍遥", "百度度丫丫"},
tts := &ttsInstances{
defaultSoundMode: "派蒙",
soundMode: soundList[:],
}
engine := control.Register(ttsServiceName, &ctrl.Options[*zero.Ctx]{
DisableOnDefault: true,
Help: "语音回复(包括拟声鸟和百度)\n" +
Help: "语音回复(大家一起来炼丹)\n" +
"- @Bot 任意文本(任意一句话回复)\n" +
"- 设置语音模式[拟声鸟阿梓 | 拟声鸟文静 | 拟声鸟药水哥 | 百度女声 | 百度男声| 百度度逍遥 | 百度度丫丫]\n" +
"- 设置默认语音模式[拟声鸟阿梓 | 拟声鸟文静 | 拟声鸟药水哥 | 百度女声 | 百度男声| 百度度逍遥 | 百度度丫丫]\n",
"- 设置语音模式[原神人物]\n" +
"- 设置默认语音模式[原神人物]\n" +
"当前适用的原神人物含有以下:\n" + list(soundList[:], 5),
})
engine.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
msg := ctx.ExtractPlainText()
// 获取回复模式
r := aireply.NewAIReply(getReplyMode(ctx))
tts, err := t.new(t.getSoundMode(ctx))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
// 获取回复的文本
reply := r.TalkPlain(msg, zero.BotConfig.NickName[0])
// 获取角色
name := tts.getSoundMode(ctx)
if _, ok := testRecord[name]; !ok {
ctx.SendChain(message.Text("配置的语言人物数据丢失!请重新设置语言人物。"))
return
}
var reply string
if tts != nil {
rec, err := tts.Speak(ctx.Event.UserID, func() string {
reply = r.TalkPlain(msg, zero.BotConfig.NickName[0])
reply = re.ReplaceAllStringFunc(reply, func(s string) string {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
log.Errorln("[tts]:", err)
return s
}
return numcn.EncodeFromFloat64(f)
})
log.Debugln("[tts]:", reply)
return reply
})
if err == nil {
ctx.SendChain(message.Record(rec))
} else {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(reply))
}
// 获取语言
record := message.Record(fmt.Sprintf(cnapi, url.QueryEscape(name), url.QueryEscape(
// 将数字转文字
re.ReplaceAllStringFunc(reply, func(s string) string {
f, err := strconv.ParseFloat(s, 64)
if err != nil {
log.Errorln("[tts]:", err)
return s
}
return numcn.EncodeFromFloat64(f)
}),
))).Add("cache", 0)
if record.Data == nil {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(reply))
}
// 发送语音
if ID := ctx.SendChain(record); ID.ID() == 0 {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(reply))
}
})
engine.OnRegex(`^设置语音模式(.*)$`, ctxext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, t)).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
param := ctx.State["regex_matched"].([]string)[1]
err := t.setSoundMode(ctx, param)
if err != nil {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
return
}
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,当前模式为", param))
})
engine.OnRegex(`^设置默认语音模式(.*)$`, ctxext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, t)).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
param := ctx.State["regex_matched"].([]string)[1]
t.setDefaultSoundMode(param)
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,默认模式为", param))
})
}
// new 语音简单工厂
func (t *ttsInstances) new(name string) (ts tts.TTS, err error) {
t.RLock()
ts = t.m[name]
t.RUnlock()
if ts == nil {
switch name {
case "拟声鸟阿梓":
t.Lock()
ts, err = mockingbird.NewMockingBirdTTS(0)
t.Unlock()
case "拟声鸟文静":
t.Lock()
ts, err = mockingbird.NewMockingBirdTTS(1)
t.Unlock()
case "拟声鸟药水哥":
t.Lock()
ts, err = mockingbird.NewMockingBirdTTS(2)
t.Unlock()
engine.OnRegex(`^设置语音模式(.*)$`, func(ctx *zero.Ctx) bool {
param := ctx.State["regex_matched"].([]string)[1]
if _, ok := testRecord[param]; !ok {
return false
}
}
return
return true
}).SetBlock(true).Handle(func(ctx *zero.Ctx) {
param := ctx.State["regex_matched"].([]string)[1]
// 保存设置
err := tts.setSoundMode(ctx, param)
if err != nil {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
return
}
// 设置验证
name := tts.getSoundMode(ctx)
if _, ok := testRecord[name]; !ok {
ctx.SendChain(message.Text("配置的语言人物数据丢失!请重新设置语言人物。"))
return
}
record := message.Record(fmt.Sprintf(cnapi, url.QueryEscape(name), url.QueryEscape(testRecord[name]))).Add("cache", 0)
if ID := ctx.SendChain(record); ID.ID() == 0 {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置失败!无法发送测试语言,请重试。"))
return
}
time.Sleep(time.Second * 2)
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,默认模式为", name))
})
engine.OnRegex(`^设置默认语音模式(.*)$`, func(ctx *zero.Ctx) bool {
param := ctx.State["regex_matched"].([]string)[1]
if _, ok := testRecord[param]; !ok {
return false
}
return true
}).SetBlock(true).Handle(func(ctx *zero.Ctx) {
param := ctx.State["regex_matched"].([]string)[1]
// 保存设置
tts.setDefaultSoundMode(param)
// 设置验证
name := tts.defaultSoundMode
record := message.Record(fmt.Sprintf(cnapi, url.QueryEscape(name), url.QueryEscape(testRecord[name]))).Add("cache", 0)
if ID := ctx.SendChain(record); ID.ID() == 0 {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置失败!无法发送测试语言,请重试。"))
return
}
time.Sleep(time.Second * 2)
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,默认模式为", name))
})
}
func (t *ttsInstances) setSoundMode(ctx *zero.Ctx, name string) error {
func (tts *ttsInstances) setSoundMode(ctx *zero.Ctx, name string) error {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
var index int64
t.RLock()
for i, s := range t.l {
tts.RLock()
for i, s := range tts.soundMode {
if s == name {
index = int64(i)
break
}
}
t.RUnlock()
tts.RUnlock()
m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
return m.SetData(gid, index)
}
func (t *ttsInstances) getSoundMode(ctx *zero.Ctx) (name string) {
func (tts *ttsInstances) getSoundMode(ctx *zero.Ctx) (name string) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if ok {
t.RLock()
defer t.RUnlock()
tts.RLock()
defer tts.RUnlock()
index := m.GetData(gid)
if int(index) < len(t.l) {
return t.l[index]
if int(index) < len(tts.soundMode) {
return tts.soundMode[index]
}
}
return "拟声鸟阿梓"
return tts.defaultSoundMode
}
func (t *ttsInstances) setDefaultSoundMode(name string) {
var index int
t.RLock()
for _, s := range t.l {
func (tts *ttsInstances) setDefaultSoundMode(name string) {
var index int64
tts.RLock()
for i, s := range tts.soundMode {
if s == name {
index = int64(i)
break
}
index++
}
t.RUnlock()
t.Lock()
t.l[0], t.l[index] = t.l[index], t.l[0]
t.Unlock()
tts.defaultSoundMode = tts.soundMode[index]
tts.RUnlock()
}

View File

@@ -23,7 +23,7 @@ func init() { // 插件主体
engine := control.Register(replyServiceName, &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "人工智能回复\n" +
"- @Bot 任意文本(任意一句话回复)\n- 设置回复模式[青云客 | 小爱]\n- ",
"- @Bot 任意文本(任意一句话回复)\n- 设置回复模式[青云客|小爱]",
})
// 回复 @和包括名字
engine.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser).

123
plugin/antiabuse/anti.go Normal file
View File

@@ -0,0 +1,123 @@
// Package antiabuse defines antiabuse plugin ,support abuse words check and add/remove abuse words
package antiabuse
import (
"strconv"
"strings"
"time"
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/ttl"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const bandur time.Duration = time.Minute * 10
var (
managers *ctrl.Manager[*zero.Ctx] // managers lazy load
cache = ttl.NewCacheOn(bandur, [4]func(int64, struct{}){nil, nil, onDel, nil})
db *antidb
)
func onDel(uid int64, _ struct{}) {
if managers == nil {
return
}
if err := managers.DoUnblock(uid); err != nil {
logrus.Errorln("[antiabuse.onDel] unblock:", err)
}
if err := db.Del("__bantime__", "WHERE id="+strconv.FormatInt(uid, 10)); err != nil {
logrus.Errorln("[antiabuse.onDel] db:", err)
}
}
func init() {
engine := control.Register("antiabuse", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "违禁词检测\n- /[添加|删除|查看]违禁词",
PrivateDataFolder: "anti_abuse",
})
onceRule := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
managers = ctx.State["manager"].(*ctrl.Control[*zero.Ctx]).Manager
var err error
db, err = newantidb(engine.DataFolder() + "anti_abuse.db")
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
return true
})
engine.OnMessage(onceRule, zero.OnlyGroup, func(ctx *zero.Ctx) bool {
if !ctx.Event.IsToMe {
return true
}
uid := ctx.Event.UserID
gid := ctx.Event.GroupID
msg := strings.ReplaceAll(ctx.MessageString(), "\n", "")
msg = strings.ReplaceAll(msg, "\r", "")
msg = strings.ReplaceAll(msg, "\t", "")
msg = strings.ReplaceAll(msg, ";", "")
if db.isInAntiList(gid, msg) {
if err := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]).Manager.DoBlock(uid); err == nil {
t := time.Now().Unix()
cache.Set(uid, struct{}{})
ctx.SetGroupBan(gid, uid, int64(bandur.Minutes()))
ctx.SendChain(message.Text("检测到违禁词, 已封禁/屏蔽", bandur))
db.Lock()
defer db.Unlock()
err := db.Create("__bantime__", nilbt)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Insert("__bantime__", &banTime{ID: uid, Time: t})
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
} else {
ctx.SendChain(message.Text("ERROR: block user: ", err))
}
return false
}
return true
})
engine.OnCommand("添加违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle(
func(ctx *zero.Ctx) {
args := ctx.State["args"].(string)
if err := db.insertWord(ctx.Event.GroupID, args); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
} else {
ctx.SendChain(message.Text("成功"))
}
})
engine.OnCommand("删除违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle(
func(ctx *zero.Ctx) {
args := ctx.State["args"].(string)
if err := db.deleteWord(ctx.Event.GroupID, args); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
} else {
ctx.SendChain(message.Text("成功"))
}
})
engine.OnCommand("查看违禁词", zero.OnlyGroup, onceRule).Handle(
func(ctx *zero.Ctx) {
b, err := text.RenderToBase64(db.listWords(ctx.Event.GroupID), text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("本群违禁词有\n"), message.Image("base64://"+binary.BytesToString(b)))
})
}

102
plugin/antiabuse/db.go Normal file
View File

@@ -0,0 +1,102 @@
package antiabuse
import (
"errors"
"strconv"
"strings"
"sync"
"time"
sqlite "github.com/FloatTech/sqlite"
)
type antidb struct {
sync.RWMutex
sqlite.Sqlite
}
type banWord struct {
Word string `db:"word"`
}
type banTime struct {
ID int64 `db:"id"`
Time int64 `db:"time"`
}
var (
nilban = &banWord{}
nilbt = &banTime{}
)
func newantidb(path string) (*antidb, error) {
db := &antidb{Sqlite: sqlite.Sqlite{DBPath: path}}
err := db.Open(bandur)
if err != nil {
return nil, err
}
_ = db.FindFor("__bantime__", nilbt, "", func() error {
t := time.Unix(nilbt.Time, 0)
ttl := time.Until(t.Add(bandur))
if ttl < time.Minute {
_ = managers.DoUnblock(nilbt.ID)
return nil
}
cache.Set(nilbt.ID, struct{}{})
cache.Touch(nilbt.ID, -time.Since(t))
return nil
})
_ = db.Del("__bantime__", "WHERE time<="+strconv.FormatInt(time.Now().Add(time.Minute-bandur).Unix(), 10))
return db, nil
}
func (db *antidb) isInAntiList(gid int64, msg string) bool {
grp := strconv.FormatInt(gid, 36)
db.RLock()
defer db.RUnlock()
return db.CanFind(grp, "WHERE instr('"+msg+"', word)>0")
}
func (db *antidb) insertWord(gid int64, word string) error {
grp := strconv.FormatInt(gid, 36)
db.Lock()
defer db.Unlock()
err := db.Create(grp, nilban)
if err != nil {
return err
}
return db.Insert(grp, &banWord{Word: word})
}
func (db *antidb) deleteWord(gid int64, word string) error {
grp := strconv.FormatInt(gid, 36)
db.Lock()
defer db.Unlock()
if n, _ := db.Count(grp); n == 0 {
return errors.New("本群还没有违禁词~")
}
return db.Del(grp, "WHERE word='"+word+"'")
}
func (db *antidb) listWords(gid int64) string {
grp := strconv.FormatInt(gid, 36)
word := &banWord{}
sb := strings.Builder{}
sb.WriteByte('[')
i := 0
db.RLock()
defer db.RUnlock()
_ = db.FindFor(grp, word, "", func() error {
if i > 0 {
sb.WriteString(" | ")
}
sb.WriteString(word.Word)
i++
return nil
})
if sb.Len() <= 4 {
return "[]"
}
sb.WriteByte(']')
return sb.String()
}

View File

@@ -12,9 +12,9 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/process"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/process"
)
const (

View File

@@ -19,7 +19,7 @@ func init() {
Help: "base16384加解密\n" +
"- 加密xxx\n- 解密xxx\n- 用yyy加密xxx\n- 用yyy解密xxx",
})
en.OnRegex(`^加密\s*(.*)`).SetBlock(true).
en.OnRegex(`^加密\s*(.+)$`).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
str := ctx.State["regex_matched"].([]string)[1]
es := base14.EncodeString(str)
@@ -29,7 +29,7 @@ func init() {
ctx.SendChain(message.Text("加密失败!"))
}
})
en.OnRegex(`^解密\s*([一-踀]*[㴁-㴆]?)$`).SetBlock(true).
en.OnRegex(`^解密\s*([一-踀]+[㴁-㴆]?)$`).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
str := ctx.State["regex_matched"].([]string)[1]
es := base14.DecodeString(str)
@@ -39,7 +39,7 @@ func init() {
ctx.SendChain(message.Text("解密失败!"))
}
})
en.OnRegex(`^用(.*)加密\s*(.*)`).SetBlock(true).
en.OnRegex(`^用(.+)加密\s*(.+)$`).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2]
t := getea(key)
@@ -50,7 +50,7 @@ func init() {
ctx.SendChain(message.Text("加密失败!"))
}
})
en.OnRegex(`^用(.*)解密\s*([一-踀]*[㴁-㴆]?)$`).SetBlock(true).
en.OnRegex(`^用(.+)解密\s*([一-踀]+[㴁-㴆]?)$`).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
key, str := ctx.State["regex_matched"].([]string)[1], ctx.State["regex_matched"].([]string)[2]
t := getea(key)

View File

@@ -3,11 +3,12 @@ package bilibili
import (
"encoding/json"
"errors"
"io"
"fmt"
"net/http"
"strconv"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/web"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/web"
"github.com/tidwall/gjson"
)
@@ -15,48 +16,40 @@ var (
errNeedCookie = errors.New("该api需要设置b站cookie请发送命令设置cookie例如\"设置b站cookie SESSDATA=82da790d,1663822823,06ecf*31\"")
)
type searchResult struct {
Mid int64 `json:"mid"`
Uname string `json:"uname"`
Gender int64 `json:"gender"`
Usign string `json:"usign"`
Level int64 `json:"level"`
}
// 搜索api通过把触发指令传入的昵称找出uid返回
func search(keyword string) (r []searchResult, err error) {
searchURL := "http://api.bilibili.com/x/web-interface/search/type?search_type=bili_user&keyword=" + keyword
data, err := web.GetData(searchURL)
// searchUser 查找b站用户
func searchUser(keyword string) (r []searchResult, err error) {
client := &http.Client{}
req, err := http.NewRequest("GET", fmt.Sprintf(searchUserURL, keyword), nil)
if err != nil {
return
}
j := gjson.ParseBytes(data)
if j.Get("data.numResults").Int() == 0 {
err = errors.New("查无此人")
return
}
err = json.Unmarshal(binary.StringToBytes(j.Get("data.result").Raw), &r)
err = reflushBilibiliCookie()
if err != nil {
return
}
req.Header.Add("cookie", cfg.BilibiliCookie)
res, err := client.Do(req)
if err != nil {
return
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
s := fmt.Sprintf("status code: %d", res.StatusCode)
err = errors.New(s)
return
}
var sd searchData
err = json.NewDecoder(res.Body).Decode(&sd)
if err != nil {
return
}
r = sd.Data.Result
return
}
type follower struct {
Mid int `json:"mid"`
Uname string `json:"uname"`
Video int `json:"video"`
Roomid int `json:"roomid"`
Rise int `json:"rise"`
Follower int `json:"follower"`
GuardNum int `json:"guardNum"`
AreaRank int `json:"areaRank"`
}
// 请求api
func fansapi(uid string) (result follower, err error) {
fanURL := "https://api.vtbs.moe/v1/detail/" + uid
data, err := web.GetData(fanURL)
// getVtbDetail 查找vtb信息
func getVtbDetail(uid string) (result vtbDetail, err error) {
data, err := web.GetData(fmt.Sprintf(vtbDetailURL, uid))
if err != nil {
return
}
@@ -66,76 +59,9 @@ func fansapi(uid string) (result follower, err error) {
return
}
func followings(uid string) (s string, err error) {
followingURL := "https://api.bilibili.com/x/relation/same/followings?vmid=" + uid
method := "GET"
client := &http.Client{}
req, err := http.NewRequest(method, followingURL, nil)
if err != nil {
return
}
c := vdb.getBilibiliCookie()
req.Header.Add("cookie", c.Value)
res, err := client.Do(req)
if err != nil {
return
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
return
}
j := gjson.ParseBytes(body)
s = j.Get("data.list.#.uname").Raw
if j.Get("code").Int() == -101 {
err = errNeedCookie
return
}
if j.Get("code").Int() != 0 {
err = errors.New(j.Get("message").String())
return
}
return
}
type userinfo struct {
Name string `json:"name"`
Mid string `json:"mid"`
Face string `json:"face"`
Fans int64 `json:"fans"`
Regtime int64 `json:"regtime"`
Attentions []int64 `json:"attentions"`
}
type medalInfo struct {
Mid int64 `json:"target_id"`
MedalName string `json:"medal_name"`
Level int64 `json:"level"`
MedalColorStart int64 `json:"medal_color_start"`
MedalColorEnd int64 `json:"medal_color_end"`
MedalColorBorder int64 `json:"medal_color_border"`
}
type medal struct {
Uname string `json:"target_name"`
medalInfo `json:"medal_info"`
}
type medalSlice []medal
func (m medalSlice) Len() int {
return len(m)
}
func (m medalSlice) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func (m medalSlice) Less(i, j int) bool {
return m[i].Level > m[j].Level
}
// 获取详情
func card(uid string) (result userinfo, err error) {
cardURL := "https://account.bilibili.com/api/member/getCardByMid?mid=" + uid
data, err := web.GetData(cardURL)
// getMemberCard 获取b站个人详情
func getMemberCard(uid interface{}) (result memberCard, err error) {
data, err := web.GetData(fmt.Sprintf(memberCardURL, uid))
if err != nil {
return
}
@@ -146,34 +72,73 @@ func card(uid string) (result userinfo, err error) {
return
}
// 获得牌子
func medalwall(uid string) (result []medal, err error) {
medalwallURL := "https://api.live.bilibili.com/xlive/web-ucenter/user/MedalWall?target_id=" + uid
method := "GET"
// getMedalwall 用b站uid获得牌子
func getMedalwall(uid string) (result []medal, err error) {
client := &http.Client{}
req, err := http.NewRequest(method, medalwallURL, nil)
req, err := http.NewRequest("GET", fmt.Sprintf(medalwallURL, uid), nil)
if err != nil {
return
}
c := vdb.getBilibiliCookie()
req.Header.Add("cookie", c.Value)
err = reflushBilibiliCookie()
if err != nil {
return
}
req.Header.Add("cookie", cfg.BilibiliCookie)
res, err := client.Do(req)
if err != nil {
return
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
var md medalData
err = json.NewDecoder(res.Body).Decode(&md)
if err != nil {
return
}
j := gjson.ParseBytes(data)
if j.Get("code").Int() == -101 {
if md.Code == -101 {
err = errNeedCookie
return
}
if j.Get("code").Int() != 0 {
err = errors.New(j.Get("message").String())
if md.Code != 0 {
err = errors.New(md.Message)
}
_ = json.Unmarshal(binary.StringToBytes(j.Get("data.list").Raw), &result)
result = md.Data.List
return
}
// getArticleInfo 用id查专栏信息
func getArticleInfo(id string) (card Card, err error) {
var data []byte
data, err = web.GetData(fmt.Sprintf(articleInfoURL, id))
if err != nil {
return
}
err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data").Raw), &card)
return
}
// getLiveRoomInfo 用直播间id查直播间信息
func getLiveRoomInfo(roomID string) (card roomCard, err error) {
var data []byte
data, err = web.GetData(fmt.Sprintf(liveRoomInfoURL, roomID))
if err != nil {
return
}
err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data").Raw), &card)
return
}
// getVideoInfo 用av或bv查视频信息
func getVideoInfo(id string) (card Card, err error) {
var data []byte
_, err = strconv.Atoi(id)
if err == nil {
data, err = web.GetData(fmt.Sprintf(videoInfoURL, id, ""))
} else {
data, err = web.GetData(fmt.Sprintf(videoInfoURL, "", id))
}
if err != nil {
return
}
err = json.Unmarshal(binary.StringToBytes(gjson.ParseBytes(data).Get("data").Raw), &card)
return
}

617
plugin/bilibili/bilibili.go Normal file
View File

@@ -0,0 +1,617 @@
// Package bilibili 查询b站用户信息
package bilibili
import (
"crypto/tls"
"encoding/binary"
"encoding/json"
"fmt"
"image"
"image/color"
"net/http"
"os"
"path"
"regexp"
"sort"
"strconv"
"time"
"github.com/Coloured-glaze/gg"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/img/writer"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img"
"github.com/FloatTech/zbputils/img/text"
log "github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
var (
re = regexp.MustCompile(`^\d+$`)
danmakuTypeMap = map[int64]string{
0: "普通消息",
1: "礼物",
2: "上舰",
3: "Superchat",
4: "进入直播间",
5: "标题变动",
}
cfgFile = "data/Bilibili/config.json"
cfg config
)
// 查成分的
func init() {
engine := control.Register("bilibili", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "bilibili (412就是拦截的意思,建议私聊把cookie设全)\n" +
"- >vup info [xxx]\n" +
"- >user info [xxx]\n" +
"- 查成分 [xxx]\n" +
"- 查弹幕 [xxx]\n" +
"- 设置b站cookie SESSDATA=82da790d,1663822823,06ecf*31\n" +
"- 更新vup",
PublicDataFolder: "Bilibili",
})
cachePath := engine.DataFolder() + "cache/"
_ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
var getdb = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
var err error
_, _ = engine.GetLazyData("bilibili.db", false)
vdb, err = initializeVup(engine.DataFolder() + "bilibili.db")
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
return true
})
engine.OnRegex(`^>user info\s?(.{1,25})$`, getPara).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
id := ctx.State["uid"].(string)
card, err := getMemberCard(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(
"uid: ", card.Mid, "\n",
"name: ", card.Name, "\n",
"sex: ", card.Sex, "\n",
"sign: ", card.Sign, "\n",
"level: ", card.LevelInfo.CurrentLevel, "\n",
"birthday: ", card.Birthday,
))
})
engine.OnRegex(`^>vup info\s?(.{1,25})$`, getPara).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
id := ctx.State["uid"].(string)
// 获取详情
fo, err := getVtbDetail(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(
"b站id: ", fo.Mid, "\n",
"名字: ", fo.Uname, "\n",
"当前粉丝数: ", fo.Follower, "\n",
"24h涨粉数: ", fo.Rise, "\n",
"视频投稿数: ", fo.Video, "\n",
"直播间id: ", fo.Roomid, "\n",
"舰队: ", fo.GuardNum, "\n",
"直播总排名: ", fo.AreaRank, "\n",
"数据来源: ", "https://vtbs.moe/detail/", fo.Mid, "\n",
"数据获取时间: ", time.Now().Format("2006-01-02 15:04:05"),
))
})
engine.OnRegex(`^查成分\s?(.{1,25})$`, getPara, getdb).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
id := ctx.State["uid"].(string)
today := time.Now().Format("20060102")
drawedFile := cachePath + id + today + "vupLike.png"
if file.IsExist(drawedFile) {
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
return
}
u, err := getMemberCard(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
vups, err := vdb.filterVup(u.Attentions)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
vupLen := len(vups)
medals, err := getMedalwall(id)
sort.Sort(medalSlice(medals))
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
}
frontVups := make([]vup, 0)
medalMap := make(map[int64]medal)
for _, v := range medals {
up := vup{
Mid: v.Mid,
Uname: v.Uname,
}
frontVups = append(frontVups, up)
medalMap[v.Mid] = v
}
vups = append(vups, frontVups...)
copy(vups[len(frontVups):], vups)
copy(vups, frontVups)
for i := len(frontVups); i < len(vups); i++ {
if _, ok := medalMap[vups[i].Mid]; ok {
vups = append(vups[:i], vups[i+1:]...)
i--
}
}
facePath := cachePath + id + "vupFace" + path.Ext(u.Face)
backX := 500
backY := 500
var back image.Image
if path.Ext(u.Face) != ".webp" {
err = initFacePic(facePath, u.Face)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
back, err = gg.LoadImage(facePath)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
back = img.Size(back, backX, backY).Im
}
if len(vups) > 50 {
ctx.SendChain(message.Text(u.Name + "关注的up主太多了只展示前50个up"))
vups = vups[:50]
}
canvas := gg.NewContext(1500, int(500*(1.1+float64(len(vups))/3)))
fontSize := 50.0
canvas.SetColor(color.White)
canvas.Clear()
if back != nil {
canvas.DrawImage(back, 0, 0)
}
canvas.SetColor(color.Black)
_, err = file.GetLazyData(text.BoldFontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
}
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
sl, _ := canvas.MeasureString("好")
length, h := canvas.MeasureString(u.Mid)
n, _ := canvas.MeasureString(u.Name)
canvas.DrawString(u.Name, 550, 160-h)
canvas.DrawRoundedRectangle(600+n-length*0.1, 160-h*2.5, length*1.2, h*2, fontSize*0.2)
canvas.SetRGB255(221, 221, 221)
canvas.Fill()
canvas.SetColor(color.Black)
canvas.DrawString(u.Mid, 600+n, 160-h)
canvas.DrawString(fmt.Sprintf("粉丝:%d", u.Fans), 550, 240-h)
canvas.DrawString(fmt.Sprintf("关注:%d", len(u.Attentions)), 1000, 240-h)
canvas.DrawString(fmt.Sprintf("管人痴成分:%.2f%%%d/%d", float64(vupLen)/float64(len(u.Attentions))*100, vupLen, len(u.Attentions)), 550, 320-h)
regtime := time.Unix(u.Regtime, 0).Format("2006-01-02 15:04:05")
canvas.DrawString("注册日期:"+regtime, 550, 400-h)
canvas.DrawString("查询日期:"+time.Now().Format("2006-01-02"), 550, 480-h)
for i, v := range vups {
if i%2 == 1 {
canvas.SetRGB255(245, 245, 245)
canvas.DrawRectangle(0, float64(backY)*1.1+float64(i)*float64(backY)/3, float64(backX*3), float64(backY)/3)
canvas.Fill()
}
canvas.SetColor(color.Black)
nl, _ := canvas.MeasureString(v.Uname)
canvas.DrawString(v.Uname, float64(backX)*0.1, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
ml, _ := canvas.MeasureString(strconv.FormatInt(v.Mid, 10))
canvas.DrawRoundedRectangle(nl-0.1*ml+float64(backX)*0.2, float64(backY)*1.1+float64(i+1)*float64(backY)/3-h*3.5, ml*1.2, h*2, fontSize*0.2)
canvas.SetRGB255(221, 221, 221)
canvas.Fill()
canvas.SetColor(color.Black)
canvas.DrawString(strconv.FormatInt(v.Mid, 10), nl+float64(backX)*0.2, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
if m, ok := medalMap[v.Mid]; ok {
mnl, _ := canvas.MeasureString(m.MedalName)
grad := gg.NewLinearGradient(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h, nl+ml+mnl+sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
r, g, b := int2rbg(m.MedalColorStart)
grad.AddColorStop(0, color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 255})
r, g, b = int2rbg(m.MedalColorEnd)
grad.AddColorStop(1, color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 255})
canvas.SetFillStyle(grad)
canvas.SetLineWidth(4)
canvas.MoveTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.LineTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.ClosePath()
canvas.Fill()
canvas.SetColor(color.White)
canvas.DrawString(m.MedalName, nl+ml+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
r, g, b = int2rbg(m.MedalColorBorder)
canvas.SetRGB255(int(r), int(g), int(b))
canvas.DrawString(strconv.FormatInt(m.Level, 10), nl+ml+mnl+sl+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
mll, _ := canvas.MeasureString(strconv.FormatInt(m.Level, 10))
canvas.SetLineWidth(4)
canvas.MoveTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+mll+sl/2+float64(backX)*0.5, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+mll+sl/2+float64(backX)*0.5, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.LineTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.ClosePath()
canvas.Stroke()
}
}
f, err := os.Create(drawedFile)
if err != nil {
log.Errorln("[bilibili]", err)
data, cl := writer.ToBytes(canvas.Image())
ctx.SendChain(message.ImageBytes(data))
cl()
return
}
_, err = writer.WriteTo(canvas.Image(), f)
_ = f.Close()
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
})
engine.OnRegex(`^查弹幕\s?(\S{1,25})\s?(\d*)$`, getPara).SetBlock(true).Handle(func(ctx *zero.Ctx) {
id := ctx.State["uid"].(string)
pagenum := ctx.State["regex_matched"].([]string)[2]
if pagenum == "" {
pagenum = "0"
}
u, err := getMemberCard(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
var danmaku danmakusuki
tr := &http.Transport{
DisableKeepAlives: true,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
data, err := web.RequestDataWith(client, fmt.Sprintf(danmakuAPI, id, pagenum), "GET", "", web.RandUA())
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
err = json.Unmarshal(data, &danmaku)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
today := time.Now().Format("20060102150415")
drawedFile := cachePath + id + today + "vupLike.png"
facePath := cachePath + id + "vupFace" + path.Ext(u.Face)
backX := 500
backY := 500
var back image.Image
if path.Ext(u.Face) != ".webp" {
err = initFacePic(facePath, u.Face)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
back, err = gg.LoadImage(facePath)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
back = img.Size(back, backX, backY).Im
}
canvas := gg.NewContext(100, 100)
fontSize := 50.0
_, err = file.GetLazyData(text.BoldFontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
}
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
dz, h := canvas.MeasureString("好")
danmuH := h * 2
faceH := float64(510)
totalDanmuku := 0
for i := 0; i < len(danmaku.Data.Data); i++ {
totalDanmuku += len(danmaku.Data.Data[i].Danmakus) + 1
}
cw := 10000
mcw := float64(2000)
ch := 550 + len(danmaku.Data.Data)*int(faceH) + totalDanmuku*int(danmuH)
canvas = gg.NewContext(cw, ch)
canvas.SetColor(color.White)
canvas.Clear()
canvas.SetColor(color.Black)
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
facestart := 100
fontH := h * 1.6
startWidth := float64(700)
startWidth2 := float64(20)
if back != nil {
canvas.DrawImage(back, facestart, 0)
}
length, _ := canvas.MeasureString(u.Mid)
n, _ := canvas.MeasureString(u.Name)
canvas.DrawString(u.Name, startWidth, 122.5)
canvas.DrawRoundedRectangle(900+n-length*0.1, 66, length*1.2, 75, fontSize*0.2)
canvas.SetRGB255(221, 221, 221)
canvas.Fill()
canvas.SetColor(color.Black)
canvas.DrawString(u.Mid, 900+n, 122.5)
canvas.DrawString(fmt.Sprintf("粉丝:%d 关注:%d", u.Fans, u.Attention), startWidth, 222.5)
canvas.DrawString(fmt.Sprintf("页码:[%d/%d]", danmaku.Data.PageNum, (danmaku.Data.Total-1)/5), startWidth, 322.5)
canvas.DrawString("网页链接: "+fmt.Sprintf(danmakuURL, u.Mid), startWidth, 422.5)
var channelStart float64
channelStart = float64(550)
for i := 0; i < len(danmaku.Data.Data); i++ {
item := danmaku.Data.Data[i]
facePath = cachePath + strconv.Itoa(int(item.Channel.UID)) + "vupFace" + path.Ext(item.Channel.FaceURL)
if path.Ext(item.Channel.FaceURL) != ".webp" {
err = initFacePic(facePath, item.Channel.FaceURL)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
back, err = gg.LoadImage(facePath)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
back = img.Size(back, backX, backY).Im
}
if back != nil {
canvas.DrawImage(back, facestart, int(channelStart))
}
canvas.SetRGB255(24, 144, 255)
canvas.DrawString("标题: "+item.Live.Title, startWidth, channelStart+fontH)
canvas.DrawString("主播: "+item.Channel.Name, startWidth, channelStart+fontH*2)
canvas.SetColor(color.Black)
canvas.DrawString("开始时间: "+time.UnixMilli(item.Live.StartDate).Format("2006-01-02 15:04:05"), startWidth, channelStart+fontH*3)
if item.Live.IsFinish {
canvas.DrawString("结束时间: "+time.UnixMilli(item.Live.StopDate).Format("2006-01-02 15:04:05"), startWidth, channelStart+fontH*4)
canvas.DrawString("直播时长: "+strconv.FormatFloat(float64(item.Live.StopDate-item.Live.StartDate)/3600000.0, 'f', 1, 64)+"小时", startWidth, channelStart+fontH*5)
} else {
t := "结束时间:"
l, _ := canvas.MeasureString(t)
canvas.DrawString(t, startWidth, channelStart+fontH*4)
canvas.SetRGB255(0, 128, 0)
t = "正在直播"
canvas.DrawString(t, startWidth+l*1.1, channelStart+fontH*4)
canvas.SetColor(color.Black)
canvas.DrawString("直播时长: "+strconv.FormatFloat(float64(time.Now().UnixMilli()-item.Live.StartDate)/3600000.0, 'f', 1, 64)+"小时", startWidth, channelStart+fontH*5)
}
canvas.DrawString("弹幕数量: "+strconv.Itoa(int(item.Live.DanmakusCount)), startWidth, channelStart+fontH*6)
canvas.DrawString("观看次数: "+strconv.Itoa(int(item.Live.WatchCount)), startWidth, channelStart+fontH*7)
t := "收益:"
l, _ := canvas.MeasureString(t)
canvas.DrawString(t, startWidth, channelStart+fontH*8)
t = "¥" + strconv.Itoa(int(item.Live.TotalIncome))
canvas.SetRGB255(255, 0, 0)
canvas.DrawString(t, startWidth+l*1.1, channelStart+fontH*8)
canvas.SetColor(color.Black)
DanmakuStart := channelStart + faceH
for i := 0; i < len(item.Danmakus); i++ {
moveW := startWidth2
danmuNow := DanmakuStart + danmuH*float64(i+1)
danItem := item.Danmakus[i]
t := time.UnixMilli(danItem.SendDate).Format("15:04:05")
l, _ := canvas.MeasureString(t)
canvas.DrawString(t, moveW, danmuNow)
moveW += l + dz
t = danItem.Name
l, _ = canvas.MeasureString(t)
canvas.SetRGB255(24, 144, 255)
canvas.DrawString(t, moveW, danmuNow)
canvas.SetColor(color.Black)
moveW += l + dz
switch danItem.Type {
case 0:
t = danItem.Message
l, _ = canvas.MeasureString(t)
canvas.DrawString(t, moveW, danmuNow)
moveW += l + dz
case 1:
t = danmakuTypeMap[danItem.Type]
l, _ = canvas.MeasureString(t)
canvas.SetRGB255(255, 0, 0)
canvas.DrawString(t, moveW, danmuNow)
moveW += l + dz
t = danItem.Message
l, _ = canvas.MeasureString(t)
canvas.DrawString(t, moveW, danmuNow)
canvas.SetColor(color.Black)
moveW += l + dz
case 2, 3:
t = danmakuTypeMap[danItem.Type]
l, _ = canvas.MeasureString(t)
if danItem.Type == 3 {
canvas.SetRGB255(0, 85, 255)
} else {
canvas.SetRGB255(128, 0, 128)
}
canvas.DrawString(t, moveW, danmuNow)
moveW += l + dz
t = danItem.Message
l, _ = canvas.MeasureString(t)
canvas.DrawString(t, moveW, danmuNow)
moveW += l
t = "["
l, _ = canvas.MeasureString(t)
canvas.DrawString(t, moveW, danmuNow)
moveW += l
t = "¥" + strconv.FormatFloat(danItem.Price, 'f', 1, 64)
l, _ = canvas.MeasureString(t)
canvas.SetRGB255(255, 0, 0)
canvas.DrawString(t, moveW, danmuNow)
if danItem.Type == 3 {
canvas.SetRGB255(0, 85, 255)
} else {
canvas.SetRGB255(128, 0, 128)
}
moveW += l
t = "]"
l, _ = canvas.MeasureString(t)
canvas.DrawString(t, moveW, danmuNow)
canvas.SetColor(color.Black)
moveW += l + dz
case 4, 5:
t = danmakuTypeMap[danItem.Type]
canvas.SetRGB255(0, 128, 0)
l, _ = canvas.MeasureString(t)
canvas.DrawString(t, moveW, danmuNow)
canvas.SetColor(color.Black)
moveW += l + dz
}
if moveW > mcw {
mcw = moveW
}
}
channelStart = DanmakuStart + float64(len(item.Danmakus)+1)*danmuH
}
im := canvas.Image().(*image.RGBA)
nim := im.SubImage(image.Rect(0, 0, int(mcw), ch))
f, err := os.Create(drawedFile)
if err != nil {
log.Errorln("[bilibili]", err)
data, cl := writer.ToBytes(nim)
ctx.SendChain(message.ImageBytes(data))
cl()
return
}
_, err = writer.WriteTo(nim, f)
_ = f.Close()
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
})
engine.OnRegex(`^设置b站cookie?\s+(.*)$`, zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
cookie := ctx.State["regex_matched"].([]string)[1]
err := setBilibiliCookie(cookie)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("成功设置b站cookie为" + cookie))
})
engine.OnFullMatch("更新vup", zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
ctx.SendChain(message.Text("少女祈祷中..."))
err := updateVup()
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("vup已更新"))
})
}
func initFacePic(filename, faceURL string) error {
if file.IsNotExist(filename) {
data, err := web.GetData(faceURL)
if err != nil {
return err
}
err = os.WriteFile(filename, data, 0666)
if err != nil {
return err
}
}
return nil
}
func int2rbg(t int64) (int64, int64, int64) {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], uint64(t))
b, g, r := int64(buf[0]), int64(buf[1]), int64(buf[2])
return r, g, b
}
func getPara(ctx *zero.Ctx) bool {
keyword := ctx.State["regex_matched"].([]string)[1]
if !re.MatchString(keyword) {
searchRes, err := searchUser(keyword)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
ctx.State["uid"] = strconv.FormatInt(searchRes[0].Mid, 10)
return true
}
next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession())
recv, cancel := next.Repeat()
defer cancel()
ctx.SendChain(message.Text("输入为纯数字请选择查询uid还是用户名输入对应序号\n0. 查询uid\n1. 查询用户名"))
for {
select {
case <-time.After(time.Second * 10):
ctx.SendChain(message.Text("时间太久啦!", zero.BotConfig.NickName[0], "帮你选择查询uid"))
ctx.State["uid"] = keyword
return true
case c := <-recv:
msg := c.Event.Message.ExtractPlainText()
num, err := strconv.Atoi(msg)
if err != nil {
ctx.SendChain(message.Text("请输入数字!"))
continue
}
if num < 0 || num > 1 {
ctx.SendChain(message.Text("序号非法!"))
continue
}
if num == 0 {
ctx.State["uid"] = keyword
return true
} else if num == 1 {
searchRes, err := searchUser(keyword)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
ctx.State["uid"] = strconv.FormatInt(searchRes[0].Mid, 10)
return true
}
}
}
}

View File

@@ -0,0 +1,106 @@
// Package bilibili bilibili卡片解析
package bilibili
import (
"regexp"
"time"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
var (
limit = ctxext.NewLimiterManager(time.Second*10, 1)
searchVideo = `bilibili.com\\?/video\\?/(?:av(\d+)|([bB][vV][0-9a-zA-Z]+))`
searchDynamic = `(t.bilibili.com|m.bilibili.com\\?/dynamic)\\?/(\d+)`
searchArticle = `bilibili.com\\?/read\\?/(?:cv|mobile\\?/)(\d+)`
searchLiveRoom = `live.bilibili.com\\?/(\d+)`
searchVideoRe = regexp.MustCompile(searchVideo)
searchDynamicRe = regexp.MustCompile(searchDynamic)
searchArticleRe = regexp.MustCompile(searchArticle)
searchLiveRoomRe = regexp.MustCompile(searchLiveRoom)
)
// 插件主体
func init() {
en := control.Register("bilibiliparse", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "b站动态、专栏、视频、直播解析\n" +
"- t.bilibili.com/642277677329285174 | bilibili.com/read/cv17134450 | bilibili.com/video/BV13B4y1x7pS | live.bilibili.com/22603245 ",
})
en.OnRegex(`((b23|acg).tv|bili2233.cn)/[0-9a-zA-Z]+`).SetBlock(true).Limit(limit.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
url := ctx.State["regex_matched"].([]string)[0]
realurl, err := getrealurl("https://" + url)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
switch {
case searchVideoRe.MatchString(realurl):
ctx.State["regex_matched"] = searchVideoRe.FindStringSubmatch(realurl)
handleVideo(ctx)
case searchDynamicRe.MatchString(realurl):
ctx.State["regex_matched"] = searchDynamicRe.FindStringSubmatch(realurl)
handleDynamic(ctx)
case searchArticleRe.MatchString(realurl):
ctx.State["regex_matched"] = searchArticleRe.FindStringSubmatch(realurl)
handleArticle(ctx)
case searchLiveRoomRe.MatchString(realurl):
ctx.State["regex_matched"] = searchLiveRoomRe.FindStringSubmatch(realurl)
handleLive(ctx)
}
})
en.OnRegex(searchVideo).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleVideo)
en.OnRegex(searchDynamic).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleDynamic)
en.OnRegex(searchArticle).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleArticle)
en.OnRegex(searchLiveRoom).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleLive)
}
func handleVideo(ctx *zero.Ctx) {
id := ctx.State["regex_matched"].([]string)[1]
if id == "" {
id = ctx.State["regex_matched"].([]string)[2]
}
card, err := getVideoInfo(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
msg, err := videoCard2msg(card)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(msg...)
}
func handleDynamic(ctx *zero.Ctx) {
msg, err := dynamicDetail(ctx.State["regex_matched"].([]string)[2])
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(msg...)
}
func handleArticle(ctx *zero.Ctx) {
card, err := getArticleInfo(ctx.State["regex_matched"].([]string)[1])
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(articleCard2msg(card, ctx.State["regex_matched"].([]string)[1])...)
}
func handleLive(ctx *zero.Ctx) {
card, err := getLiveRoomInfo(ctx.State["regex_matched"].([]string)[1])
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(liveCard2msg(card)...)
}

View File

@@ -1,19 +1,18 @@
package bilibili
import (
"encoding/json"
"errors"
"os"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/web"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/web"
_ "github.com/fumiama/sqlite3" // use sql
"github.com/jinzhu/gorm"
"github.com/tidwall/gjson"
)
const (
bilibiliCookie = "bilbili_cookie"
)
var (
vtbURLs = [...]string{"https://api.vtbs.moe/v1/short", "https://api.tokyo.vtbs.moe/v1/short", "https://vtbs.musedash.moe/v1/short"}
vdb *vupdb
@@ -32,17 +31,8 @@ func (vup) TableName() string {
return "vup"
}
type config struct {
Key string `gorm:"column:key;primary_key"`
Value string `gorm:"column:value"`
}
func (config) TableName() string {
return "config"
}
// initialize 初始化vtb数据库
func initialize(dbpath string) (*vupdb, error) {
// initializeVup 初始化vup数据库
func initializeVup(dbpath string) (*vupdb, error) {
if _, err := os.Stat(dbpath); err != nil || os.IsNotExist(err) {
// 生成文件
f, err := os.Create(dbpath)
@@ -55,7 +45,7 @@ func initialize(dbpath string) (*vupdb, error) {
if err != nil {
return nil, err
}
gdb.AutoMigrate(&vup{}).AutoMigrate(&config{})
gdb.AutoMigrate(&vup{})
return (*vupdb)(gdb), nil
}
@@ -103,28 +93,31 @@ func updateVup() error {
return nil
}
func (vdb *vupdb) setBilibiliCookie(cookie string) (err error) {
db := (*gorm.DB)(vdb)
c := config{
Key: bilibiliCookie,
Value: cookie,
func setBilibiliCookie(cookie string) (err error) {
cfg = config{
BilibiliCookie: cookie,
}
if err = db.Model(&config{}).First(&c, "key = ? ", bilibiliCookie).Error; err != nil {
// error handling...
if gorm.IsRecordNotFoundError(err) {
err = db.Model(&config{}).Create(&c).Error
}
} else {
err = db.Model(&config{}).Where("key = ? ", bilibiliCookie).Update(
map[string]interface{}{
"value": cookie,
}).Error
}
return
return saveConfig(cfg)
}
func (vdb *vupdb) getBilibiliCookie() (c config) {
db := (*gorm.DB)(vdb)
db.Model(&config{}).First(&c, "key = ?", bilibiliCookie)
return
func reflushBilibiliCookie() (err error) {
if file.IsNotExist(cfgFile) {
err = errors.New("未初始化配置")
return
}
reader, err := os.Open(cfgFile)
if err != nil {
return
}
defer reader.Close()
return json.NewDecoder(reader).Decode(&cfg)
}
func saveConfig(cfg config) (err error) {
reader, err := os.Create(cfgFile)
if err != nil {
return err
}
defer reader.Close()
return json.NewEncoder(reader).Encode(&cfg)
}

View File

@@ -0,0 +1,357 @@
// Package bilibili b站推送
package bilibili
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
)
const (
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
referer = "https://www.bilibili.com/"
infoURL = "https://api.bilibili.com/x/space/acc/info?mid=%v"
serviceName = "bilibilipush"
)
// bdb bilibili推送数据库
var bdb *bilibilipushdb
var (
lastTime = map[int64]int64{}
liveStatus = map[int64]int{}
upMap = map[int64]string{}
)
func init() {
en := control.Register(serviceName, &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "bilibilipush,需要配合job一起使用\n" +
"- 添加b站订阅[uid|name]\n" +
"- 取消b站订阅[uid|name]\n" +
"- 取消b站动态订阅[uid|name]\n" +
"- 取消b站直播订阅[uid|name]\n" +
"- b站推送列表\n" +
"- 拉取b站推送 (使用job执行定时任务------记录在\"@every 10s\"触发的指令)",
PrivateDataFolder: serviceName,
})
// 加载bilibili推送数据库
dbpath := en.DataFolder()
dbfile := dbpath + "push.db"
bdb = initializePush(dbfile)
en.OnRegex(`^添加b站订阅\s?(.{1,25})$`, zero.UserOrGrpAdmin, getPara).SetBlock(true).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["uid"].(string), 10, 64)
name, err := getName(buid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if err := subscribe(buid, gid); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("已添加" + name + "的订阅"))
})
en.OnRegex(`^取消b站订阅\s?(.{1,25})$`, zero.UserOrGrpAdmin, getPara).SetBlock(true).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["uid"].(string), 10, 64)
name, err := getName(buid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if err := unsubscribe(buid, gid); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("已取消" + name + "的订阅"))
})
en.OnRegex(`^取消b站动态订阅\s?(.{1,25})$`, zero.UserOrGrpAdmin, getPara).SetBlock(true).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["uid"].(string), 10, 64)
name, err := getName(buid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if err := unsubscribeDynamic(buid, gid); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("已取消" + name + "的动态订阅"))
})
en.OnRegex(`^取消b站直播订阅\s?(.{1,25})$`, zero.UserOrGrpAdmin, getPara).SetBlock(true).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["uid"].(string), 10, 64)
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
name, err := getName(buid)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if err := unsubscribeLive(buid, gid); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("已取消" + name + "的直播订阅"))
})
en.OnFullMatch("b站推送列表", zero.UserOrGrpAdmin).SetBlock(true).Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
bpl := bdb.getAllPushByGroup(gid)
msg := "--------b站推送列表--------"
for _, v := range bpl {
if _, ok := upMap[v.BilibiliUID]; !ok {
bdb.updateAllUp()
}
msg += fmt.Sprintf("\nuid:%-12d 动态:", v.BilibiliUID)
if v.DynamicDisable == 0 {
msg += "●"
} else {
msg += "○"
}
msg += " 直播:"
if v.LiveDisable == 0 {
msg += "●"
} else {
msg += "○"
}
msg += " up主" + upMap[v.BilibiliUID]
}
data, err := text.RenderToBase64(msg, text.FontFile, 600, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
en.OnFullMatch("拉取b站推送").SetBlock(true).Handle(func(ctx *zero.Ctx) {
err := sendDynamic(ctx)
if err != nil {
ctx.SendPrivateMessage(ctx.Event.UserID, message.Text("Error: bilibilipush,", err))
}
err = sendLive(ctx)
if err != nil {
ctx.SendPrivateMessage(ctx.Event.UserID, message.Text("Error: bilibilipush,", err))
}
})
}
// 取得uid的名字
func getName(buid int64) (name string, err error) {
var ok bool
if name, ok = upMap[buid]; !ok {
var data []byte
data, err = web.RequestDataWith(web.NewDefaultClient(), fmt.Sprintf(infoURL, buid), "GET", referer, ua)
if err != nil {
return
}
status := int(gjson.Get(binary.BytesToString(data), "code").Int())
if status != 0 {
err = errors.New(gjson.Get(binary.BytesToString(data), "message").String())
return
}
name = gjson.Get(binary.BytesToString(data), "data.name").String()
bdb.insertBilibiliUp(buid, name)
upMap[buid] = name
}
return
}
// subscribe 订阅
func subscribe(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"live_disable": 0,
"dynamic_disable": 0,
}
return bdb.insertOrUpdateLiveAndDynamic(bpMap)
}
// unsubscribe 取消订阅
func unsubscribe(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"live_disable": 1,
"dynamic_disable": 1,
}
return bdb.insertOrUpdateLiveAndDynamic(bpMap)
}
func unsubscribeDynamic(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"dynamic_disable": 1,
}
return bdb.insertOrUpdateLiveAndDynamic(bpMap)
}
func unsubscribeLive(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"live_disable": 1,
}
return bdb.insertOrUpdateLiveAndDynamic(bpMap)
}
func getUserDynamicCard(buid int64) (cardList []gjson.Result, err error) {
data, err := web.RequestDataWith(web.NewDefaultClient(), fmt.Sprintf(spaceHistoryURL, buid, 0), "GET", referer, ua)
if err != nil {
return
}
cardList = gjson.Get(binary.BytesToString(data), "data.cards").Array()
return
}
func getLiveList(uids ...int64) (string, error) {
m := make(map[string]interface{})
m["uids"] = uids
b, err := json.Marshal(m)
if err != nil {
return "", err
}
data, err := web.PostData(liveListURL, "application/json", bytes.NewReader(b))
if err != nil {
return "", err
}
return binary.BytesToString(data), nil
}
func sendDynamic(ctx *zero.Ctx) error {
uids := bdb.getAllBuidByDynamic()
for _, buid := range uids {
time.Sleep(2 * time.Second)
cardList, err := getUserDynamicCard(buid)
if err != nil {
return err
}
if len(cardList) == 0 {
return errors.Errorf("%v的历史动态数为0", buid)
}
t, ok := lastTime[buid]
// 第一次先记录时间,啥也不做
if !ok {
lastTime[buid] = cardList[0].Get("desc.timestamp").Int()
return nil
}
for i := len(cardList) - 1; i >= 0; i-- {
ct := cardList[i].Get("desc.timestamp").Int()
if ct > t && ct > time.Now().Unix()-600 {
lastTime[buid] = ct
m, ok := control.Lookup(serviceName)
if ok {
groupList := bdb.getAllGroupByBuidAndDynamic(buid)
msg, err := dynamicCard2msg(cardList[i].Raw, 0)
if err != nil {
err = errors.Errorf("动态%v的解析有问题,%v", cardList[i].Get("desc.dynamic_id_str"), err)
return err
}
for _, gid := range groupList {
if m.IsEnabledIn(gid) {
time.Sleep(time.Millisecond * 100)
switch {
case gid > 0:
ctx.SendGroupMessage(gid, msg)
case gid < 0:
ctx.SendPrivateMessage(-gid, msg)
}
}
}
}
}
}
}
return nil
}
func sendLive(ctx *zero.Ctx) error {
uids := bdb.getAllBuidByLive()
ll, err := getLiveList(uids...)
if err != nil {
return err
}
gjson.Get(ll, "data").ForEach(func(key, value gjson.Result) bool {
newStatus := int(value.Get("live_status").Int())
if newStatus == 2 {
newStatus = 0
}
if _, ok := liveStatus[key.Int()]; !ok {
liveStatus[key.Int()] = newStatus
return true
}
oldStatus := liveStatus[key.Int()]
if newStatus != oldStatus && newStatus == 1 {
liveStatus[key.Int()] = newStatus
m, ok := control.Lookup(serviceName)
if ok {
groupList := bdb.getAllGroupByBuidAndLive(key.Int())
roomID := value.Get("short_id").Int()
if roomID == 0 {
roomID = value.Get("room_id").Int()
}
lURL := liveURL + strconv.FormatInt(roomID, 10)
lName := value.Get("uname").String()
lTitle := value.Get("title").String()
lCover := value.Get("cover_from_user").String()
if lCover == "" {
lCover = value.Get("keyframe").String()
}
var msg []message.MessageSegment
msg = append(msg, message.Text(lName+" 正在直播:\n"))
msg = append(msg, message.Text(lTitle))
msg = append(msg, message.Image(lCover))
msg = append(msg, message.Text("直播链接:", lURL))
for _, gid := range groupList {
if m.IsEnabledIn(gid) {
time.Sleep(time.Millisecond * 100)
switch {
case gid > 0:
ctx.SendGroupMessage(gid, msg)
case gid < 0:
ctx.SendPrivateMessage(-gid, msg)
}
}
}
}
} else if newStatus != oldStatus {
liveStatus[key.Int()] = newStatus
}
return true
})
return nil
}

View File

@@ -0,0 +1,150 @@
package bilibili
import (
"encoding/json"
"os"
_ "github.com/fumiama/sqlite3" // import sql
"github.com/jinzhu/gorm"
)
// bilibilipushdb bilibili推送数据库
type bilibilipushdb gorm.DB
type bilibilipush struct {
ID int64 `gorm:"column:id;primary_key" json:"id"`
BilibiliUID int64 `gorm:"column:bilibili_uid;index:idx_buid_gid" json:"bilibili_uid"`
GroupID int64 `gorm:"column:group_id;index:idx_buid_gid" json:"group_id"`
LiveDisable int64 `gorm:"column:live_disable;default:0" json:"live_disable"`
DynamicDisable int64 `gorm:"column:dynamic_disable;default:0" json:"dynamic_disable"`
}
// TableName ...
func (bilibilipush) TableName() string {
return "bilibili_push"
}
type bilibiliup struct {
BilibiliUID int64 `gorm:"column:bilibili_uid;primary_key"`
Name string `gorm:"column:name"`
}
// TableName ...
func (bilibiliup) TableName() string {
return "bilibili_up"
}
// initializePush 初始化bilibilipushdb数据库
func initializePush(dbpath string) *bilibilipushdb {
var err error
if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) {
// 生成文件
f, err := os.Create(dbpath)
if err != nil {
return nil
}
defer f.Close()
}
gdb, err := gorm.Open("sqlite3", dbpath)
if err != nil {
panic(err)
}
gdb.AutoMigrate(&bilibilipush{}).AutoMigrate(&bilibiliup{})
return (*bilibilipushdb)(gdb)
}
// insertOrUpdateLiveAndDynamic 插入或更新数据库
func (bdb *bilibilipushdb) insertOrUpdateLiveAndDynamic(bpMap map[string]interface{}) (err error) {
db := (*gorm.DB)(bdb)
bp := bilibilipush{}
data, err := json.Marshal(&bpMap)
if err != nil {
return
}
err = json.Unmarshal(data, &bp)
if err != nil {
return
}
if err = db.Model(&bilibilipush{}).First(&bp, "bilibili_uid = ? and group_id = ?", bp.BilibiliUID, bp.GroupID).Error; err != nil {
if gorm.IsRecordNotFoundError(err) {
err = db.Model(&bilibilipush{}).Create(&bp).Error
}
} else {
err = db.Model(&bilibilipush{}).Where("bilibili_uid = ? and group_id = ?", bp.BilibiliUID, bp.GroupID).Update(bpMap).Error
}
return
}
func (bdb *bilibilipushdb) getAllBuidByLive() (buidList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Model(&bilibilipush{}).Find(&bpl, "live_disable = 0")
temp := make(map[int64]bool)
for _, v := range bpl {
_, ok := temp[v.BilibiliUID]
if !ok {
buidList = append(buidList, v.BilibiliUID)
temp[v.BilibiliUID] = true
}
}
return
}
func (bdb *bilibilipushdb) getAllBuidByDynamic() (buidList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Model(&bilibilipush{}).Find(&bpl, "dynamic_disable = 0")
temp := make(map[int64]bool)
for _, v := range bpl {
_, ok := temp[v.BilibiliUID]
if !ok {
buidList = append(buidList, v.BilibiliUID)
temp[v.BilibiliUID] = true
}
}
return
}
func (bdb *bilibilipushdb) getAllGroupByBuidAndLive(buid int64) (groupList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Model(&bilibilipush{}).Find(&bpl, "bilibili_uid = ? and live_disable = 0", buid)
for _, v := range bpl {
groupList = append(groupList, v.GroupID)
}
return
}
func (bdb *bilibilipushdb) getAllGroupByBuidAndDynamic(buid int64) (groupList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Model(&bilibilipush{}).Find(&bpl, "bilibili_uid = ? and dynamic_disable = 0", buid)
for _, v := range bpl {
groupList = append(groupList, v.GroupID)
}
return
}
func (bdb *bilibilipushdb) getAllPushByGroup(groupID int64) (bpl []bilibilipush) {
db := (*gorm.DB)(bdb)
db.Model(&bilibilipush{}).Find(&bpl, "group_id = ? and (live_disable = 0 or dynamic_disable = 0)", groupID)
return
}
func (bdb *bilibilipushdb) insertBilibiliUp(buid int64, name string) {
db := (*gorm.DB)(bdb)
bu := bilibiliup{
BilibiliUID: buid,
Name: name,
}
db.Model(&bilibiliup{}).Create(bu)
}
func (bdb *bilibilipushdb) updateAllUp() {
db := (*gorm.DB)(bdb)
var bul []bilibiliup
db.Model(&bilibiliup{}).Find(&bul)
for _, v := range bul {
upMap[v.BilibiliUID] = v.Name
}
}

233
plugin/bilibili/card2msg.go Normal file
View File

@@ -0,0 +1,233 @@
package bilibili
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/web"
"github.com/tidwall/gjson"
"github.com/wdvxdr1123/ZeroBot/message"
)
var (
typeMsg = map[int]string{
1: "转发了动态",
2: "有图营业",
4: "无图营业",
8: "投稿了视频",
16: "投稿了短视频",
64: "投稿了文章",
256: "投稿了音频",
2048: "发布了简报",
4200: "发布了直播",
4308: "发布了直播",
}
)
// dynamicCard2msg cType=0时,处理DynCard字符串,cType=1, 2, 4, 8, 16, 64, 256, 2048, 4200, 4308时,处理Card字符串,cType为card类型
func dynamicCard2msg(str string, cType int) (msg []message.MessageSegment, err error) {
var (
dynamicCard dynamicCard
card Card
vote Vote
)
msg = make([]message.MessageSegment, 0, 16)
// 初始化结构体
switch cType {
case 0:
err = json.Unmarshal(binary.StringToBytes(str), &dynamicCard)
if err != nil {
return
}
err = json.Unmarshal(binary.StringToBytes(dynamicCard.Card), &card)
if err != nil {
return
}
if dynamicCard.Extension.Vote != "" {
err = json.Unmarshal(binary.StringToBytes(dynamicCard.Extension.Vote), &vote)
if err != nil {
return
}
}
cType = dynamicCard.Desc.Type
case 1, 2, 4, 8, 16, 64, 256, 2048, 4200, 4308:
err = json.Unmarshal(binary.StringToBytes(str), &card)
if err != nil {
return
}
default:
err = errors.New("只有0, 1, 2, 4, 8, 16, 64, 256, 2048, 4200, 4308模式")
return
}
// 生成消息
switch cType {
case 1:
msg = append(msg, message.Text(card.User.Uname, typeMsg[cType], "\n",
card.Item.Content, "\n",
"转发的内容: \n"))
var originMsg []message.MessageSegment
originMsg, err = dynamicCard2msg(card.Origin, card.Item.OrigType)
if err != nil {
return
}
msg = append(msg, originMsg...)
case 2:
msg = append(msg, message.Text(card.User.Name, "在", time.Unix(int64(card.Item.UploadTime), 0).Format("2006-01-02 15:04:05"), typeMsg[cType], "\n",
card.Item.Description))
for i := 0; i < len(card.Item.Pictures); i++ {
msg = append(msg, message.Image(card.Item.Pictures[i].ImgSrc))
}
case 4:
msg = append(msg, message.Text(card.User.Uname, "在", time.Unix(int64(card.Item.Timestamp), 0).Format("2006-01-02 15:04:05"), typeMsg[cType], "\n",
card.Item.Content, "\n"))
if dynamicCard.Extension.Vote != "" {
msg = append(msg, message.Text("【投票】", vote.Desc, "\n",
"截止日期: ", time.Unix(int64(vote.Endtime), 0).Format("2006-01-02 15:04:05"), "\n",
"参与人数: ", humanNum(vote.JoinNum), "\n",
"投票选项( 最多选择", vote.ChoiceCnt, "项 )\n"))
for i := 0; i < len(vote.Options); i++ {
msg = append(msg, message.Text("- ", vote.Options[i].Idx, ". ", vote.Options[i].Desc, "\n"))
if vote.Options[i].ImgURL != "" {
msg = append(msg, message.Image(vote.Options[i].ImgURL))
}
}
}
case 8:
msg = append(msg, message.Text(card.Owner.Name, "在", time.Unix(int64(card.Pubdate), 0).Format("2006-01-02 15:04:05"), typeMsg[cType], "\n",
card.Title))
msg = append(msg, message.Image(card.Pic))
msg = append(msg, message.Text(card.Desc, "\n",
card.ShareSubtitle, "\n",
"视频链接: ", card.ShortLink, "\n"))
case 16:
msg = append(msg, message.Text(card.User.Name, "在", time.Unix(int64(card.Item.UploadTime), 0).Format("2006-01-02 15:04:05"), typeMsg[cType], "\n",
card.Item.Description))
msg = append(msg, message.Image(card.Item.Cover.Default))
case 64:
msg = append(msg, message.Text(card.Author.(map[string]interface{})["name"], "在", time.Unix(int64(card.PublishTime), 0).Format("2006-01-02 15:04:05"), typeMsg[cType], "\n",
card.Title, "\n",
card.Summary))
for i := 0; i < len(card.ImageUrls); i++ {
msg = append(msg, message.Image(card.ImageUrls[i]))
}
if card.ID != 0 {
msg = append(msg, message.Text("文章链接: https://www.bilibili.com/read/cv", card.ID, "\n"))
}
case 256:
msg = append(msg, message.Text(card.Upper, "在", time.Unix(int64(card.Ctime), 0).Format("2006-01-02 15:04:05"), typeMsg[cType], "\n",
card.Title))
msg = append(msg, message.Image(card.Cover))
msg = append(msg, message.Text(card.Intro, "\n"))
if card.ID != 0 {
msg = append(msg, message.Text("音频链接: https://www.bilibili.com/audio/au", card.ID, "\n"))
}
case 2048:
msg = append(msg, message.Text(card.User.Uname, typeMsg[cType], "\n",
card.Vest.Content, "\n",
card.Sketch.Title, "\n",
card.Sketch.DescText, "\n"))
msg = append(msg, message.Image(card.Sketch.CoverURL))
msg = append(msg, message.Text("分享链接: ", card.Sketch.TargetURL, "\n"))
case 4308:
if dynamicCard.Desc.UserProfile.Info.Uname != "" {
msg = append(msg, message.Text(dynamicCard.Desc.UserProfile.Info.Uname, typeMsg[cType], "\n"))
}
msg = append(msg, message.Image(card.LivePlayInfo.Cover))
msg = append(msg, message.Text("\n", card.LivePlayInfo.Title, "\n",
"房间号: ", card.LivePlayInfo.RoomID, "\n",
"分区: ", card.LivePlayInfo.ParentAreaName))
if card.LivePlayInfo.ParentAreaName != card.LivePlayInfo.AreaName {
msg = append(msg, message.Text("-", card.LivePlayInfo.AreaName))
}
if card.LivePlayInfo.LiveStatus == 0 {
msg = append(msg, message.Text("未开播 \n"))
} else {
msg = append(msg, message.Text("直播中 ", card.LivePlayInfo.WatchedShow, "\n"))
}
msg = append(msg, message.Text("直播链接: ", card.LivePlayInfo.Link))
default:
msg = append(msg, message.Text("动态id: ", dynamicCard.Desc.DynamicIDStr, "未知动态类型: ", cType, "\n"))
}
if dynamicCard.Desc.DynamicIDStr != "" {
msg = append(msg, message.Text("动态链接: ", tURL, dynamicCard.Desc.DynamicIDStr))
}
return
}
// dynamicDetail 用动态id查动态信息
func dynamicDetail(dynamicIDStr string) (msg []message.MessageSegment, err error) {
var data []byte
data, err = web.GetData(fmt.Sprintf(dynamicDetailURL, dynamicIDStr))
if err != nil {
return
}
return dynamicCard2msg(gjson.ParseBytes(data).Get("data.card").Raw, 0)
}
// articleCard2msg 专栏转消息
func articleCard2msg(card Card, defaultID string) (msg []message.MessageSegment) {
msg = make([]message.MessageSegment, 0, 16)
for i := 0; i < len(card.OriginImageUrls); i++ {
msg = append(msg, message.Image(card.OriginImageUrls[i]))
}
msg = append(msg, message.Text("\n", card.Title, "\n", "UP主: ", card.AuthorName, "\n",
"阅读: ", humanNum(card.Stats.View), " 评论: ", humanNum(card.Stats.Reply), "\n",
cvURL, defaultID))
return
}
// liveCard2msg 直播卡片转消息
func liveCard2msg(card roomCard) (msg []message.MessageSegment) {
msg = make([]message.MessageSegment, 0, 16)
msg = append(msg, message.Image(card.RoomInfo.Keyframe))
msg = append(msg, message.Text("\n", card.RoomInfo.Title, "\n",
"主播: ", card.AnchorInfo.BaseInfo.Uname, "\n",
"房间号: ", card.RoomInfo.RoomID, "\n"))
if card.RoomInfo.ShortID != 0 {
msg = append(msg, message.Text("短号: ", card.RoomInfo.ShortID, "\n"))
}
msg = append(msg, message.Text("分区: ", card.RoomInfo.ParentAreaName))
if card.RoomInfo.ParentAreaName != card.RoomInfo.AreaName {
msg = append(msg, message.Text("-", card.RoomInfo.AreaName))
}
if card.RoomInfo.LiveStatus == 0 {
msg = append(msg, message.Text("未开播 \n"))
} else {
msg = append(msg, message.Text("直播中 ", humanNum(card.RoomInfo.Online), "人气\n"))
}
if card.RoomInfo.ShortID != 0 {
msg = append(msg, message.Text("直播间链接: ", lURL, card.RoomInfo.ShortID))
} else {
msg = append(msg, message.Text("直播间链接: ", lURL, card.RoomInfo.RoomID))
}
return
}
// videoCard2msg 视频卡片转消息
func videoCard2msg(card Card) (msg []message.MessageSegment, err error) {
var mCard memberCard
msg = make([]message.MessageSegment, 0, 16)
mCard, err = getMemberCard(card.Owner.Mid)
if err != nil {
return
}
msg = append(msg, message.Text("标题: ", card.Title, "\n"))
if card.Rights.IsCooperation == 1 {
for i := 0; i < len(card.Staff); i++ {
msg = append(msg, message.Text(card.Staff[i].Title, ": ", card.Staff[i].Name, " 粉丝: ", humanNum(card.Staff[i].Follower), "\n"))
}
} else {
msg = append(msg, message.Text("UP主: ", card.Owner.Name, " 粉丝: ", humanNum(mCard.Fans), "\n"))
}
msg = append(msg, message.Text("播放: ", humanNum(card.Stat.View), " 弹幕: ", humanNum(card.Stat.Danmaku)))
msg = append(msg, message.Image(card.Pic))
msg = append(msg, message.Text("\n点赞: ", humanNum(card.Stat.Like), " 投币: ", humanNum(card.Stat.Coin), "\n",
"收藏: ", humanNum(card.Stat.Favorite), " 分享: ", humanNum(card.Stat.Share), "\n",
vURL, card.BvID))
return
}

View File

@@ -0,0 +1,82 @@
package bilibili
import (
"testing"
)
func TestArticleInfo(t *testing.T) {
card, err := getArticleInfo("17279244")
if err != nil {
t.Fatal(err)
}
t.Log(articleCard2msg(card, "17279244"))
}
func TestDynamicDetail(t *testing.T) {
t.Log("cType = 1")
t.Log(dynamicDetail("642279068898689029"))
t.Log("cType = 2")
t.Log(dynamicDetail("642470680290394121"))
t.Log("cType = 2048")
t.Log(dynamicDetail("642277677329285174"))
t.Log("cType = 4")
t.Log(dynamicDetail("642154347357011968"))
t.Log("cType = 8")
t.Log(dynamicDetail("675892999274627104"))
t.Log("cType = 4308")
t.Log(dynamicDetail("668598718656675844"))
t.Log("cType = 64")
t.Log(dynamicDetail("675966082178088963"))
t.Log("cType = 256")
t.Log(dynamicDetail("599253048535707632"))
t.Log("cType = 4,投票类型")
t.Log(dynamicDetail("677231070435868704"))
}
func TestMemberCard(t *testing.T) {
card, err := getMemberCard(2)
if err != nil {
t.Fatal(err)
}
t.Logf("%+v\n", card)
}
func TestVideoInfo(t *testing.T) {
card, err := getVideoInfo("10007")
if err != nil {
t.Fatal(err)
}
t.Log(videoCard2msg(card))
card, err = getVideoInfo("BV1xx411c7mD")
if err != nil {
t.Fatal(err)
}
t.Log(videoCard2msg(card))
card, err = getVideoInfo("bv1xx411c7mD")
if err != nil {
t.Fatal(err)
}
t.Log(videoCard2msg(card))
card, err = getVideoInfo("BV1mF411j7iU")
if err != nil {
t.Fatal(err)
}
t.Log(videoCard2msg(card))
}
func TestLiveRoomInfo(t *testing.T) {
card, err := getLiveRoomInfo("83171")
if err != nil {
t.Fatal(err)
}
t.Log(liveCard2msg(card))
}

View File

@@ -1,358 +0,0 @@
// Package bilibili 查询b站用户信息
package bilibili
import (
"encoding/binary"
"fmt"
"image"
"image/color"
"os"
"path"
"regexp"
"sort"
"strconv"
"time"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/img"
"github.com/FloatTech/zbputils/img/text"
"github.com/FloatTech/zbputils/img/writer"
"github.com/FloatTech/zbputils/web"
"github.com/fogleman/gg"
log "github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
var engine = control.Register("bilibili", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "bilibili\n" +
"- >vup info [xxx]\n" +
"- >user info [xxx]\n" +
"- 查成分 [xxx]\n" +
"- 设置b站cookie SESSDATA=82da790d,1663822823,06ecf*31\n" +
"- 更新vup",
PublicDataFolder: "Bilibili",
})
var re = regexp.MustCompile(`^\d+$`)
// 查成分的
func init() {
cachePath := engine.DataFolder() + "cache/"
_ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
var getdb = ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
var err error
_, _ = engine.GetLazyData("bilibili.db", false)
vdb, err = initialize(engine.DataFolder() + "bilibili.db")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return false
}
return true
})
engine.OnRegex(`^>user info\s?(.{1,25})$`, getdb).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
keyword := ctx.State["regex_matched"].([]string)[1]
uidRes, err := search(keyword)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
id := strconv.FormatInt(uidRes[0].Mid, 10)
follwings, err := followings(id)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
}
ctx.SendChain(message.Text(
"search: ", uidRes[0].Mid, "\n",
"name: ", uidRes[0].Uname, "\n",
"sex: ", []string{"", "男", "女", "未知"}[uidRes[0].Gender], "\n",
"sign: ", uidRes[0].Usign, "\n",
"level: ", uidRes[0].Level, "\n",
"follow: ", follwings,
))
})
engine.OnRegex(`^>vup info\s?(.{1,25})$`).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
keyword := ctx.State["regex_matched"].([]string)[1]
res, err := search(keyword)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
id := strconv.FormatInt(res[0].Mid, 10)
// 获取详情
fo, err := fansapi(id)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
ctx.SendChain(message.Text(
"search: ", fo.Mid, "\n",
"名字: ", fo.Uname, "\n",
"当前粉丝数: ", fo.Follower, "\n",
"24h涨粉数: ", fo.Rise, "\n",
"视频投稿数: ", fo.Video, "\n",
"直播间id: ", fo.Roomid, "\n",
"舰队: ", fo.GuardNum, "\n",
"直播总排名: ", fo.AreaRank, "\n",
"数据来源: ", "https://vtbs.moe/detail/", fo.Mid, "\n",
"数据获取时间: ", time.Now().Format("2006-01-02 15:04:05"),
))
})
engine.OnRegex(`^查成分\s?(.{1,25})$`, getdb, getPara).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
id := ctx.State["uid"].(string)
today := time.Now().Format("20060102")
drawedFile := cachePath + id + today + "vupLike.png"
if file.IsExist(drawedFile) {
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
return
}
u, err := card(id)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
vups, err := vdb.filterVup(u.Attentions)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
vupLen := len(vups)
medals, err := medalwall(id)
sort.Sort(medalSlice(medals))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
}
frontVups := make([]vup, 0)
medalMap := make(map[int64]medal)
for _, v := range medals {
up := vup{
Mid: v.Mid,
Uname: v.Uname,
}
frontVups = append(frontVups, up)
medalMap[v.Mid] = v
}
vups = append(vups, frontVups...)
copy(vups[len(frontVups):], vups)
copy(vups, frontVups)
for i := len(frontVups); i < len(vups); i++ {
if _, ok := medalMap[vups[i].Mid]; ok {
vups = append(vups[:i], vups[i+1:]...)
i--
}
}
facePath := cachePath + id + "vupFace" + path.Ext(u.Face)
backX := 500
backY := 500
var back image.Image
if path.Ext(u.Face) != ".webp" {
err = initFacePic(facePath, u.Face)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
back, err = gg.LoadImage(facePath)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
back = img.Size(back, backX, backY).Im
}
if len(vups) > 50 {
ctx.SendChain(message.Text(u.Name + "关注的up主太多了只展示前50个up"))
vups = vups[:50]
}
canvas := gg.NewContext(1500, int(500*(1.1+float64(len(vups))/3)))
fontSize := 50.0
canvas.SetColor(color.White)
canvas.Clear()
if back != nil {
canvas.DrawImage(back, 0, 0)
}
canvas.SetColor(color.Black)
_, err = file.GetLazyData(text.BoldFontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
}
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
sl, _ := canvas.MeasureString("好")
length, h := canvas.MeasureString(u.Mid)
n, _ := canvas.MeasureString(u.Name)
canvas.DrawString(u.Name, 550, 160-h)
canvas.DrawRoundedRectangle(600+n-length*0.1, 160-h*2.5, length*1.2, h*2, fontSize*0.2)
canvas.SetRGB255(221, 221, 221)
canvas.Fill()
canvas.SetColor(color.Black)
canvas.DrawString(u.Mid, 600+n, 160-h)
canvas.DrawString(fmt.Sprintf("粉丝:%d", u.Fans), 550, 240-h)
canvas.DrawString(fmt.Sprintf("关注:%d", len(u.Attentions)), 1000, 240-h)
canvas.DrawString(fmt.Sprintf("管人痴成分:%.2f%%%d/%d", float64(vupLen)/float64(len(u.Attentions))*100, vupLen, len(u.Attentions)), 550, 320-h)
regtime := time.Unix(u.Regtime, 0).Format("2006-01-02 15:04:05")
canvas.DrawString("注册日期:"+regtime, 550, 400-h)
canvas.DrawString("查询日期:"+time.Now().Format("2006-01-02"), 550, 480-h)
for i, v := range vups {
if i%2 == 1 {
canvas.SetRGB255(245, 245, 245)
canvas.DrawRectangle(0, float64(backY)*1.1+float64(i)*float64(backY)/3, float64(backX*3), float64(backY)/3)
canvas.Fill()
}
canvas.SetColor(color.Black)
nl, _ := canvas.MeasureString(v.Uname)
canvas.DrawString(v.Uname, float64(backX)*0.1, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
ml, _ := canvas.MeasureString(strconv.FormatInt(v.Mid, 10))
canvas.DrawRoundedRectangle(nl-0.1*ml+float64(backX)*0.2, float64(backY)*1.1+float64(i+1)*float64(backY)/3-h*3.5, ml*1.2, h*2, fontSize*0.2)
canvas.SetRGB255(221, 221, 221)
canvas.Fill()
canvas.SetColor(color.Black)
canvas.DrawString(strconv.FormatInt(v.Mid, 10), nl+float64(backX)*0.2, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
if m, ok := medalMap[v.Mid]; ok {
mnl, _ := canvas.MeasureString(m.MedalName)
grad := gg.NewLinearGradient(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h, nl+ml+mnl+sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
r, g, b := int2rbg(m.MedalColorStart)
grad.AddColorStop(0, color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 255})
r, g, b = int2rbg(m.MedalColorEnd)
grad.AddColorStop(1, color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 255})
canvas.SetFillStyle(grad)
canvas.SetLineWidth(4)
canvas.MoveTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.LineTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.ClosePath()
canvas.Fill()
canvas.SetColor(color.White)
canvas.DrawString(m.MedalName, nl+ml+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
r, g, b = int2rbg(m.MedalColorBorder)
canvas.SetRGB255(int(r), int(g), int(b))
canvas.DrawString(strconv.FormatInt(m.Level, 10), nl+ml+mnl+sl+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-2*h)
mll, _ := canvas.MeasureString(strconv.FormatInt(m.Level, 10))
canvas.SetLineWidth(4)
canvas.MoveTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+mll+sl/2+float64(backX)*0.5, float64(backY)*1.1+float64(i+1)*float64(backY)/3-3.5*h)
canvas.LineTo(nl+ml+mnl+mll+sl/2+float64(backX)*0.5, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.LineTo(nl+ml-sl/2+float64(backX)*0.4, float64(backY)*1.1+float64(i+1)*float64(backY)/3-1.5*h)
canvas.ClosePath()
canvas.Stroke()
}
}
f, err := os.Create(drawedFile)
if err != nil {
log.Errorln("[bilibili]", err)
data, cl := writer.ToBytes(canvas.Image())
ctx.SendChain(message.ImageBytes(data))
cl()
return
}
_, err = writer.WriteTo(canvas.Image(), f)
_ = f.Close()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
})
engine.OnRegex(`^设置b站cookie?\s+(.{1,100})$`, zero.SuperUserPermission, getdb).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
cookie := ctx.State["regex_matched"].([]string)[1]
err := vdb.setBilibiliCookie(cookie)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
ctx.SendChain(message.Text("成功设置b站cookie为" + cookie))
})
engine.OnFullMatch("更新vup", zero.SuperUserPermission, getdb).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
ctx.SendChain(message.Text("少女祈祷中..."))
err := updateVup()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
ctx.SendChain(message.Text("vup已更新"))
})
}
func initFacePic(filename, faceURL string) error {
if file.IsNotExist(filename) {
data, err := web.GetData(faceURL)
if err != nil {
return err
}
err = os.WriteFile(filename, data, 0666)
if err != nil {
return err
}
}
return nil
}
func int2rbg(t int64) (int64, int64, int64) {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], uint64(t))
b, g, r := int64(buf[0]), int64(buf[1]), int64(buf[2])
return r, g, b
}
func getPara(ctx *zero.Ctx) bool {
keyword := ctx.State["regex_matched"].([]string)[1]
if !re.MatchString(keyword) {
searchRes, err := search(keyword)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return false
}
ctx.State["uid"] = strconv.FormatInt(searchRes[0].Mid, 10)
return true
}
next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession())
recv, cancel := next.Repeat()
defer cancel()
ctx.SendChain(message.Text("输入为纯数字请选择查询uid还是用户名输入对应序号\n0. 查询uid\n1. 查询用户名"))
for {
select {
case <-time.After(time.Second * 10):
ctx.SendChain(message.Text("时间太久啦!", zero.BotConfig.NickName[0], "帮你选择查询uid"))
ctx.State["uid"] = keyword
return true
case c := <-recv:
msg := c.Event.Message.ExtractPlainText()
num, err := strconv.Atoi(msg)
if err != nil {
ctx.SendChain(message.Text("请输入数字!"))
continue
}
if num < 0 || num > 1 {
ctx.SendChain(message.Text("序号非法!"))
continue
}
if num == 0 {
ctx.State["uid"] = keyword
return true
} else if num == 1 {
searchRes, err := search(keyword)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return false
}
ctx.State["uid"] = strconv.FormatInt(searchRes[0].Mid, 10)
return true
}
}
}
}

331
plugin/bilibili/types.go Normal file
View File

@@ -0,0 +1,331 @@
package bilibili
const (
// tURL bilibili动态前缀
tURL = "https://t.bilibili.com/"
// liveURL bilibili直播前缀
liveURL = "https://live.bilibili.com/"
// dynamicDetailURL 当前动态信息,一个card
dynamicDetailURL = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail?dynamic_id=%v"
// memberCardURL 个人信息
memberCardURL = "https://account.bilibili.com/api/member/getCardByMid?mid=%v"
// articleInfoURL 查看专栏信息
articleInfoURL = "https://api.bilibili.com/x/article/viewinfo?id=%v"
// cvURL b站专栏前缀
cvURL = "https://www.bilibili.com/read/cv"
// liveRoomInfoURL 查看直播间信息
liveRoomInfoURL = "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=%v"
// lURL b站直播间前缀
lURL = "https://live.bilibili.com/"
// videoInfoURL 查看视频信息
videoInfoURL = "https://api.bilibili.com/x/web-interface/view?aid=%v&bvid=%v"
// vURL 视频网址前缀
vURL = "https://www.bilibili.com/video/"
// searchUserURL 查找b站用户
searchUserURL = "http://api.bilibili.com/x/web-interface/search/type?search_type=bili_user&keyword=%v"
// vtbDetailURL 查找vtb信息
vtbDetailURL = "https://api.vtbs.moe/v1/detail/%v"
// medalwallURL 查找牌子
medalwallURL = "https://api.live.bilibili.com/xlive/web-ucenter/user/MedalWall?target_id=%v"
// spaceHistoryURL 历史动态信息,一共12个card
spaceHistoryURL = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid=%v&offset_dynamic_id=%v&need_top=0"
// liveListURL 获得直播状态
liveListURL = "https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids"
// danmakuAPI 弹幕网获得用户弹幕api
danmakuAPI = "https://danmaku.suki.club/api/search/user/detail?uid=%v&pagenum=%v&pagesize=5"
// danmakuURL 弹幕网链接
danmakuURL = "https://danmaku.suki.club/user/%v"
)
// dynamicCard 总动态结构体,包括desc,card
type dynamicCard struct {
Desc Desc `json:"desc"`
Card string `json:"card"`
Extension struct {
VoteCfg struct {
VoteID int `json:"vote_id"`
Desc string `json:"desc"`
JoinNum int `json:"join_num"`
} `json:"vote_cfg"`
Vote string `json:"vote"`
} `json:"extension"`
}
// Card 卡片结构体
type Card struct {
Item struct {
Content string `json:"content"`
UploadTime int `json:"upload_time"`
Description string `json:"description"`
Pictures []struct {
ImgSrc string `json:"img_src"`
} `json:"pictures"`
Timestamp int `json:"timestamp"`
Cover struct {
Default string `json:"default"`
} `json:"cover"`
OrigType int `json:"orig_type"`
} `json:"item"`
AID interface{} `json:"aid"`
BvID interface{} `json:"bvid"`
Dynamic interface{} `json:"dynamic"`
Pic string `json:"pic"`
Title string `json:"title"`
ID int `json:"id"`
Summary string `json:"summary"`
ImageUrls []string `json:"image_urls"`
OriginImageUrls []string `json:"origin_image_urls"`
Sketch struct {
Title string `json:"title"`
DescText string `json:"desc_text"`
CoverURL string `json:"cover_url"`
TargetURL string `json:"target_url"`
} `json:"sketch"`
Stat struct {
Aid int `json:"aid"`
View int `json:"view"`
Danmaku int `json:"danmaku"`
Reply int `json:"reply"`
Favorite int `json:"favorite"`
Coin int `json:"coin"`
Share int `json:"share"`
Like int `json:"like"`
} `json:"stat"`
Stats struct {
Aid int `json:"aid"`
View int `json:"view"`
Danmaku int `json:"danmaku"`
Reply int `json:"reply"`
Favorite int `json:"favorite"`
Coin int `json:"coin"`
Share int `json:"share"`
Like int `json:"like"`
} `json:"stats"`
Owner struct {
Name string `json:"name"`
Pubdate int `json:"pubdate"`
Mid int `json:"mid"`
} `json:"owner"`
Cover string `json:"cover"`
ShortID interface{} `json:"short_id"`
LivePlayInfo struct {
ParentAreaName string `json:"parent_area_name"`
AreaName string `json:"area_name"`
Cover string `json:"cover"`
Link string `json:"link"`
Online int `json:"online"`
RoomID int `json:"room_id"`
LiveStatus int `json:"live_status"`
WatchedShow string `json:"watched_show"`
Title string `json:"title"`
} `json:"live_play_info"`
Intro string `json:"intro"`
Schema string `json:"schema"`
Author interface{} `json:"author"`
AuthorName string `json:"author_name"`
PlayCnt int `json:"play_cnt"`
ReplyCnt int `json:"reply_cnt"`
TypeInfo string `json:"type_info"`
User struct {
Name string `json:"name"`
Uname string `json:"uname"`
} `json:"user"`
Desc string `json:"desc"`
ShareSubtitle string `json:"share_subtitle"`
ShortLink string `json:"short_link"`
PublishTime int `json:"publish_time"`
BannerURL string `json:"banner_url"`
Ctime int `json:"ctime"`
Vest struct {
Content string `json:"content"`
} `json:"vest"`
Upper string `json:"upper"`
Origin string `json:"origin"`
Pubdate int `json:"pubdate"`
Rights struct {
IsCooperation int `json:"is_cooperation"`
} `json:"rights"`
Staff []struct {
Title string `json:"title"`
Name string `json:"name"`
Follower int `json:"follower"`
} `json:"staff"`
}
// Desc 描述结构体
type Desc struct {
Type int `json:"type"`
DynamicIDStr string `json:"dynamic_id_str"`
OrigType int `json:"orig_type"`
Timestamp int `json:"timestamp"`
Origin struct {
DynamicIDStr string `json:"dynamic_id_str"`
} `json:"origin"`
UserProfile struct {
Info struct {
Uname string `json:"uname"`
} `json:"info"`
} `json:"user_profile"`
}
// Vote 投票结构体
type Vote struct {
ChoiceCnt int `json:"choice_cnt"`
Desc string `json:"desc"`
Endtime int `json:"endtime"`
JoinNum int `json:"join_num"`
Options []struct {
Idx int `json:"idx"`
Desc string `json:"desc"`
ImgURL string `json:"img_url"`
} `json:"options"`
}
// memberCard 个人信息卡片
type memberCard struct {
Mid string `json:"mid"`
Name string `json:"name"`
Sex string `json:"sex"`
Face string `json:"face"`
Coins float64 `json:"coins"`
Regtime int64 `json:"regtime"`
Birthday string `json:"birthday"`
Sign string `json:"sign"`
Attentions []int64 `json:"attentions"`
Fans int `json:"fans"`
Friend int `json:"friend"`
Attention int `json:"attention"`
LevelInfo struct {
CurrentLevel int `json:"current_level"`
} `json:"level_info"`
}
// roomCard 直播间卡片
type roomCard struct {
RoomInfo struct {
RoomID int `json:"room_id"`
ShortID int `json:"short_id"`
Title string `json:"title"`
LiveStatus int `json:"live_status"`
AreaName string `json:"area_name"`
ParentAreaName string `json:"parent_area_name"`
Keyframe string `json:"keyframe"`
Online int `json:"online"`
} `json:"room_info"`
AnchorInfo struct {
BaseInfo struct {
Uname string `json:"uname"`
} `json:"base_info"`
} `json:"anchor_info"`
}
// searchData 查找b站用户总结构体
type searchData struct {
Data struct {
NumResults int `json:"numResults"`
Result []searchResult `json:"result"`
} `json:"data"`
}
// searchResult 查找b站用户结果
type searchResult struct {
Mid int64 `json:"mid"`
Uname string `json:"uname"`
Gender int64 `json:"gender"`
Usign string `json:"usign"`
Level int64 `json:"level"`
}
// medalData 牌子接口返回结构体
type medalData struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
List []medal `json:"list"`
} `json:"data"`
}
// medalInfo b站牌子信息
type medalInfo struct {
Mid int64 `json:"target_id"`
MedalName string `json:"medal_name"`
Level int64 `json:"level"`
MedalColorStart int64 `json:"medal_color_start"`
MedalColorEnd int64 `json:"medal_color_end"`
MedalColorBorder int64 `json:"medal_color_border"`
}
type medal struct {
Uname string `json:"target_name"`
medalInfo `json:"medal_info"`
}
type medalSlice []medal
func (m medalSlice) Len() int {
return len(m)
}
func (m medalSlice) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func (m medalSlice) Less(i, j int) bool {
return m[i].Level > m[j].Level
}
// vtb信息
type vtbDetail struct {
Mid int `json:"mid"`
Uname string `json:"uname"`
Video int `json:"video"`
Roomid int `json:"roomid"`
Rise int `json:"rise"`
Follower int `json:"follower"`
GuardNum int `json:"guardNum"`
AreaRank int `json:"areaRank"`
}
// danmakusuki 弹幕网结构体
type danmakusuki struct {
Code int64 `json:"code"`
Message string `json:"message"`
Data struct {
Data []struct {
Channel struct {
Name string `json:"name"`
IsLiving bool `json:"isLiving"`
UID int64 `json:"uId"`
RoomID int64 `json:"roomId"`
FaceURL string `json:"faceUrl"`
LiveCount int64 `json:"liveCount"`
} `json:"channel"`
Live struct {
LiveID string `json:"liveId"`
Title string `json:"title"`
IsFinish bool `json:"isFinish"`
CoverURL string `json:"coverUrl"`
StartDate int64 `json:"startDate"`
StopDate int64 `json:"stopDate"`
DanmakusCount int64 `json:"danmakusCount"`
TotalIncome float64 `json:"totalIncome"`
WatchCount int64 `json:"watchCount"`
} `json:"live"`
Danmakus []struct {
Name string `json:"name"`
Type int64 `json:"type"`
UID int64 `json:"uId"`
SendDate int64 `json:"sendDate"`
Price float64 `json:"price"`
Message string `json:"message"`
} `json:"danmakus"`
} `json:"data"`
Total int64 `json:"total"`
PageNum int64 `json:"pageNum"`
PageSize int64 `json:"pageSize"`
HasMore bool `json:"hasMore"`
} `json:"data"`
}
// 配置结构体
type config struct {
BilibiliCookie string `json:"bilibili_cookie"`
}

25
plugin/bilibili/util.go Normal file
View File

@@ -0,0 +1,25 @@
package bilibili
import (
"net/http"
"strconv"
)
// humanNum 格式化人数
func humanNum(res int) string {
if res/10000 != 0 {
return strconv.FormatFloat(float64(res)/10000, 'f', 2, 64) + "万"
}
return strconv.Itoa(res)
}
// getrealurl 获取跳转后的链接
func getrealurl(url string) (realurl string, err error) {
data, err := http.Head(url)
if err != nil {
return
}
_ = data.Body.Close()
realurl = data.Request.URL.String()
return
}

View File

@@ -1,190 +0,0 @@
// Package bilibiliparse b站视频链接解析
package bilibiliparse
import (
"encoding/json"
"net/http"
"regexp"
"strconv"
"strings"
"time"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/web"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
type result struct {
Data struct {
Bvid string `json:"bvid"`
Aid int `json:"aid"`
Copyright int `json:"copyright"`
Pic string `json:"pic"`
Title string `json:"title"`
Pubdate int `json:"pubdate"`
Ctime int `json:"ctime"`
Rights struct {
IsCooperation int `json:"is_cooperation"`
} `json:"rights"`
Owner struct {
Mid int `json:"mid"`
Name string `json:"name"`
} `json:"owner"`
Stat struct {
Aid int `json:"aid"`
View int `json:"view"`
Danmaku int `json:"danmaku"`
Reply int `json:"reply"`
Favorite int `json:"favorite"`
Coin int `json:"coin"`
Share int `json:"share"`
Like int `json:"like"`
} `json:"stat"`
Staff []struct {
Title string `json:"title"`
Name string `json:"name"`
Follower int `json:"follower"`
} `json:"staff"`
} `json:"data"`
}
type owner struct {
Data struct {
Card struct {
Fans int `json:"fans"`
} `json:"card"`
} `json:"data"`
}
const (
videoapi = "https://api.bilibili.com/x/web-interface/view?"
cardapi = "http://api.bilibili.com/x/web-interface/card?"
origin = "https://www.bilibili.com/video/"
)
var (
reg = regexp.MustCompile(`https://www.bilibili.com/video/([0-9a-zA-Z]+)`)
limit = ctxext.NewLimiterManager(time.Second*10, 1)
)
// 插件主体
func init() {
en := control.Register("bilibiliparse", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "b站视频链接解析\n" +
"- https://www.bilibili.com/video/BV1xx411c7BF | https://www.bilibili.com/video/av1605 | https://b23.tv/I8uzWCA | https://www.bilibili.com/video/bv1xx411c7BF",
})
en.OnRegex(`(av[0-9]+|BV[0-9a-zA-Z]{10}){1}`).SetBlock(true).Limit(limit.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
if strings.Contains(ctx.MessageString(), "[CQ:forward") {
return
}
id := ctx.State["regex_matched"].([]string)[1]
m, err := parse(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(m)
})
en.OnRegex(`https://www.bilibili.com/video/([0-9a-zA-Z]+)`).SetBlock(true).Limit(limit.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
id := ctx.State["regex_matched"].([]string)[1]
m, err := parse(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(m)
})
en.OnRegex(`(https://b23.tv/[0-9a-zA-Z]+)`).SetBlock(true).Limit(limit.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
url := ctx.State["regex_matched"].([]string)[1]
realurl, err := getrealurl(url)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
m, err := parse(cuturl(realurl))
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(m)
})
}
// parse 解析视频数据
func parse(id string) (m message.Message, err error) {
var vid string
switch id[:2] {
case "av":
vid = "aid=" + id[2:]
case "BV":
vid = "bvid=" + id
}
data, err := web.GetData(videoapi + vid)
if err != nil {
return
}
var r result
err = json.Unmarshal(data, &r)
if err != nil {
return
}
m = make(message.Message, 0, 16)
m = append(m, message.Text("标题: ", r.Data.Title, "\n"))
if r.Data.Rights.IsCooperation == 1 {
for i := 0; i < len(r.Data.Staff); i++ {
m = append(m, message.Text(r.Data.Staff[i].Title, ": ", r.Data.Staff[i].Name, ", 粉丝: ", row(r.Data.Staff[i].Follower), "\n"))
}
} else {
o, err := getcard(r.Data.Owner.Mid)
if err != nil {
return m, err
}
m = append(m, message.Text("UP主: ", r.Data.Owner.Name, ", 粉丝: ", row(o.Data.Card.Fans), "\n"))
}
m = append(m, message.Text("播放: ", row(r.Data.Stat.View), ", 弹幕: ", row(r.Data.Stat.Danmaku), "\n"),
message.Image(r.Data.Pic),
message.Text("\n点赞: ", row(r.Data.Stat.Like), ", 投币: ", row(r.Data.Stat.Coin), "\n收藏: ", row(r.Data.Stat.Favorite), ", 分享: ", row(r.Data.Stat.Share), "\n", origin, id))
return
}
// getrealurl 获取跳转后的链接
func getrealurl(url string) (realurl string, err error) {
data, err := http.Head(url)
if err != nil {
return
}
realurl = data.Request.URL.String()
return
}
// cuturl 获取aid或者bvid
func cuturl(url string) (id string) {
if !reg.MatchString(url) {
return
}
return reg.FindStringSubmatch(url)[1]
}
// getcard 获取个人信息
func getcard(mid int) (o owner, err error) {
data, err := web.GetData(cardapi + "mid=" + strconv.Itoa(mid))
if err != nil {
return
}
err = json.Unmarshal(data, &o)
return
}
func row(res int) string {
if res/10000 != 0 {
return strconv.FormatFloat(float64(res)/10000, 'f', 2, 64) + "万"
}
return strconv.Itoa(res)
}

View File

@@ -8,10 +8,10 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
)
@@ -22,23 +22,23 @@ func init() {
PublicDataFolder: "BookReview",
})
getdb := ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
db.DBPath = engine.DataFolder() + "bookreview.db"
// os.RemoveAll(dbpath)
_, _ = engine.GetLazyData("bookreview.db", true)
err := db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("bookreview", &book{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
n, err := db.Count("bookreview")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
log.Infof("[bookreview]读取%d条书评", n)
@@ -51,11 +51,11 @@ func init() {
b := getBookReviewByKeyword(ctx.State["regex_matched"].([]string)[1])
data, err := text.RenderToBase64(b.BookReview, text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
@@ -64,11 +64,11 @@ func init() {
br := getRandomBookReview()
data, err := text.RenderToBase64(br.BookReview, text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
}

View File

@@ -0,0 +1,62 @@
// Package breakrepeat 打断复读
package breakrepeat
import (
"math/rand"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/RomiChan/syncx"
zero "github.com/wdvxdr1123/ZeroBot"
)
const (
maxLimit = 3
)
type result struct {
Limit int64
RawMsg string
}
var (
sm syncx.Map[int64, *result]
)
func init() {
engine := control.Register("breakrepeat", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "打断复读,打断3次以上复读\n",
})
engine.On(`message/group`, zero.OnlyGroup).SetBlock(false).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
raw := ctx.Event.RawMessage
if r, ok := sm.Load(gid); !ok || r.RawMsg != raw {
sm.Store(gid, &result{
Limit: 0,
RawMsg: raw,
})
return
}
if r, ok := sm.Load(gid); ok {
sm.Store(gid, &result{
Limit: r.Limit + 1,
RawMsg: raw,
})
}
if res, ok := sm.Load(gid); ok && res.Limit >= maxLimit {
r := []rune(res.RawMsg)
if len(r) > 2 {
rand.Shuffle(len(r), func(i, j int) {
r[i], r[j] = r[j], r[i]
})
ctx.Send(string(r))
}
sm.Store(gid, &result{
Limit: 0,
RawMsg: res.RawMsg,
})
}
})
}

View File

@@ -39,17 +39,17 @@ func init() {
kw := ctx.State["regex_matched"].([]string)[1]
err := login()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
data, err := search(kw, "7", "0")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
text, err := dealHTML(helper.BytesToString(data))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(text))
@@ -59,17 +59,17 @@ func init() {
kw := ctx.State["regex_matched"].([]string)[1]
err := login()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
data, err := search(kw, "7", "2")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
text, err := dealHTML(helper.BytesToString(data))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(text))

View File

@@ -0,0 +1,111 @@
// Package charreverser 英文字符反转
package charreverser
import (
"regexp"
"strings"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const commandRegex = `[A-z]{1}([A-z]|\s)+[A-z]{1}` // 命令正则表达式
var (
charMap = map[rune]rune{
'a': 'ɐ',
'b': 'q',
'c': 'ɔ',
'd': 'p',
'e': 'ǝ',
'f': 'ɟ',
'g': 'ƃ',
'h': 'ɥ',
'i': 'ᴉ',
'j': 'ɾ',
'k': 'ʞ',
'l': 'l',
'm': 'ɯ',
'n': 'u',
'o': 'o',
'p': 'd',
'q': 'b',
'r': 'ɹ',
's': 's',
't': 'ʇ',
'u': 'n',
'v': 'ʌ',
'w': 'ʍ',
'x': 'x',
'y': 'ʎ',
'z': 'z',
'A': '∀',
'B': 'ᗺ',
'C': 'Ɔ',
'D': 'ᗡ',
'E': 'Ǝ',
'F': 'Ⅎ',
'G': '⅁',
'H': 'H',
'I': 'I',
'J': 'ſ',
'K': 'ʞ',
'L': '˥',
'M': 'W',
'N': 'N',
'O': 'O',
'P': 'Ԁ',
'Q': 'Ò',
'R': 'ᴚ',
'S': 'S',
'T': '⏊',
'U': '∩',
'V': 'Λ',
'W': 'M',
'X': 'X',
'Y': '⅄',
'Z': 'Z',
}
compiledRegex = regexp.MustCompile(commandRegex)
)
func init() {
// 初始化engine
engine := control.Register(
"charreverser",
&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "字符翻转\n -翻转 <英文字符串>",
},
)
// 处理字符翻转指令
engine.OnRegex(`翻转( )+[A-z]{1}([A-z]|\s)+[A-z]{1}`).SetBlock(true).Handle(
func(ctx *zero.Ctx) {
// 获取需要翻转的字符串
results := compiledRegex.FindAllString(ctx.MessageString(), -1)
str := results[0]
// 将字符顺序翻转
var tempBuilder strings.Builder
for i := len(str) - 1; i >= 0; i-- {
tempBuilder.WriteByte(str[i])
}
// 翻转字符字形
var reversedStrBuilder strings.Builder
for _, char := range tempBuilder.String() {
if char != ' ' {
reversedStrBuilder.WriteRune(charMap[char])
} else {
reversedStrBuilder.WriteRune(' ')
}
}
// 发送翻转后的字符串
ctx.SendChain(message.Text(reversedStrBuilder.String()))
},
)
}

View File

@@ -8,9 +8,9 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
)
func init() {
@@ -21,23 +21,23 @@ func init() {
})
en.OnRegex("^抽象翻译((\\s|[\\r\\n]|[\\p{Han}\\p{P}A-Za-z0-9])+)$",
ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
db.DBPath = en.DataFolder() + "cxh.db"
// os.RemoveAll(dbpath)
_, _ = en.GetLazyData("cxh.db", true)
err := db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("pinyin", &pinyin{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
n, err := db.Count("pinyin")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Printf("[chouxianghua]读取%d条拼音", n)

View File

@@ -10,10 +10,10 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/web"
)
var (
@@ -31,7 +31,7 @@ func init() {
ctx.SendChain(message.Text("少女祈祷中......"))
data, err := web.RequestDataWith(web.NewDefaultClient(), coserURL, "GET", "", ua)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
text := gjson.Get(helper.BytesToString(data), "data.Title").String()
@@ -50,7 +50,7 @@ func init() {
if id := ctx.SendGroupForwardMessage(
ctx.Event.GroupID,
m).Get("message_id").Int(); id == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控或下载图片用时过长,请耐心等待"))
ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待"))
}
})
}

View File

@@ -9,10 +9,10 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/math"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/math"
)
func init() {
@@ -22,23 +22,23 @@ func init() {
PublicDataFolder: "CpStory",
})
getdb := ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
db.DBPath = engine.DataFolder() + "cp.db"
// os.RemoveAll(dbpath)
_, _ = engine.GetLazyData("cp.db", true)
err := db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("cp_story", &cpstory{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
n, err := db.Count("cp_story")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Printf("[cpstory]读取%d条故事", n)

View File

@@ -8,10 +8,11 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/process"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/process"
)
const (
@@ -26,26 +27,26 @@ func init() {
PublicDataFolder: "Curse",
})
getdb := ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
db.DBPath = engine.DataFolder() + "curse.db"
_, err := engine.GetLazyData("curse.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("curse", &curse{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
c, err := db.Count("curse")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Infoln("[curse]加载", c, "条骂人语录")

View File

@@ -6,10 +6,10 @@ import (
"encoding/hex"
"github.com/FloatTech/AnimeAPI/danbooru"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/img/writer"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/img/writer"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
@@ -32,7 +32,7 @@ func init() { // 插件主体
for _, url := range ctx.State["image_url"].([]string) {
t, err := danbooru.TagURL("", url)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
digest := md5.Sum(helper.StringToBytes(url))

View File

@@ -5,9 +5,9 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/ZeroBot-Plugin/plugin/diana/data"
)
@@ -17,16 +17,15 @@ var engine = control.Register("diana", &ctrl.Options[*zero.Ctx]{
Help: "嘉然\n" +
"- 小作文\n" +
"- 发大病\n" +
"- 教你一篇小作文[作文]\n" +
"- [回复]查重",
"- 教你一篇小作文[作文]",
PublicDataFolder: "Diana",
})
func init() {
getdb := ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
err := data.LoadText(engine.DataFolder() + "text.db")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
return true
@@ -49,7 +48,7 @@ func init() {
Handle(func(ctx *zero.Ctx) {
err := data.AddText(ctx.State["regex_matched"].([]string)[1])
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
} else {
ctx.SendChain(message.Text("记住啦!"))
}

View File

@@ -6,9 +6,9 @@ import (
"encoding/binary"
"time"
binutils "github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/file"
sql "github.com/FloatTech/sqlite"
binutils "github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/file"
"github.com/sirupsen/logrus"
)

View File

@@ -1,89 +0,0 @@
// Package diana 嘉然相关
package diana
import (
"bytes"
"math"
"strings"
"time"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/web"
"github.com/tidwall/gjson"
"github.com/wdvxdr1123/ZeroBot/message"
zero "github.com/wdvxdr1123/ZeroBot"
)
// 小作文查重: 回复要查的消息 查重
func init() {
engine.OnMessage(func(ctx *zero.Ctx) bool {
msg := ctx.Event.Message
if msg[0].Type != "reply" {
return false
}
for _, elem := range msg {
if elem.Type == "text" {
text := elem.Data["text"]
text = strings.ReplaceAll(text, " ", "")
text = strings.ReplaceAll(text, "\r", "")
text = strings.ReplaceAll(text, "\n", "")
if text == "查重" {
return true
}
}
}
return false
}).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
msg := ctx.GetMessage(message.NewMessageIDFromString(ctx.Event.Message[0].Data["id"])).Elements[0].Data["text"]
result, err := zhiwangapi(msg)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
if result.Get("code").Int() != 0 {
ctx.SendChain(message.Text("api返回错误:", result.Get("code").Int()))
return
}
if result.Get("data.related.#").Int() == 0 {
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("枝网没搜到查重率为0%,鉴定为原创")))
return
}
related := result.Get("data.related.0.reply").Map()
rate := result.Get("data.related.0.rate").Float()
relatedcontent := related["content"].String()
if len(relatedcontent) > 102 {
relatedcontent = relatedcontent[:102] + "....."
}
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(
"枝网文本复制检测报告(简洁)", "\n",
"查重时间: ", time.Now().Format("2006-01-02 15:04:05"), "\n",
"总文字复制比: ", math.Floor(rate*100), "%", "\n",
"相似小作文:", "\n", relatedcontent, "\n",
"获赞数:", related["like_num"].String(), "\n",
result.Get("data.related.0.reply_url").String(), "\n",
"作者: ", related["m_name"].String(), "\n",
"发表时间: ", time.Unix(int64(related["ctime"].Float()), 0).Format("2006-01-02 15:04:05"), "\n",
"查重结果仅作参考,请注意辨别是否为原创", "\n",
"数据来源: https://asoulcnki.asia/",
)))
})
}
func zhiwangapi(text string) (*gjson.Result, error) {
b, cl := binary.OpenWriterF(func(w *binary.Writer) {
w.WriteString("{\n\"text\":\"")
w.WriteString(text)
w.WriteString("\"\n}")
})
data, err := web.PostData("https://asoulcnki.asia/v1/api/check", "application/json", bytes.NewReader(b))
cl()
if err != nil {
return nil, err
}
result := gjson.ParseBytes(data)
return &result, nil
}

View File

@@ -6,8 +6,8 @@ import (
"strconv"
"sync"
"github.com/FloatTech/floatbox/binary"
sql "github.com/FloatTech/sqlite"
"github.com/FloatTech/zbputils/binary"
)
type bottle struct {

View File

@@ -55,7 +55,7 @@ func init() {
msg,
).throw(sea, channel)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("你将它扔进大海,希望有人捞到吧~")))
@@ -78,7 +78,7 @@ func init() {
logrus.Debugln("[driftbottle]", grp, channel)
b, err := fetchBottle(sea, channel, grp)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
var wg sync.WaitGroup
@@ -96,7 +96,7 @@ func init() {
)
wg.Wait()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
})
@@ -109,7 +109,7 @@ func init() {
}
err := createChannel(sea, channel)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("成功~")))
@@ -125,7 +125,7 @@ func init() {
c, err := sea.Count(channel)
seamu.RUnlock()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text("你缓缓走入大海,感受着海浪轻柔地拍打着你的小腿,膝盖……\n波浪卷着你的腰腹你感觉有些把握不住平衡了……\n……\n你沉入海中", c, " 个物体与你一同沉浮。\n不知何处涌来一股暗流你失去了意识。")))

View File

@@ -7,10 +7,10 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/web"
)
const (
@@ -64,7 +64,7 @@ func init() {
}
data, time, err := queryEpidemic(city)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if data == nil {

45
plugin/event/data.go Normal file
View File

@@ -0,0 +1,45 @@
package event
type storage int64
// 申请
func (s *storage) setapply(on bool) {
if on {
*s |= 0b001
} else {
*s &= 0b110
}
}
// 邀请
func (s *storage) setinvite(on bool) {
if on {
*s |= 0b010
} else {
*s &= 0b101
}
}
// 主人
func (s *storage) setmaster(on bool) {
if on {
*s |= 0b100
} else {
*s &= 0b011
}
}
// 申请
func (s *storage) isapplyon() bool {
return *s&0b001 > 0
}
// 邀请
func (s *storage) isinviteon() bool {
return *s&0b010 > 0
}
// 主人
func (s *storage) ismasteroff() bool {
return *s&0b100 > 0
}

144
plugin/event/event.go Normal file
View File

@@ -0,0 +1,144 @@
// Package event 好友申请以及群聊邀请事件处理
package event
import (
"encoding/binary"
"strconv"
"time"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
base14 "github.com/fumiama/go-base16384"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
func init() {
engine := control.Register("event", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "好友申请以及群聊邀请事件处理,默认发送给主人列表第一位\n" +
"- [开启|关闭]自动同意[申请|邀请|主人]\n" +
"- [同意|拒绝][申请|邀请][flag]\n" +
"flag跟随事件一起发送, 默认同意主人的事件",
})
engine.On("request/group/invite").SetBlock(false).
Handle(func(ctx *zero.Ctx) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if ok {
su := zero.BotConfig.SuperUsers[0]
now := time.Unix(ctx.Event.Time, 0).Format("2006-01-02 15:04:05")
flag, err := strconv.ParseInt(ctx.Event.Flag, 10, 64)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], uint64(flag))
es := base14.EncodeToString(buf[1:])
userid := ctx.Event.UserID
username := ctx.CardOrNickName(userid)
data := (storage)(c.GetData(-su))
groupid := ctx.Event.GroupID
groupname := ctx.GetGroupInfo(groupid, true).Name
logrus.Info("[event]收到来自[", username, "](", userid, ")的群聊邀请,群:[", groupname, "](", groupid, ")")
if data.isinviteon() || (!data.ismasteroff() && zero.SuperUserPermission(ctx)) {
ctx.SetGroupAddRequest(ctx.Event.Flag, "invite", true, "")
ctx.SendPrivateForwardMessage(su, message.Message{message.CustomNode(username, userid,
"已自动同意在"+now+"收到来自"+
"\n用户:["+username+"]("+strconv.FormatInt(userid, 10)+")的群聊邀请"+
"\n群聊:["+groupname+"]("+strconv.FormatInt(groupid, 10)+")"+
"\nflag:"+es)})
return
}
ctx.SendPrivateForwardMessage(su,
message.Message{message.CustomNode(username, userid,
"在"+now+"收到来自"+
"\n用户:["+username+"]("+strconv.FormatInt(userid, 10)+")的群聊邀请"+
"\n群聊:["+groupname+"]("+strconv.FormatInt(groupid, 10)+")"+
"\n请在下方复制flag并在前面加上:"+
"\n同意/拒绝邀请,来决定同意还是拒绝"),
message.CustomNode(username, userid, es)})
}
})
engine.On("request/friend").SetBlock(false).
Handle(func(ctx *zero.Ctx) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if ok {
su := zero.BotConfig.SuperUsers[0]
now := time.Unix(ctx.Event.Time, 0).Format("2006-01-02 15:04:05")
flag, err := strconv.ParseInt(ctx.Event.Flag, 10, 64)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], uint64(flag))
es := base14.EncodeToString(buf[1:])
comment := ctx.Event.Comment
userid := ctx.Event.UserID
username := ctx.CardOrNickName(userid)
data := (storage)(c.GetData(-su))
logrus.Info("[event]收到来自[", username, "](", userid, ")的好友申请")
if data.isapplyon() || (!data.ismasteroff() && zero.SuperUserPermission(ctx)) {
ctx.SetFriendAddRequest(ctx.Event.Flag, true, "")
ctx.SendPrivateForwardMessage(su, message.Message{message.CustomNode(username, userid,
"已自动同意在"+now+"收到来自"+
"\n用户:["+username+"]("+strconv.FormatInt(userid, 10)+")"+
"\n的好友请求:"+comment+
"\nflag:"+es)})
return
}
ctx.SendPrivateForwardMessage(su,
message.Message{message.CustomNode(username, userid,
"在"+now+"收到来自"+
"\n用户:["+username+"]("+strconv.FormatInt(userid, 10)+")"+
"\n的好友请求:"+comment+
"\n请在下方复制flag并在前面加上:"+
"\n同意/拒绝申请,来决定同意还是拒绝"),
message.CustomNode(username, userid, es)})
}
})
engine.OnRegex(`^(同意|拒绝)(申请|邀请)\s*([一-踀]{4})\s*(.*)$`, zero.SuperUserPermission, zero.OnlyPrivate).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
su := zero.BotConfig.SuperUsers[0]
cmd := ctx.State["regex_matched"].([]string)[1]
org := ctx.State["regex_matched"].([]string)[2]
es := ctx.State["regex_matched"].([]string)[3]
other := ctx.State["regex_matched"].([]string)[4]
var buf [8]byte
copy(buf[1:], base14.DecodeFromString(es))
flag := strconv.FormatInt(int64(binary.BigEndian.Uint64(buf[:])), 10)
ok := cmd == "同意"
switch org {
case "申请":
ctx.SetFriendAddRequest(flag, ok, other)
ctx.SendPrivateMessage(su, message.Text("已", cmd, org))
case "邀请":
ctx.SetGroupAddRequest(flag, "invite", ok, other)
ctx.SendPrivateMessage(su, message.Text("已", cmd, org))
}
})
engine.OnRegex(`^(开启|关闭)自动同意(申请|邀请|主人)$`, zero.SuperUserPermission, zero.OnlyPrivate).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
c := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
su := zero.BotConfig.SuperUsers[0]
option := ctx.State["regex_matched"].([]string)[1]
from := ctx.State["regex_matched"].([]string)[2]
data := (storage)(c.GetData(-su))
switch from {
case "申请":
data.setapply(option == "开启")
case "邀请":
data.setinvite(option == "开启")
case "主人":
data.setmaster(option == "关闭")
}
err := c.SetData(-su, int64(data))
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("已设置自动同意" + from + "为" + option))
})
}

View File

@@ -2,8 +2,8 @@
package font
import (
"github.com/FloatTech/floatbox/binary"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
@@ -36,7 +36,7 @@ func init() {
}
b, err := text.RenderToBase64(txt, fnt, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Image("base64://" + binary.BytesToString(b)))

View File

@@ -11,19 +11,20 @@ import (
"os"
"strconv"
"github.com/fogleman/gg" // 注册了 jpg png gif
"github.com/Coloured-glaze/gg" // 注册了 jpg png gif
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/img/writer"
"github.com/FloatTech/floatbox/math"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/img/pool"
"github.com/FloatTech/zbputils/img/writer"
"github.com/FloatTech/zbputils/math"
)
const (
@@ -87,21 +88,21 @@ func init() {
}
ctx.SendChain(message.Text("没有这个底图哦~"))
})
en.OnFullMatchGroup([]string{"运势", "抽签"}, ctxext.DoOnceOnSuccess(
en.OnFullMatchGroup([]string{"运势", "抽签"}, fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
data, err := file.GetLazyData(omikujson, false)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = json.Unmarshal(data, &omikujis)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
_, err = file.GetLazyData(font, true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
return true
@@ -127,19 +128,19 @@ func init() {
zipfile := images + kind + ".zip"
_, err := file.GetLazyData(zipfile, false)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 随机获取背景
background, index, err := randimage(zipfile, ctx)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 随机获取签文
randtextindex := ctxext.RandSenderPerDayN(ctx.Event.UserID, len(omikujis))
randtextindex := fcext.RandSenderPerDayN(ctx.Event.UserID, len(omikujis))
title, text := omikujis[randtextindex]["title"], omikujis[randtextindex]["content"]
digest := md5.Sum(helper.StringToBytes(zipfile + strconv.Itoa(index) + title + text))
cachefile := cache + hex.EncodeToString(digest[:])
@@ -154,7 +155,7 @@ func init() {
return err
}, ctxext.Send(ctx), ctxext.GetMessage(ctx))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
})
@@ -171,7 +172,7 @@ func randimage(path string, ctx *zero.Ctx) (im image.Image, index int, err error
}
defer reader.Close()
file := reader.File[ctxext.RandSenderPerDayN(ctx.Event.UserID, len(reader.File))]
file := reader.File[fcext.RandSenderPerDayN(ctx.Event.UserID, len(reader.File))]
f, err := file.Open()
if err != nil {
return

View File

@@ -9,6 +9,7 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
fcext "github.com/FloatTech/floatbox/ctxext"
sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
@@ -30,26 +31,26 @@ func init() {
PublicDataFolder: "Funny",
})
en.OnPrefixGroup([]string{"讲个笑话", "夸夸"}, ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
en.OnPrefixGroup([]string{"讲个笑话", "夸夸"}, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
db.DBPath = en.DataFolder() + "jokes.db"
_, err := en.GetLazyData("jokes.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("jokes", &joke{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
c, err := db.Count("jokes")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Infoln("[funny]加载", c, "个笑话")
@@ -60,7 +61,7 @@ func init() {
var j joke
err := db.Pick("jokes", &j)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(strings.ReplaceAll(j.Text, "%name", name)))

View File

@@ -13,11 +13,12 @@ import (
"strings"
"sync/atomic"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/img/writer"
"github.com/FloatTech/floatbox/process"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/writer"
"github.com/FloatTech/zbputils/process"
"github.com/golang/freetype"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
@@ -62,21 +63,21 @@ func init() {
err := c.SetData(gid, int64(store))
if err != nil {
process.SleepAbout1sTo2s()
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
}
})
engine.OnFullMatch("原神十连", ctxext.DoOnceOnSuccess(
engine.OnFullMatch("原神十连", fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
zipfile := engine.DataFolder() + "Genshin.zip"
_, err := engine.GetLazyData("Genshin.zip", false)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = parsezip(zipfile)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
return true
@@ -95,7 +96,7 @@ func init() {
store := (storage)(c.GetData(gid))
img, str, mode, err := randnums(10, store)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
b, cl := writer.ToBytes(img)

View File

@@ -1,6 +1,6 @@
# ZeroBot-Plugin-Gif
[ZeroBot QQ机器人](https://github.com/wdvxdr1123/ZeroBot)插件可以制作各种沙雕gif图
> 素材包地址: https://gitcode.net/u011570312/imagematerials
> 素材包地址: https://gitcode.net/anto_july/imagematerials
## 触发方式
1. [指令词]+[qq号] 如爬123456
@@ -29,3 +29,75 @@
- [x] 浮雕
- [x] 打码
- [x] 负片
- [x] 旋转45
- [x] 变形100 100
- [x]
- [x] 娶|结婚申请|结婚登记
- [x] 像只
- [x] 阿尼亚喜欢
- [x] 我永远喜欢|永远喜欢
- [x] 像样的亲亲
- [x] 国旗
- [x] 不要靠近
- [x] 万能表情|空白表情
- [x] 采访
- [x] 需要|你可能需要
- [x] 这像画吗
- [x] 小画家
- [x] 完美
- [x] 玩游戏 (应该使用透视变换)
- [x] 出警
- [x] 警察
- [x] 舔|舔屏|prpr (应该使用透视变换)
- [x] 安全感
- [x] 精神支柱
- [x] 想什么
- [x] 墙纸
- [x] 为什么at我
- [x] 交个朋友
- [x] 打工人|继续干活
- [x] 兑换券
- [ ] 捂脸 (使用了透视变换, 需要研究矩阵变换)
- [x] 注意力涣散
- [x] 垃圾桶|垃圾
- [x]
- [x] 啾啾
- [x] 2敲
- [x] 听音乐
- [ ] 群青 (需要mask)
- [ ] 加载中 (需要mask)
- [x] 永远爱你 (未加闪光)
- [ ] 关注 (处理文字麻烦)
- [x] 2拍
- [x]
- [x]
- [x] 打拳 (未加闪光)
- [ ] 复读 (处理文字麻烦)
- [x]
- [x]
- [x]
- [x]
- [x] 紧贴
- [ ] 膜拜 (使用了透视变换, 需要研究矩阵变换)
- [ ] 小天使 (摆)
- [ ] 一直 (摆)
- [x]
- [ ] 问问 (摆)
- [ ] 典中典 (摆)
- [ ] 震惊 (摆)
- [ ] 哈哈镜 (摆)
- [ ] 对称 (猎奇, 不整)
- [x]
- [x] 2蹭
- [x] 诶嘿
- [x] 膜拜
- [x]
- [x]
- [x] 给我变
- [x] 玩一下
- [x] 不要看
- [x] 小天使
- [x] 你的
- [x] 我老婆
- [x] 远离
- [x] 抬棺

View File

@@ -5,7 +5,7 @@ import (
"strconv"
"sync"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/zbputils/img"
"github.com/sirupsen/logrus"
)
@@ -20,7 +20,7 @@ func dlchan(name string, s *string, wg *sync.WaitGroup, exit func(error)) {
target := datapath + `materials/` + name
var err error
if file.IsNotExist(target) {
err = file.DownloadTo(`https://gitcode.net/u011570312/imagematerials/-/raw/main/`+name, target, true)
err = file.DownloadTo(`https://gitcode.net/m0_60838134/imagematerials/-/raw/main/`+name, target, true)
if err != nil {
exit(err)
return
@@ -35,7 +35,7 @@ func dlchan(name string, s *string, wg *sync.WaitGroup, exit func(error)) {
func dlblock(name string) (string, error) {
target := datapath + `materials/` + name
if file.IsNotExist(target) {
err := file.DownloadTo(`https://gitcode.net/u011570312/imagematerials/-/raw/main/`+name, target, true)
err := file.DownloadTo(`https://gitcode.net/m0_60838134/imagematerials/-/raw/main/`+name, target, true)
if err != nil {
return "", err
}
@@ -73,8 +73,8 @@ func newContext(user int64) *context {
return c
}
func loadFirstFrames(paths []string, size int) (imgs []*img.ImgFactory, err error) {
imgs = make([]*img.ImgFactory, size)
func loadFirstFrames(paths []string, size int) (imgs []*img.Factory, err error) {
imgs = make([]*img.Factory, size)
for i := range imgs {
imgs[i], err = img.LoadFirstFrame(paths[i], 0, 0)
if err != nil {

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ import (
"strconv"
"strings"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/zbputils/img"
)

File diff suppressed because it is too large Load Diff

View File

@@ -2,52 +2,162 @@
package gif
import (
"reflect"
"strconv"
"strings"
"github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
var (
cmds = []string{"搓", "冲", "摸", "拍", "丢", "吃", "敲", "啃", "蹭", "爬", "撕",
"灰度", "上翻", "下翻", "左翻", "右翻", "反色", "浮雕", "打码", "负片"}
cmd = make([]string, 0)
datapath string
cmdMap = map[string]func(cc *context, args ...string) (string, error){
"搓": cuo,
"冲": xqe,
"摸": mo,
"拍": pai,
"丢": diu,
"吃": chi,
"敲": qiao,
"啃": ken,
"蹭": ceng,
"爬": pa,
"撕": si,
"灰度": grayscale,
"上翻": flipV,
"下翻": flipV,
"左翻": flipH,
"右翻": flipH,
"反色": invert,
"浮雕": convolve3x3,
"打码": blur,
"负片": invertAndGrayscale,
"旋转": rotate,
"变形": deformation,
"亲": kiss,
"结婚申请": marriage,
"结婚登记": marriage,
"阿尼亚喜欢": anyasuki,
"像只": alike,
"我永远喜欢": alwaysLike,
"永远喜欢": alwaysLike,
"像样的亲亲": decentKiss,
"国旗": chinaFlag,
"不要靠近": dontTouch,
"万能表情": universal,
"空白表情": universal,
"采访": interview,
"需要": need,
"你可能需要": need,
"这像画吗": paint,
"小画家": painter,
"完美": perfect,
"玩游戏": playGame,
"出警": police,
"警察": police1,
"舔": prpr,
"舔屏": prpr,
"prpr": prpr,
"安全感": safeSense,
"精神支柱": support,
"想什么": thinkwhat,
"墙纸": wallpaper,
"为什么at我": whyatme,
"交个朋友": makeFriend,
"打工人": backToWork,
"继续干活": backToWork,
"兑换券": coupon,
"注意力涣散": distracted,
"垃圾桶": garbage,
"垃圾": garbage,
"捶": thump,
"啾啾": jiujiu,
"2敲": knock,
"听音乐": listenMusic,
"永远爱你": loveYou,
"2拍": pat,
"顶": jackUp,
"捣": pound,
"打拳": punch,
"滚": roll,
"吸": suck,
"嗦": suck,
"扔": throw,
"锤": hammer,
"紧贴": tightly,
"紧紧贴着": tightly,
"转": turn,
"蒙蔽": mengbi,
"踩": cai,
"好玩": haowan,
"2转": whirl,
"2滚": push,
"踢球": tiqiu,
"2舔": lick,
"可莉吃": klee,
"胡桃啃": hutaoken,
"怀": huai,
"砰": peng,
"你犯法了": fanfa,
"炖": dun,
"2蹭": ceng2,
"诶嘿": eihei,
"膜拜": worship,
"吞": ci,
"揍": zou,
"给我变": bian,
"玩一下": van,
"不要看": neko,
"小天使": xiaotianshi,
"你的": youer,
"我老婆": nowife,
"远离": yuanli,
"抬棺": taiguan,
"一直": alwaysDoGif,
}
)
func init() { // 插件主体
for k := range cmdMap {
cmd = append(cmd, k)
}
en := control.Register("gif", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "制图\n- " + strings.Join(cmds, "\n- "),
DisableOnDefault: false,
Help: "GIF制图命令后艾特群友/QQ号/一张图方可触发命令\n其中XXX可以为任何文字可以不写\n对机器人操作请先艾特机器人再执行命令\n" +
"- 搓|-冲|-摸|-拍|-丢|-吃|-敲|-啃|-蹭|-爬|-撕|-灰度|-上翻|-下翻\n" +
"- 左翻|-右翻|-反色|-浮雕|- 打码|- 负片|- 旋转|- 变形|- 亲\n" +
"- 结婚申请|结婚登记|- 阿尼亚喜欢XXX|- 像只|- 我永远喜欢XXX\n" +
"- 像样的亲亲|- 国旗|- 不要靠近|- 万能表情|-空白表情|- 采访\n" +
"- 需要|-你可能需要|- 这像画吗|- 小画家|- 完美|- 玩游戏|- 出警\n" +
"- 警察|- 舔|舔屏|prpr|- 安全感|- 精神支柱|- 想什么|- 墙纸\n" +
"- 为什么at我|- 交个朋友|- 打工人|-继续干活|- 兑换券|- 炖\n" +
"- 垃圾桶|- 垃圾|- 捶|- 啾啾|- 2敲|- 听音乐|- 永远爱你|- 2拍\n" +
"- 顶|- 捣|- 打拳|- 滚|- 吸|- 嗦|- 扔|- 锤|- 紧贴|紧紧贴着|- 转\n" +
"- 抬棺|- 远离|- 我老婆|- 小天使XXX|- 你的XXX|- 不要看\n" +
"- 玩一下XXX|- 给我变|- 揍|- 吞|- 膜拜|- 诶嘿|- 2蹭|- 你犯法了\n" +
"- 砰|- 注意力涣散|- 蒙蔽|- 踩|- 好玩|- 2转|- 踢球|- 2舔|\n" +
"- 可莉吃|- 胡桃啃|- 怀|- 一直(支持动图)",
PrivateDataFolder: "gif",
}).ApplySingle(ctxext.DefaultSingle)
datapath = file.BOTPATH + "/" + en.DataFolder()
en.OnRegex(`^(` + strings.Join(cmds, "|") + `)\D*?(\[CQ:(image\,file=([0-9a-zA-Z]{32}).*|at.+?(\d{5,11}))\].*|(\d+))$`).
en.OnRegex(`^(` + strings.Join(cmd, "|") + `)[\s\S]*?(\[CQ:(image\,file=([0-9a-zA-Z]{32}).*|at.+?(\d{5,11}))\].*|(\d+))$`).
SetBlock(true).Handle(func(ctx *zero.Ctx) {
c := newContext(ctx.Event.UserID)
list := ctx.State["regex_matched"].([]string)
err := c.prepareLogos(list[4]+list[5]+list[6], strconv.FormatInt(ctx.Event.UserID, 10))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
var picurl string
if len([]rune(list[1])) == 1 {
r := reflect.ValueOf(c).MethodByName("A" + list[1]).Call(nil)
picurl = r[0].String()
if !r[1].IsNil() {
err = r[1].Interface().(error)
}
} else {
picurl, err = c.other(list[1]) // "灰度", "上翻", "下翻", "左翻", "右翻", "反色", "倒放", "浮雕", "打码", "负片"
}
argslist := strings.Split(strings.TrimSuffix(strings.TrimPrefix(list[0], list[1]), list[2]), " ")
picurl, err := cmdMap[list[1]](c, argslist...)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Image(picurl))

View File

@@ -35,12 +35,12 @@ func init() { // 插件主体
}.Encode()
body, err := netGet(api.String(), header)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
}
// 解析请求
info := gjson.ParseBytes(body)
if info.Get("total_count").Int() == 0 {
ctx.SendChain(message.Text("ERROR:没有找到这样的仓库"))
ctx.SendChain(message.Text("ERROR: 没有找到这样的仓库"))
return
}
repo := info.Get("items.0")
@@ -61,9 +61,9 @@ func init() { // 插件主体
"Star/Fork/Issue: ",
repo.Get("watchers").Int(), "/", repo.Get("forks").Int(), "/", repo.Get("open_issues").Int(), "\n",
"Language: ",
notnull(repo.Get("language").Str, "None"), "\n",
notnull(repo.Get("language").Str), "\n",
"License: ",
notnull(strings.ToUpper(repo.Get("license.key").Str), "None"), "\n",
notnull(strings.ToUpper(repo.Get("license.key").Str)), "\n",
"Last pushed: ",
repo.Get("pushed_at").Str, "\n",
"Jump: ",
@@ -79,9 +79,9 @@ func init() { // 插件主体
"Star/Fork/Issue: ",
repo.Get("watchers").Int(), "/", repo.Get("forks").Int(), "/", repo.Get("open_issues").Int(), "\n",
"Language: ",
notnull(repo.Get("language").Str, "None"), "\n",
notnull(repo.Get("language").Str), "\n",
"License: ",
notnull(strings.ToUpper(repo.Get("license.key").Str), "None"), "\n",
notnull(strings.ToUpper(repo.Get("license.key").Str)), "\n",
"Last pushed: ",
repo.Get("pushed_at").Str, "\n",
"Jump: ",
@@ -97,9 +97,9 @@ func init() { // 插件主体
// notnull 如果传入文本为空,则返回默认值
func notnull(text, defstr string) string {
func notnull(text string) string {
if text == "" {
return defstr
return "None"
}
return text
}

811
plugin/guessmusic/main.go Normal file
View File

@@ -0,0 +1,811 @@
// Package guessmusic 基于zbp的猜歌插件
package guessmusic
import (
"bytes"
"encoding/json"
"io/fs"
"io/ioutil"
"math/rand"
"net/http"
"net/url"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"github.com/pkg/errors"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"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/wdvxdr1123/ZeroBot/extension/single"
// 图片输出
"github.com/FloatTech/zbputils/img/text"
)
const (
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66"
)
var (
catlist = make(map[string]int64, 100)
filelist []string
musictypelist = "mp3;MP3;wav;WAV;amr;AMR;3gp;3GP;3gpp;3GPP;acc;ACC"
cuttime = [...]string{"00:00:05", "00:00:30", "00:01:00"} // 音乐切割时间点,可自行调节时间(时:分:秒)
cfg config
)
func init() { // 插件主体
engine := control.Register("guessmusic", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "猜歌插件该插件依赖ffmpeg\n" +
"------bot主人指令------\n" +
"- 设置猜歌缓存歌库路径 [绝对路径]\n" +
"- 设置猜歌[本地/Api] [true/false]\n" +
"- 登录网易云\n" +
"- 添加歌单 [网易云歌单ID] [歌单名称]\n" +
"- 删除歌单 [网易云歌单ID/API歌单名称]\n" +
"注:\n1.不登陆也能用API有几率返回400\n" +
"2.[歌单名称]可为空,默认原标题\n" +
"------公 用 指 令------\n" +
"- 获取歌单列表\n" +
"- [网易云歌单ID/API歌单名称]歌单信息\n" +
"- [个人/团队]猜歌\n" +
"注默认歌库为网易云ACG动画榜\n" +
"可在后面添加[-歌单名称]进行指定歌单猜歌\n" +
"歌单的歌曲命名规则为:\n歌名 - 歌手 - 其他(歌曲出处之类)",
PrivateDataFolder: "guessmusic",
}).ApplySingle(single.New(
single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }),
single.WithPostFn[int64](func(ctx *zero.Ctx) {
ctx.Send(
message.ReplyWithMessage(ctx.Event.MessageID,
message.Text("已经有正在进行的游戏..."),
),
)
}),
))
cachePath := engine.DataFolder() + "cache/"
err := os.MkdirAll(cachePath, 0755)
if err != nil {
panic(err)
}
cfgFile := engine.DataFolder() + "config.json"
if file.IsExist(cfgFile) {
reader, err := os.Open(cfgFile)
if err == nil {
err = json.NewDecoder(reader).Decode(&cfg)
if err != nil {
panic(err)
}
} else {
panic(err)
}
err = reader.Close()
if err != nil {
panic(err)
}
} else {
var plist = []listRaw{
{
Name: "动画榜",
ID: 3001835560,
},
}
cfg = config{ // 默认 config
MusicPath: file.BOTPATH + "/data/guessmusic/music/", // 绝对路径,歌库根目录,通过指令进行更改
Local: true, // 是否使用本地音乐库
API: true, // 是否使用 Api
Cookie: "",
Playlist: plist,
}
err = saveConfig(cfgFile)
if err != nil {
panic(err)
}
}
err = getcatlist(cfg.MusicPath)
if err != nil {
logrus.Infof("[guessmusic2]无法获取歌单列表,[error]%s", err)
}
engine.OnRegex(`^设置猜歌(缓存歌库路径|本地|Api)\s*(.*)$`, func(ctx *zero.Ctx) bool {
if !zero.SuperUserPermission(ctx) {
ctx.SendChain(message.Text("只有bot主人可以设置"))
return false
}
return true
}).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
option := ctx.State["regex_matched"].([]string)[1]
value := ctx.State["regex_matched"].([]string)[2]
switch option {
case "缓存歌库路径":
if value == "" {
ctx.SendChain(message.Text("请输入正确的路径!"))
return
}
musicPath := strings.ReplaceAll(value, "\\", "/")
if !strings.HasSuffix(musicPath, "/") {
musicPath += "/"
}
err = os.MkdirAll(cfg.MusicPath, 0755)
if err != nil {
ctx.SendChain(message.Text("[生成文件夹错误]ERROR: ", err))
return
}
cfg.MusicPath = musicPath
case "本地":
choice, err := strconv.ParseBool(value)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
cfg.Local = choice
case "Api":
choice, err := strconv.ParseBool(value)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
cfg.API = choice
}
err = saveConfig(cfgFile)
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR: ", err))
}
})
engine.OnFullMatch("登录网易云", zero.SuperUserPermission, func(ctx *zero.Ctx) bool {
if !zero.OnlyPrivate(ctx) {
ctx.SendChain(message.Text("为了保护登录过程请bot主人私聊。"))
return false
}
return true
}).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
keyURL := "https://music.cyrilstudio.top/login/qr/key"
data, err := web.GetData(keyURL)
if err != nil {
ctx.SendChain(message.Text("获取网易云key失败, ERROR: ", err))
return
}
var keyInfo keyInfo
err = json.Unmarshal(data, &keyInfo)
if err != nil {
ctx.SendChain(message.Text("解析网易云key失败, ERROR: ", err))
return
}
qrURL := "https://music.cyrilstudio.top/login/qr/create?key=" + keyInfo.Data.Unikey + "&qrimg=1"
data, err = web.GetData(qrURL)
if err != nil {
ctx.SendChain(message.Text("获取网易云二维码失败, ERROR: ", err))
return
}
var qrInfo qrInfo
err = json.Unmarshal(data, &qrInfo)
if err != nil {
ctx.SendChain(message.Text("解析网易云二维码失败, ERROR: ", err))
return
}
ctx.SendChain(message.Text("[请使用手机APP扫描二维码或者进入网页扫码登录]\n", qrInfo.Data.Qrurl),
message.Image("base64://"+strings.ReplaceAll(qrInfo.Data.Qrimg, "data:image/png;base64,", "")),
message.Text("二维码有效时间为6分钟,登陆后请耐心等待结果获取cookie过程有些漫长。"))
i := 0
for range time.NewTicker(10 * time.Second).C {
apiURL := "https://music.cyrilstudio.top/login/qr/check?key=" + url.QueryEscape(keyInfo.Data.Unikey)
referer := "https://music.cyrilstudio.top"
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
if err != nil {
ctx.SendChain(message.Text("无法获取登录状态, ERROR: ", err))
return
}
var cookiesInfo cookyInfo
err = json.Unmarshal(data, &cookiesInfo)
if err != nil {
ctx.SendChain(message.Text("解析登录状态失败, ERROR: ", err))
return
}
switch cookiesInfo.Code {
case 803:
cfg.Cookie = cookiesInfo.Cookie
err = saveConfig(cfgFile)
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR: ", err))
}
return
case 801:
i++
if i%6 == 0 { // 每1分钟才提醒一次,减少提示(380/60=6次)
ctx.SendChain(message.Text("状态:", cookiesInfo.Message))
}
continue
case 800:
ctx.SendChain(message.Text("状态:", cookiesInfo.Message))
return
default:
ctx.SendChain(message.Text("状态:", cookiesInfo.Message))
continue
}
}
})
engine.OnRegex(`^添加歌单\s?(\d+)(\s(.*))?$`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
listID := ctx.State["regex_matched"].([]string)[1]
listName := ctx.State["regex_matched"].([]string)[3]
ctx.SendChain(message.Text("正在校验歌单信息,请稍等"))
// 是否存在该歌单
apiURL := "https://music.cyrilstudio.top/playlist/detail?id=" + listID + "&cookie=" + cfg.Cookie
referer := "https://music.cyrilstudio.top"
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
if err != nil {
ctx.SendChain(message.Text("无法连接歌单,[error]", err))
return
}
var parsed topList
err = json.Unmarshal(data, &parsed)
if err != nil {
ctx.SendChain(message.Text("无法解析歌单ID内容,[error]", err))
return
}
// 是否有权限访问歌单列表内容
apiURL = "https://music.cyrilstudio.top/playlist/track/all?id=" + listID + "&cookie=" + cfg.Cookie
referer = "https://music.163.com/"
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
if err != nil {
ctx.SendChain(message.Text("无法获取歌单列表\n ERROR: ", err))
return
}
var musiclist topMusicInfo
err = json.Unmarshal(data, &musiclist)
if err != nil {
ctx.SendChain(message.Text("你的cookie在API中无权访问该歌单\n该歌单有可能是用户私人歌单"))
return
}
// 获取列表名字
if listName == "" {
listName = parsed.Playlist.Name
}
playID, _ := strconv.ParseInt(listID, 10, 64)
catlist[listName] = playID
cfg.Playlist = append(cfg.Playlist, listRaw{
Name: listName,
ID: playID,
})
err = saveConfig(cfgFile)
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR: ", err))
}
})
engine.OnRegex(`^删除歌单\s?(.*)$`, zero.SuperUserPermission).SetBlock(true).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
delList := ctx.State["regex_matched"].([]string)[1]
var playlist []listRaw
var newCatList = make(map[string]int64)
var ok = false
for name, musicID := range catlist {
if delList == name || delList == strconv.FormatInt(musicID, 10) {
ok = true
continue
}
newCatList[name] = musicID
playlist = append(playlist, listRaw{
Name: name,
ID: musicID,
})
}
if !ok {
ctx.SendChain(message.Text("目标歌单未找到,请确认是否正确"))
return
}
catlist = newCatList
cfg.Playlist = playlist
err = saveConfig(cfgFile)
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR: ", err))
}
})
engine.OnFullMatch("获取歌单列表").SetBlock(true).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
var msg []string
// 获取网易云歌单列表
if cfg.API {
catlist = make(map[string]int64, 100)
msg = append(msg, "当前添加的API歌单含有以下\n")
for i, listInfo := range cfg.Playlist {
catlist[listInfo.Name] = listInfo.ID
msg = append(msg, strconv.Itoa(i)+":"+listInfo.Name)
if i%3 == 2 {
msg = append(msg, "\n")
}
}
}
// 获取本地歌单列表*/
if cfg.Local {
err = os.MkdirAll(cfg.MusicPath, 0755)
if err == nil {
files, err := ioutil.ReadDir(cfg.MusicPath)
if err == nil {
if len(files) == 0 {
ctx.SendChain(message.Text("缓存目录没有读取到任何歌单"))
filelist = nil
} else {
msg = append(msg, "\n当前本地歌单含有以下\n")
i := 0
for _, name := range files {
if !name.IsDir() {
continue
}
filelist[i] = strconv.Itoa(i) + ":" + name.Name()
msg = append(msg, filelist[i])
if i%3 == 2 {
msg = append(msg, "\n")
}
i++
}
}
} else {
ctx.SendChain(message.Text("[读取本地列表错误]ERROR: ", err))
}
} else {
ctx.SendChain(message.Text("[生成文件夹错误]ERROR: ", err))
}
}
if msg == nil {
ctx.SendChain(message.Text("本地和API均未开启"))
return
}
msgs, err := text.RenderToBase64(strings.Join(msg, " "), text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("生成列表图片失败,请重试"))
return
}
if id := ctx.SendChain(message.Image("base64://" + helper.BytesToString(msgs))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
engine.OnSuffix("歌单信息").SetBlock(true).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
list := ctx.State["args"].(string)
if list == "" {
ctx.SendChain(message.Text("请输入歌单ID或者API歌单名称\n歌单ID为(网页/分享)链接的“playlist”后面的第一串数字"))
return
}
var listIDStr string
for listName, listID := range catlist {
if list == listName || list == strconv.FormatInt(listID, 10) {
listIDStr = strconv.FormatInt(listID, 10)
break
}
}
if listIDStr == "" {
_, err := strconv.ParseInt(list, 10, 64)
if err != nil {
ctx.SendChain(message.Text("仅支持歌单ID查询"))
return
}
listIDStr = list
}
apiURL := "https://music.cyrilstudio.top/playlist/detail?id=" + listIDStr + "&cookie=" + cfg.Cookie
referer := "https://music.cyrilstudio.top"
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
if err != nil {
ctx.SendChain(message.Text("无法连接歌单,[error]", err))
return
}
var parsed topList
err = json.Unmarshal(data, &parsed)
if err != nil {
ctx.SendChain(message.Text("无法解析歌单ID内容,[error]", err))
return
}
ctx.SendChain(
message.Image(parsed.Playlist.CoverImgURL),
message.Text(
"歌单名称:", parsed.Playlist.Name,
"\n歌单ID", parsed.Playlist.ID,
"\n创建人", parsed.Playlist.Creator.Nickname,
"\n创建时间", time.Unix(parsed.Playlist.CreateTime/1000, 0).Format("2006-01-02"),
"\n标签", strings.Join(parsed.Playlist.Tags, ";"),
"\n歌曲数量", parsed.Playlist.TrackCount,
"\n歌单简介:\n", parsed.Playlist.Description,
"\n更新时间", time.Unix(parsed.Playlist.UpdateTime/1000, 0).Format("2006-01-02"),
))
})
engine.OnRegex(`^(个人|团队)猜歌(-(.*))?$`, zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
mode := ctx.State["regex_matched"].([]string)[3]
if mode == "" {
mode = "动画榜"
catlist[mode] = 3001835560
}
_, ok := catlist[mode]
// 如果本地和API不存在该歌单
if !strings.Contains(strings.Join(filelist, " "), mode) && !ok {
ctx.SendChain(message.Text("歌单名称错误,可以发送“获取歌单列表”获取歌单名称"))
return
}
gid := strconv.FormatInt(ctx.Event.GroupID, 10)
ctx.SendChain(message.Text("正在准备歌曲,请稍等\n回答“-[歌曲信息(歌名歌手等)|提示|取消]”\n一共3段语音6次机会"))
// 随机抽歌
musicName, pathOfMusic, err := musicLottery(mode, cfg.MusicPath)
if err != nil {
ctx.SendChain(message.Text(err))
return
}
// 解析歌曲信息
music := strings.Split(musicName, ".")
// 获取音乐后缀
musictype := music[len(music)-1]
if !strings.Contains(musictypelist, musictype) {
ctx.SendChain(message.Text("抽取到了本地歌曲:\n",
musicName, "\n该歌曲不是音乐后缀请联系bot主人修改"))
return
}
// 获取音乐信息
musicInfo := strings.Split(strings.ReplaceAll(musicName, "."+musictype, ""), " - ")
infoNum := len(musicInfo)
if infoNum == 1 {
ctx.SendChain(message.Text("抽取到了本地歌曲:\n",
musicName, "\n该歌曲命名不符合命名规则请联系bot主人修改"))
return
}
answerString := "歌名:" + musicInfo[0] + "\n歌手:" + musicInfo[1]
musicAlia := ""
if infoNum > 2 {
musicAlia = musicInfo[2]
answerString += "\n其他信息:\n" + strings.ReplaceAll(musicAlia, "&", "\n")
}
// 切割音频生成3个10秒的音频
outputPath := cachePath + gid + "/"
err = cutMusic(musicName, pathOfMusic, outputPath)
if err != nil {
ctx.SendChain(message.Text(err))
return
}
// 进行猜歌环节
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + "0.wav"))
var next *zero.FutureEvent
if ctx.State["regex_matched"].([]string)[1] == "个人" {
next = zero.NewFutureEvent("message", 999, false, zero.OnlyGroup, zero.RegexRule(`^-\S{1,}`), ctx.CheckSession())
} else {
next = zero.NewFutureEvent("message", 999, false, zero.OnlyGroup, zero.RegexRule(`^-\S{1,}`), zero.CheckGroup(ctx.Event.GroupID))
}
var musicCount = 0 // 音频数量
var answerCount = 0 // 问答次数
recv, cancel := next.Repeat()
defer cancel()
wait := time.NewTimer(40 * time.Second)
tick := time.NewTimer(105 * time.Second)
after := time.NewTimer(120 * time.Second)
for {
select {
case <-tick.C:
ctx.SendChain(message.Text("猜歌游戏你还有15s作答时间"))
case <-after.C:
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID,
message.Text("时间超时,猜歌结束,公布答案:\n", answerString)))
return
case <-wait.C:
wait.Reset(40 * time.Second)
musicCount++
if musicCount > 2 {
wait.Stop()
continue
}
ctx.SendChain(
message.Text("好像有些难度呢,再听这段音频,要仔细听哦"),
)
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(musicCount) + ".wav"))
case c := <-recv:
wait.Reset(40 * time.Second)
tick.Reset(105 * time.Second)
after.Reset(120 * time.Second)
answer := strings.Replace(c.Event.Message.String(), "-", "", 1)
switch {
case answer == "取消":
if c.Event.UserID == ctx.Event.UserID {
wait.Stop()
tick.Stop()
after.Stop()
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID,
message.Text("游戏已取消,猜歌答案是\n", answerString, "\n\n\n下面欣赏猜歌的歌曲")))
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
return
}
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("你无权限取消"),
),
)
case answer == "提示":
musicCount++
if musicCount > 2 {
wait.Stop()
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("已经没有提示了哦"),
),
)
continue
}
wait.Reset(40 * time.Second)
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("再听这段音频,要仔细听哦"),
),
)
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(musicCount) + ".wav"))
case strings.Contains(musicInfo[0], answer) || strings.EqualFold(musicInfo[0], answer):
wait.Stop()
tick.Stop()
after.Stop()
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
message.Text("太棒了,你猜对歌曲名了!答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
return
case strings.Contains(musicInfo[1], answer) || strings.EqualFold(musicInfo[1], answer):
wait.Stop()
tick.Stop()
after.Stop()
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
message.Text("太棒了,你猜对歌手名了!答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
return
case strings.Contains(musicAlia, answer) || strings.EqualFold(musicAlia, answer):
wait.Stop()
tick.Stop()
after.Stop()
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
message.Text("太棒了,你猜对出处了!答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
return
default:
musicCount++
switch {
case musicCount > 2 && answerCount < 6:
wait.Stop()
answerCount++
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("答案不对哦,加油啊~"),
),
)
case musicCount > 2:
wait.Stop()
tick.Stop()
after.Stop()
ctx.Send(message.ReplyWithMessage(c.Event.MessageID,
message.Text("次数到了,没能猜出来。答案是\n", answerString, "\n\n下面欣赏猜歌的歌曲")))
ctx.SendChain(message.Record("file:///" + pathOfMusic + musicName))
return
default:
wait.Reset(40 * time.Second)
answerCount++
ctx.Send(
message.ReplyWithMessage(c.Event.MessageID,
message.Text("答案不对,再听这段音频,要仔细听哦"),
),
)
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + outputPath + strconv.Itoa(musicCount) + ".wav"))
}
}
}
}
})
}
func saveConfig(cfgFile string) (err error) {
if reader, err := os.Create(cfgFile); err == nil {
err = json.NewEncoder(reader).Encode(&cfg)
if err != nil {
return err
}
} else {
return err
}
return nil
}
func getcatlist(pathOfMusic string) error {
catlist = make(map[string]int64, 100)
for _, listInfo := range cfg.Playlist {
catlist[listInfo.Name] = listInfo.ID
}
err := os.MkdirAll(pathOfMusic, 0755)
if err != nil {
err = errors.Errorf("[生成文件夹错误]ERROR: %s", err)
return err
}
files, err := ioutil.ReadDir(pathOfMusic)
if err != nil {
err = errors.Errorf("[读取本地列表错误]ERROR: %s", err)
return err
}
for i, name := range files {
filelist = append(filelist, strconv.Itoa(i)+":"+name.Name())
}
return nil
}
// 随机抽取音乐
func musicLottery(mode, musicPath string) (musicName, pathOfMusic string, err error) {
pathOfMusic = musicPath + mode + "/"
err = os.MkdirAll(pathOfMusic, 0755)
if err != nil {
err = errors.Errorf("[生成文件夹错误]ERROR: %s", err)
return
}
files, err := ioutil.ReadDir(pathOfMusic)
if err != nil {
err = errors.Errorf("[读取本地列表错误]ERROR: %s", err)
return
}
listID, ok := catlist[mode]
listIDstr := strconv.FormatInt(listID, 10)
if cfg.Local && cfg.API {
switch {
case len(files) == 0:
if !ok {
// 如果歌单是本地歌单
err = errors.New("本地歌单数据为0")
return
}
// 如果没有任何本地就下载歌曲
musicName, err = getListMusic(listIDstr, pathOfMusic)
if err != nil {
err = errors.Errorf("[本地数据为0歌曲下载错误]ERROR: %s", err)
return
}
case rand.Intn(2) == 0 || !ok:
// 1/2概率抽本地或者歌单只有本地有时
musicName = getLocalMusic(files)
default:
musicName, err = getListMusic(listIDstr, pathOfMusic)
if err != nil {
// 如果下载失败就从本地抽一个歌曲
musicName = getLocalMusic(files)
err = nil
}
}
return
}
if cfg.Local {
if len(files) == 0 {
err = errors.New("[本地数据为0未开启API数据]")
return
}
musicName = getLocalMusic(files)
return
}
if cfg.API && ok {
musicName, err = getListMusic(listIDstr, pathOfMusic)
if err != nil {
err = errors.Errorf("[获取API失败未开启本地数据] ERROR: %s", err)
return
}
return
}
err = errors.New("[请确认本地和API设置已开启或歌单存在]")
return
}
func getLocalMusic(files []fs.FileInfo) (musicName string) {
if len(files) > 1 {
musicName = files[rand.Intn(len(files))].Name()
} else {
musicName = files[0].Name()
}
return
}
// 下载网易云歌单音乐
func getListMusic(listID, pathOfMusic string) (musicName string, err error) {
apiURL := "https://music.cyrilstudio.top/playlist/track/all?id=" + listID + "&cookie=" + cfg.Cookie
referer := "https://music.163.com/"
data, err := web.RequestDataWith(web.NewDefaultClient(), apiURL, "GET", referer, ua)
if err != nil {
err = errors.Errorf("无法获取歌单列表\n ERROR: %s", err)
return
}
var parsed topMusicInfo
err = json.Unmarshal(data, &parsed)
if err != nil {
err = errors.Errorf("无法读取歌单列表\n ERROR: %s", err)
return
}
listlen := len(parsed.Songs)
randidx := rand.Intn(listlen)
// 将"/"符号去除,不然无法生成文件
name := strings.ReplaceAll(parsed.Songs[randidx].Name, "/", "·")
musicID := parsed.Songs[randidx].ID
artistName := ""
for i, ARInfo := range parsed.Songs[randidx].Ar {
if i != 0 {
artistName += "&" + ARInfo.Name
} else {
artistName += ARInfo.Name
}
}
cource := ""
if parsed.Songs[randidx].Alia != nil {
cource = strings.Join(parsed.Songs[randidx].Alia, "&")
// 将"/"符号去除,不然无法下载
cource = strings.ReplaceAll(cource, "/", "&")
}
if name == "" || musicID == 0 {
err = errors.New("无法获API取歌曲信息")
return
}
musicURL := "http://music.163.com/song/media/outer/url?id=" + strconv.Itoa(musicID)
response, err := http.Head(musicURL)
if err != nil {
if strings.Contains(err.Error(), "404") {
err = errors.Errorf("歌曲丢失, 可能歌曲已下架或者登录状态已过期。\n可尝试重新登录排除后者问题。")
} else {
err = errors.Errorf("下载音乐失败, ERROR: %s", err)
}
return
}
_ = response.Body.Close()
if response.StatusCode != 200 {
err = errors.Errorf("下载音乐失败, Status Code: %d", response.StatusCode)
return
}
if cource != "" {
musicName = name + " - " + artistName + " - " + cource + ".mp3"
} else {
musicName = name + " - " + artistName + ".mp3"
}
downMusic := pathOfMusic + musicName
if file.IsNotExist(downMusic) {
data, err = web.GetData(musicURL)
if err != nil {
return
}
err = os.WriteFile(downMusic, data, 0666)
if err != nil {
return
}
}
return
}
// 切割音乐成三个10s音频
func cutMusic(musicName, pathOfMusic, outputPath string) (err error) {
err = os.MkdirAll(outputPath, 0755)
if err != nil {
err = errors.Errorf("[生成歌曲目录错误]ERROR: %s", err)
return
}
var stderr bytes.Buffer
cmdArguments := []string{"-y", "-i", pathOfMusic + musicName,
"-ss", cuttime[0], "-t", "10", file.BOTPATH + "/" + outputPath + "0.wav",
"-ss", cuttime[1], "-t", "10", file.BOTPATH + "/" + outputPath + "1.wav",
"-ss", cuttime[2], "-t", "10", file.BOTPATH + "/" + outputPath + "2.wav", "-hide_banner"}
cmd := exec.Command("ffmpeg", cmdArguments...)
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
err = errors.Errorf("[生成歌曲错误]ERROR: %s", stderr.String())
return
}
return
}

406
plugin/guessmusic/struct.go Normal file
View File

@@ -0,0 +1,406 @@
package guessmusic
type listRaw struct {
Name string `json:"name"`
ID int64 `json:"id"`
}
type config struct {
MusicPath string `json:"musicPath"`
Local bool `json:"local"`
API bool `json:"api"`
Cookie string `json:"cookie"`
Playlist []listRaw `json:"playlist"`
}
type keyInfo struct {
Data struct {
Code int `json:"code"`
Unikey string `json:"unikey"`
} `json:"data"`
Code int `json:"code"`
}
type cookyInfo struct {
Code int `json:"code"`
Message string `json:"message"`
Cookie string `json:"cookie"`
}
type qrInfo struct {
Code int `json:"code"`
Data struct {
Qrurl string `json:"qrurl"`
Qrimg string `json:"qrimg"`
} `json:"data"`
}
type topList struct {
Code int `json:"code"`
RelatedVideos interface{} `json:"relatedVideos"`
Playlist struct {
ID int64 `json:"id"`
Name string `json:"name"`
CoverImgID int64 `json:"coverImgId"`
CoverImgURL string `json:"coverImgUrl"`
CoverImgIDStr string `json:"coverImgId_str"`
AdType int `json:"adType"`
UserID int `json:"userId"`
CreateTime int64 `json:"createTime"`
Status int `json:"status"`
OpRecommend bool `json:"opRecommend"`
HighQuality bool `json:"highQuality"`
NewImported bool `json:"newImported"`
UpdateTime int64 `json:"updateTime"`
TrackCount int `json:"trackCount"`
SpecialType int `json:"specialType"`
Privacy int `json:"privacy"`
TrackUpdateTime int64 `json:"trackUpdateTime"`
CommentThreadID string `json:"commentThreadId"`
PlayCount int `json:"playCount"`
TrackNumberUpdateTime int64 `json:"trackNumberUpdateTime"`
SubscribedCount int `json:"subscribedCount"`
CloudTrackCount int `json:"cloudTrackCount"`
Ordered bool `json:"ordered"`
Description string `json:"description"`
Tags []string `json:"tags"`
UpdateFrequency interface{} `json:"updateFrequency"`
BackgroundCoverID int `json:"backgroundCoverId"`
BackgroundCoverURL interface{} `json:"backgroundCoverUrl"`
TitleImage int `json:"titleImage"`
TitleImageURL interface{} `json:"titleImageUrl"`
EnglishTitle interface{} `json:"englishTitle"`
OfficialPlaylistType interface{} `json:"officialPlaylistType"`
Subscribers []struct {
DefaultAvatar bool `json:"defaultAvatar"`
Province int `json:"province"`
AuthStatus int `json:"authStatus"`
Followed bool `json:"followed"`
AvatarURL string `json:"avatarUrl"`
AccountStatus int `json:"accountStatus"`
Gender int `json:"gender"`
City int `json:"city"`
Birthday int `json:"birthday"`
UserID int `json:"userId"`
UserType int `json:"userType"`
Nickname string `json:"nickname"`
Signature string `json:"signature"`
Description string `json:"description"`
DetailDescription string `json:"detailDescription"`
AvatarImgID int64 `json:"avatarImgId"`
BackgroundImgID int64 `json:"backgroundImgId"`
BackgroundURL string `json:"backgroundUrl"`
Authority int `json:"authority"`
Mutual bool `json:"mutual"`
ExpertTags interface{} `json:"expertTags"`
Experts interface{} `json:"experts"`
DjStatus int `json:"djStatus"`
VipType int `json:"vipType"`
RemarkName interface{} `json:"remarkName"`
AuthenticationTypes int `json:"authenticationTypes"`
AvatarDetail interface{} `json:"avatarDetail"`
Anchor bool `json:"anchor"`
BackgroundImgIDStr string `json:"backgroundImgIdStr"`
AvatarImgIDStr string `json:"avatarImgIdStr"`
AvatarImgIDString string `json:"AvatarImgIDString"`
} `json:"subscribers"`
Subscribed interface{} `json:"subscribed"`
Creator struct {
DefaultAvatar bool `json:"defaultAvatar"`
Province int `json:"province"`
AuthStatus int `json:"authStatus"`
Followed bool `json:"followed"`
AvatarURL string `json:"avatarUrl"`
AccountStatus int `json:"accountStatus"`
Gender int `json:"gender"`
City int `json:"city"`
Birthday int `json:"birthday"`
UserID int `json:"userId"`
UserType int `json:"userType"`
Nickname string `json:"nickname"`
Signature string `json:"signature"`
Description string `json:"description"`
DetailDescription string `json:"detailDescription"`
AvatarImgID int64 `json:"avatarImgId"`
BackgroundImgID int64 `json:"backgroundImgId"`
BackgroundURL string `json:"backgroundUrl"`
Authority int `json:"authority"`
Mutual bool `json:"mutual"`
ExpertTags interface{} `json:"expertTags"`
Experts interface{} `json:"experts"`
DjStatus int `json:"djStatus"`
VipType int `json:"vipType"`
RemarkName interface{} `json:"remarkName"`
AuthenticationTypes int `json:"authenticationTypes"`
AvatarDetail struct {
UserType int `json:"userType"`
IdentityLevel int `json:"identityLevel"`
IdentityIconURL string `json:"identityIconUrl"`
} `json:"avatarDetail"`
Anchor bool `json:"anchor"`
BackgroundImgIDStr string `json:"backgroundImgIdStr"`
AvatarImgIDStr string `json:"avatarImgIdStr"`
AvatarImgIDString string `json:"AvatarImgIDString"`
} `json:"creator"`
Tracks []struct {
Name string `json:"name"`
ID int `json:"id"`
Pst int `json:"pst"`
T int `json:"t"`
Ar []struct {
ID int `json:"id"`
Name string `json:"name"`
Tns []interface{} `json:"tns"`
Alias []interface{} `json:"alias"`
} `json:"ar"`
Alia []string `json:"alia"`
Pop int `json:"pop"`
St int `json:"st"`
Rt string `json:"rt"`
Fee int `json:"fee"`
V int `json:"v"`
Crbt interface{} `json:"crbt"`
Cf string `json:"cf"`
Al struct {
ID int `json:"id"`
Name string `json:"name"`
PicURL string `json:"picUrl"`
Tns []interface{} `json:"tns"`
PicStr string `json:"pic_str"`
Pic int64 `json:"pic"`
} `json:"al"`
Dt int `json:"dt"`
H struct {
Br int `json:"br"`
Fid int `json:"fid"`
Size int `json:"size"`
Vd float64 `json:"vd"`
Sr int `json:"sr"`
} `json:"h"`
M struct {
Br int `json:"br"`
Fid int `json:"fid"`
Size int `json:"size"`
Vd float64 `json:"vd"`
Sr int `json:"sr"`
} `json:"m"`
L struct {
Br int `json:"br"`
Fid int `json:"fid"`
Size int `json:"size"`
Vd float64 `json:"vd"`
Sr int `json:"sr"`
} `json:"l"`
Sq interface{} `json:"sq"`
Hr interface{} `json:"hr"`
A interface{} `json:"a"`
Cd string `json:"cd"`
No int `json:"no"`
RtURL interface{} `json:"rtUrl"`
Ftype int `json:"ftype"`
RtUrls []interface{} `json:"rtUrls"`
DjID int `json:"djId"`
Copyright int `json:"copyright"`
SID int `json:"s_id"`
Mark int `json:"mark"`
OriginCoverType int `json:"originCoverType"`
OriginSongSimpleData interface{} `json:"originSongSimpleData"`
TagPicList interface{} `json:"tagPicList"`
ResourceState bool `json:"resourceState"`
Version int `json:"version"`
SongJumpInfo interface{} `json:"songJumpInfo"`
EntertainmentTags interface{} `json:"entertainmentTags"`
Single int `json:"single"`
NoCopyrightRcmd interface{} `json:"noCopyrightRcmd"`
Alg interface{} `json:"alg"`
Rtype int `json:"rtype"`
Rurl interface{} `json:"rurl"`
Mst int `json:"mst"`
Cp int `json:"cp"`
Mv int `json:"mv"`
PublishTime int64 `json:"publishTime"`
Tns []string `json:"tns,omitempty"`
} `json:"tracks"`
VideoIds interface{} `json:"videoIds"`
Videos interface{} `json:"videos"`
TrackIds []struct {
ID int `json:"id"`
V int `json:"v"`
T int `json:"t"`
At int64 `json:"at"`
Alg interface{} `json:"alg"`
UID int `json:"uid"`
RcmdReason string `json:"rcmdReason"`
Sc interface{} `json:"sc"`
Lr int `json:"lr,omitempty"`
} `json:"trackIds"`
ShareCount int `json:"shareCount"`
CommentCount int `json:"commentCount"`
RemixVideo interface{} `json:"remixVideo"`
SharedUsers interface{} `json:"sharedUsers"`
HistorySharedUsers interface{} `json:"historySharedUsers"`
GradeStatus string `json:"gradeStatus"`
Score interface{} `json:"score"`
AlgTags interface{} `json:"algTags"`
} `json:"playlist"`
Urls interface{} `json:"urls"`
Privileges []struct {
ID int `json:"id"`
Fee int `json:"fee"`
Payed int `json:"payed"`
RealPayed int `json:"realPayed"`
St int `json:"st"`
Pl int `json:"pl"`
Dl int `json:"dl"`
Sp int `json:"sp"`
Cp int `json:"cp"`
Subp int `json:"subp"`
Cs bool `json:"cs"`
Maxbr int `json:"maxbr"`
Fl int `json:"fl"`
Pc interface{} `json:"pc"`
Toast bool `json:"toast"`
Flag int `json:"flag"`
PaidBigBang bool `json:"paidBigBang"`
PreSell bool `json:"preSell"`
PlayMaxbr int `json:"playMaxbr"`
DownloadMaxbr int `json:"downloadMaxbr"`
MaxBrLevel string `json:"maxBrLevel"`
PlayMaxBrLevel string `json:"playMaxBrLevel"`
DownloadMaxBrLevel string `json:"downloadMaxBrLevel"`
PlLevel string `json:"plLevel"`
DlLevel string `json:"dlLevel"`
FlLevel string `json:"flLevel"`
Rscl int `json:"rscl"`
FreeTrialPrivilege struct {
ResConsumable bool `json:"resConsumable"`
UserConsumable bool `json:"userConsumable"`
ListenType interface{} `json:"listenType"`
} `json:"freeTrialPrivilege"`
ChargeInfoList []struct {
Rate int `json:"rate"`
ChargeURL interface{} `json:"chargeUrl"`
ChargeMessage interface{} `json:"chargeMessage"`
ChargeType int `json:"chargeType"`
} `json:"chargeInfoList"`
} `json:"privileges"`
SharedPrivilege interface{} `json:"sharedPrivilege"`
ResEntrance interface{} `json:"resEntrance"`
}
type topMusicInfo struct {
Songs []struct {
Name string `json:"name"`
ID int `json:"id"`
Pst int `json:"pst"`
T int `json:"t"`
Ar []struct {
ID int `json:"id"`
Name string `json:"name"`
Tns []interface{} `json:"tns"`
Alias []interface{} `json:"alias"`
} `json:"ar"`
Alia []string `json:"alia"`
Pop int `json:"pop"`
St int `json:"st"`
Rt string `json:"rt"`
Fee int `json:"fee"`
V int `json:"v"`
Crbt interface{} `json:"crbt"`
Cf string `json:"cf"`
Al struct {
ID int `json:"id"`
Name string `json:"name"`
PicURL string `json:"picUrl"`
Tns []interface{} `json:"tns"`
PicStr string `json:"pic_str"`
Pic int64 `json:"pic"`
} `json:"al"`
Dt int `json:"dt"`
H struct {
Br int `json:"br"`
Fid int `json:"fid"`
Size int `json:"size"`
Vd float32 `json:"vd"`
Sr int `json:"sr"`
} `json:"h"`
M struct {
Br int `json:"br"`
Fid int `json:"fid"`
Size int `json:"size"`
Vd float32 `json:"vd"`
Sr int `json:"sr"`
} `json:"m"`
L struct {
Br int `json:"br"`
Fid int `json:"fid"`
Size int `json:"size"`
Vd float32 `json:"vd"`
Sr int `json:"sr"`
} `json:"l"`
Sq interface{} `json:"sq"`
Hr interface{} `json:"hr"`
A interface{} `json:"a"`
Cd string `json:"cd"`
No int `json:"no"`
RtURL interface{} `json:"rtUrl"`
Ftype int `json:"ftype"`
RtUrls []interface{} `json:"rtUrls"`
DjID int `json:"djId"`
Copyright int `json:"copyright"`
SID int `json:"s_id"`
Mark int `json:"mark"`
OriginCoverType int `json:"originCoverType"`
OriginSongSimpleData interface{} `json:"originSongSimpleData"`
TagPicList interface{} `json:"tagPicList"`
ResourceState bool `json:"resourceState"`
Version int `json:"version"`
SongJumpInfo interface{} `json:"songJumpInfo"`
EntertainmentTags interface{} `json:"entertainmentTags"`
AwardTags interface{} `json:"awardTags"`
Single int `json:"single"`
NoCopyrightRcmd interface{} `json:"noCopyrightRcmd"`
Rtype int `json:"rtype"`
Rurl interface{} `json:"rurl"`
Mst int `json:"mst"`
Cp int `json:"cp"`
Mv int `json:"mv"`
PublishTime int64 `json:"publishTime"`
Tns []string `json:"tns,omitempty"`
} `json:"songs"`
Privileges []struct {
ID int `json:"id"`
Fee int `json:"fee"`
Payed int `json:"payed"`
St int `json:"st"`
Pl int `json:"pl"`
Dl int `json:"dl"`
Sp int `json:"sp"`
Cp int `json:"cp"`
Subp int `json:"subp"`
Cs bool `json:"cs"`
Maxbr int `json:"maxbr"`
Fl int `json:"fl"`
Toast bool `json:"toast"`
Flag int `json:"flag"`
PreSell bool `json:"preSell"`
PlayMaxbr int `json:"playMaxbr"`
DownloadMaxbr int `json:"downloadMaxbr"`
MaxBrLevel string `json:"maxBrLevel"`
PlayMaxBrLevel string `json:"playMaxBrLevel"`
DownloadMaxBrLevel string `json:"downloadMaxBrLevel"`
PlLevel string `json:"plLevel"`
DlLevel string `json:"dlLevel"`
FlLevel string `json:"flLevel"`
Rscl int `json:"rscl"`
FreeTrialPrivilege struct {
ResConsumable bool `json:"resConsumable"`
UserConsumable bool `json:"userConsumable"`
ListenType interface{} `json:"listenType"`
} `json:"freeTrialPrivilege"`
ChargeInfoList []struct {
Rate int `json:"rate"`
ChargeURL interface{} `json:"chargeUrl"`
ChargeMessage interface{} `json:"chargeMessage"`
ChargeType int `json:"chargeType"`
} `json:"chargeInfoList"`
} `json:"privileges"`
Code int `json:"code"`
}

View File

@@ -11,11 +11,11 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"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/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/web"
)
var reqconf = [...]string{"GET", "https://hs.fbigame.com",
@@ -81,7 +81,7 @@ func init() {
ctx.Event.GroupID,
sk,
).Get("message_id").Int(); id == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
// 卡组

View File

@@ -10,11 +10,11 @@ import (
"strconv"
"unsafe"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
@@ -107,11 +107,11 @@ func init() {
engine.OnRegex(`^百人一首之\s?(\d+)$`).SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
i, err := strconv.Atoi(ctx.State["regex_matched"].([]string)[1])
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if i > 100 || i < 1 {
ctx.SendChain(message.Text("ERROR:超出范围"))
ctx.SendChain(message.Text("ERROR: 超出范围"))
return
}
ctx.SendChain(

View File

@@ -18,12 +18,12 @@ import (
"github.com/FloatTech/AnimeAPI/pixiv"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/pool"
"github.com/FloatTech/zbputils/web"
)
type resultjson struct {
@@ -71,14 +71,14 @@ func init() {
keyword := ctx.State["regex_matched"].([]string)[1]
soutujson, err := soutuapi(keyword)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
rannum := rand.Intn(len(soutujson.Data.Illusts))
il := soutujson.Data.Illusts[rannum]
illust, err := pixiv.Works(il.ID)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
u := illust.ImageUrls[0]
@@ -101,7 +101,7 @@ func init() {
),
), ctxext.GetFirstMessageInForward(ctx))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
})

View File

@@ -8,10 +8,10 @@ import (
"strconv"
"time"
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/antchfx/htmlquery"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
@@ -29,22 +29,22 @@ func init() {
PublicDataFolder: "Jandan",
})
getdb := ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
db.DBPath = engine.DataFolder() + "pics.db"
_, _ = engine.GetLazyData("pics.db", false)
err := db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("picture", &picture{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
n, err := db.Count("picture")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Printf("[jandan]读取%d张图片", n)
@@ -55,7 +55,7 @@ func init() {
Handle(func(ctx *zero.Ctx) {
u, err := getRandomPicture()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Image(u))
@@ -67,13 +67,13 @@ func init() {
webpageURL := api
doc, err := htmlquery.LoadURL(webpageURL)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
re := regexp.MustCompile(`\d+`)
pageTotal, err := strconv.Atoi(re.FindString(htmlquery.FindOne(doc, "//*[@id='comments']/div[2]/div/span[@class='current-comment-page']/text()").Data))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
LOOP:
@@ -81,12 +81,12 @@ func init() {
logrus.Debugln("[jandan]", fmt.Sprintf("处理第%d/%d页...", i, pageTotal))
doc, err = htmlquery.LoadURL(webpageURL)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
picList, err := htmlquery.QueryAll(doc, "//*[@class='view_img_link']")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if len(picList) != 0 {

View File

@@ -35,7 +35,7 @@ func init() {
case 2:
data, err := juejuezi(string(toDealStr[0]), string(toDealStr[1]))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(gjson.Get(helper.BytesToString(data), "text").String()))
@@ -43,7 +43,7 @@ func init() {
params := ctx.GetWordSlices(string(toDealStr)).Get("slices").Array()
data, err := juejuezi(params[0].String(), params[1].String())
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(gjson.Get(helper.BytesToString(data), "text").String()))

View File

@@ -10,13 +10,13 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/math"
"github.com/FloatTech/floatbox/process"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/pool"
"github.com/FloatTech/zbputils/math"
"github.com/FloatTech/zbputils/process"
"github.com/FloatTech/zbputils/web"
)
const (
@@ -43,7 +43,7 @@ func init() {
if custapi != "" {
data, err := web.GetData(custapi)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
continue
}
queue <- "base64://" + base64.StdEncoding.EncodeToString(data)
@@ -51,12 +51,12 @@ func init() {
}
data, err := web.GetData(api)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
continue
}
json := gjson.ParseBytes(data)
if e := json.Get("error").Str; e != "" {
ctx.SendChain(message.Text("ERROR:", e))
ctx.SendChain(message.Text("ERROR: ", e))
continue
}
url := json.Get("data.0.urls.original").Str
@@ -77,14 +77,14 @@ func init() {
}()
select {
case <-time.After(time.Minute):
ctx.SendChain(message.Text("ERROR:等待填充,请稍后再试......"))
ctx.SendChain(message.Text("ERROR: 等待填充,请稍后再试......"))
case img := <-queue:
id := ctx.SendChain(message.Image(img))
if id.ID() == 0 {
process.SleepAbout1sTo2s()
id = ctx.SendChain(message.Image(img).Add("cache", "0"))
if id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:图片发送失败,可能被风控了~"))
ctx.SendChain(message.Text("ERROR: 图片发送失败,可能被风控了~"))
}
}
}
@@ -93,7 +93,7 @@ func init() {
Handle(func(ctx *zero.Ctx) {
u := strings.TrimSpace(ctx.State["args"].(string))
if !strings.HasPrefix(u, "http") {
ctx.SendChain(message.Text("ERROR:url非法!"))
ctx.SendChain(message.Text("ERROR: url非法!"))
return
}
custapi = u

View File

@@ -10,8 +10,8 @@ import (
"github.com/sirupsen/logrus"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"github.com/FloatTech/zbputils/math"
"github.com/FloatTech/zbputils/web"
"github.com/FloatTech/floatbox/math"
"github.com/FloatTech/floatbox/web"
)
// user hash file

View File

@@ -13,12 +13,12 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/math"
"github.com/FloatTech/floatbox/process"
sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/math"
"github.com/FloatTech/zbputils/process"
"github.com/FloatTech/ZeroBot-Plugin/plugin/manager/timer"
)
@@ -553,12 +553,9 @@ func init() { // 插件主体
// 根据 gist 自动同意加群
// 加群请在github新建一个gist其文件名为本群群号的字符串的md5(小写)内容为一行是当前unix时间戳(10分钟内有效)。
// 然后请将您的用户名和gist哈希(小写)按照username/gisthash的格式填写到回答即可。
engine.OnRequest().SetBlock(false).Handle(func(ctx *zero.Ctx) {
/*if ctx.Event.RequestType == "friend" {
ctx.SetFriendAddRequest(ctx.Event.Flag, true, "")
}*/
engine.On("request/group/add").SetBlock(false).Handle(func(ctx *zero.Ctx) {
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if ok && c.GetData(ctx.Event.GroupID)&0x10 == 0x10 && ctx.Event.RequestType == "group" && ctx.Event.SubType == "add" {
if ok && c.GetData(ctx.Event.GroupID)&0x10 == 0x10 {
// gist 文件名是群号的 ascii 编码的 md5
// gist 内容是当前 uinx 时间戳,在 10 分钟内视为有效
ans := ctx.Event.Comment[strings.Index(ctx.Event.Comment, "答案:")+len("答案:"):]

View File

@@ -7,8 +7,8 @@ import (
"sync"
"time"
"github.com/FloatTech/floatbox/process"
sql "github.com/FloatTech/sqlite"
"github.com/FloatTech/zbputils/process"
"github.com/fumiama/cron"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"

View File

@@ -14,15 +14,15 @@ import (
"strings"
"time"
"github.com/FloatTech/floatbox/binary"
"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/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/web"
"github.com/pkg/errors"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"gitlab.com/gomidi/midi/gm"
"gitlab.com/gomidi/midi/v2"
"gitlab.com/gomidi/midi/v2/smf"
)
@@ -30,11 +30,13 @@ import (
func init() {
engine := control.Register("midicreate", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "midi音乐制作,该插件需要安装timidity,安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh\n" +
Help: "midi音乐制作, 该插件需要安装timidity, linux安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.sh, windows安装脚本可参考https://gitcode.net/anto_july/midi/-/raw/master/timidity.bat?inline=false, windows需要管理员模式运行\n" +
"- midi制作 CCGGAAGR FFEEDDCR GGFFEEDR GGFFEEDR CCGGAAGR FFEEDDCR\n" +
"- 个人听音练习\n" +
"- 团队听音练习\n" +
"- *.mid (解析上传的mid文件)",
"- *.mid (midi 转 txt)\n" +
"- midi制作*.txt (txt 转 midi)\n" +
"- 设置音色40 (0~127)",
PrivateDataFolder: "midicreate",
})
cachePath := engine.DataFolder() + "cache/"
@@ -48,13 +50,13 @@ func init() {
uid := ctx.Event.UserID
input := ctx.State["args"].(string)
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
cmidiFile, err := str2music(input, midiFile)
cmidiFile, err := str2music(ctx, input, midiFile)
if err != nil {
if file.IsExist(midiFile) {
ctx.UploadThisGroupFile(file.BOTPATH+"/"+midiFile, filepath.Base(midiFile), "")
return
}
ctx.SendChain(message.Text("ERROR:无法转换midi文件,", err))
ctx.SendChain(message.Text("ERROR: 无法转换midi文件,", err))
return
}
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
@@ -87,9 +89,9 @@ func init() {
target := uint8(55 + rand.Intn(34))
answer := name(target) + strconv.Itoa(int(target/12))
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
cmidiFile, err := str2music(answer, midiFile)
cmidiFile, err := str2music(ctx, answer, midiFile)
if err != nil {
ctx.SendChain(message.Text("ERROR:听音练习结束, 无法转换midi文件, ", err))
ctx.SendChain(message.Text("ERROR: 听音练习结束, 无法转换midi文件, ", err))
return
}
time.Sleep(time.Millisecond * 500)
@@ -137,7 +139,7 @@ func init() {
),
)
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
cmidiFile, err = str2music(c.Event.Message.String(), midiFile)
cmidiFile, err = str2music(ctx, c.Event.Message.String(), midiFile)
if err != nil {
ctx.SendChain(message.Text("ERROR: can't convert midi file,", err))
return
@@ -172,9 +174,9 @@ func init() {
target = uint8(55 + rand.Intn(34))
answer = name(target) + strconv.Itoa(int(target/12))
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
cmidiFile, err = str2music(answer, midiFile)
cmidiFile, err = str2music(ctx, answer, midiFile)
if err != nil {
ctx.SendChain(message.Text("ERROR:听音练习结束, 无法转换midi文件, ", err))
ctx.SendChain(message.Text("ERROR: 听音练习结束, 无法转换midi文件, ", err))
return
}
time.Sleep(time.Millisecond * 500)
@@ -193,7 +195,7 @@ func init() {
)
time.Sleep(time.Millisecond * 500)
midiFile = cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
cmidiFile, err = str2music(c.Event.Message.String(), midiFile)
cmidiFile, err = str2music(ctx, c.Event.Message.String(), midiFile)
if err != nil {
ctx.SendChain(message.Text("ERROR: can't convert midi file,", err))
return
@@ -228,11 +230,57 @@ func init() {
fileURL := ctx.GetThisGroupFileUrl(ctx.Event.File.BusID, ctx.Event.File.ID)
data, err := web.GetData(fileURL)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
midStr := mid2txt(data)
ctx.SendChain(message.Text("文件名:", ctx.Event.File.Name, "\n转化的midi字符:", midStr))
s, err := smf.ReadFrom(bytes.NewReader(data))
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
for i := 0; i < int(s.NumTracks()); i++ {
midStr := mid2txt(data, i)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
fileName := strings.ReplaceAll(cachePath+"/"+ctx.Event.File.Name, ".mid", fmt.Sprintf("-%d.txt", i))
_ = os.WriteFile(fileName, binary.StringToBytes(midStr), 0666)
ctx.UploadThisGroupFile(file.BOTPATH+"/"+fileName, filepath.Base(fileName), "")
}
})
engine.On("notice/group_upload", func(ctx *zero.Ctx) bool {
return path.Ext(ctx.Event.File.Name) == ".txt" && strings.Contains(ctx.Event.File.Name, "midi制作")
}).SetBlock(false).Limit(ctxext.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
fileURL := ctx.GetThisGroupFileUrl(ctx.Event.File.BusID, ctx.Event.File.ID)
data, err := web.GetData(fileURL)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
uid := ctx.Event.UserID
midiFile := cachePath + strconv.FormatInt(uid, 10) + time.Now().Format("20060102150405") + "_midicreate.mid"
cmidiFile, err := str2music(ctx, binary.BytesToString(data), midiFile)
if err != nil {
ctx.SendChain(message.Text("ERROR: 无法转换midi文件,", err))
return
}
ctx.SendChain(message.Record("file:///" + file.BOTPATH + "/" + cmidiFile))
})
engine.OnPrefix("设置音色").SetBlock(true).
Handle(func(ctx *zero.Ctx) {
param := ctx.State["args"].(string)
timbre, err := strconv.Atoi(param)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
}
err = setTimbreMode(ctx, int64(timbre))
if err != nil {
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err))
return
}
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("成功"))
})
}
@@ -253,8 +301,8 @@ var (
}
)
func str2music(input, midiFile string) (cmidiFile string, err error) {
err = mkMidi(midiFile, input)
func str2music(ctx *zero.Ctx, input, midiFile string) (cmidiFile string, err error) {
err = mkMidi(ctx, midiFile, input)
if err != nil {
return
}
@@ -264,7 +312,7 @@ func str2music(input, midiFile string) (cmidiFile string, err error) {
return
}
func mkMidi(filePath, input string) error {
func mkMidi(ctx *zero.Ctx, filePath, input string) error {
if file.IsExist(filePath) {
return nil
}
@@ -276,7 +324,8 @@ func mkMidi(filePath, input string) error {
tr.Add(0, smf.MetaMeter(4, 4))
tr.Add(0, smf.MetaTempo(72))
tr.Add(0, smf.MetaInstrument("Violin"))
tr.Add(0, midi.ProgramChange(0, gm.Instr_Violin.Value()))
timbre := getTimbreMode(ctx)
tr.Add(0, midi.ProgramChange(0, uint8(timbre)))
k := strings.ReplaceAll(input, " ", "")
@@ -410,46 +459,43 @@ func processOne(note string) uint8 {
return o(base, level)
}
func mid2txt(midBytes []byte) (midStr string) {
func mid2txt(midBytes []byte, trackNo int) (midStr string) {
var (
absTicksStart float64
absTicksEnd float64
startNote byte
endNote byte
defaultMetric = 960.0
defaultTrackNo = 0
absTicksStart float64
absTicksEnd float64
startNote byte
endNote byte
defaultMetric = 960.0
)
_ = smf.ReadTracksFrom(bytes.NewReader(midBytes)).
_ = smf.ReadTracksFrom(bytes.NewReader(midBytes), trackNo).
Do(
func(te smf.TrackEvent) {
if !te.Message.IsMeta() && te.TrackNo == defaultTrackNo {
if !te.Message.IsMeta() {
b := te.Message.Bytes()
if len(b) == 3 {
if b[0] == 0x90 && b[2] > 0 {
absTicksStart = float64(te.AbsTicks)
startNote = b[1]
}
if b[0] == 0x80 || (b[0] == 0x90 && b[2] == 0x00) {
absTicksEnd = float64(te.AbsTicks)
endNote = b[1]
if te.Message.Is(midi.NoteOnMsg) && b[2] > 0 {
absTicksStart = float64(te.AbsTicks)
startNote = b[1]
}
if te.Message.Is(midi.NoteOffMsg) || (te.Message.Is(midi.NoteOnMsg) && b[2] == 0x00) {
absTicksEnd = float64(te.AbsTicks)
endNote = b[1]
if startNote == endNote {
sign := name(b[1])
level := b[1] / 12
length := (absTicksEnd - absTicksStart) / defaultMetric
midStr += sign
if level != 5 {
midStr += strconv.Itoa(int(level))
}
pow := int(math.Round(math.Log2(length)))
if pow >= -4 && pow != 0 {
midStr += "<" + strconv.Itoa(pow)
}
startNote = 0
endNote = 0
}
}
if (b[0] == 0x80 || (b[0] == 0x90 && b[2] == 0x00)) && startNote == endNote {
sign := name(b[1])
level := b[1] / 12
length := (absTicksEnd - absTicksStart) / defaultMetric
midStr += sign
if level != 5 {
midStr += strconv.Itoa(int(level))
}
pow := int(math.Round(math.Log2(length)))
if pow >= -4 && pow != 0 {
midStr += "<" + strconv.Itoa(pow)
}
startNote = 0
endNote = 0
}
if (b[0] == 0x90 && b[2] > 0) && absTicksStart > absTicksEnd {
if (te.Message.Is(midi.NoteOnMsg) && b[2] > 0) && absTicksStart > absTicksEnd {
length := (absTicksStart - absTicksEnd) / defaultMetric
pow := int(math.Round(math.Log2(length)))
if pow == 0 {
@@ -463,3 +509,31 @@ func mid2txt(midBytes []byte) (midStr string) {
)
return
}
func setTimbreMode(ctx *zero.Ctx, timbre int64) error {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if timbre < 0 || timbre > 127 {
return errors.New("音色应该在0~127之间")
}
m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
return errors.New("no such plugin")
}
return m.SetData(gid, timbre)
}
func getTimbreMode(ctx *zero.Ctx) (index int64) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if ok {
index := m.GetData(gid)
return index
}
return 40
}

53
plugin/moegoe/main.go Normal file
View File

@@ -0,0 +1,53 @@
// Package moegoe 日韩中 VITS 模型拟声
package moegoe
import (
"fmt"
"net/url"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
)
const (
jpapi = "https://moegoe.azurewebsites.net/api/speak?text=%s&id=%d"
krapi = "https://moegoe.azurewebsites.net/api/speakkr?text=%s&id=%d"
cnapi = "http://233366.proxy.nscc-gz.cn:8888?speaker=%s&text=%s"
)
var speakers = map[string]uint{
"宁宁": 0, "爱瑠": 1, "芳乃": 2, "茉子": 3, "丛雨": 4, "小春": 5, "七海": 6,
"Sua": 0, "Mimiru": 1, "Arin": 2, "Yeonhwa": 3, "Yuhwa": 4, "Seonbae": 5,
}
func init() {
en := control.Register("moegoe", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "moegoe\n" +
"- 让[宁宁|爱瑠|芳乃|茉子|丛雨|小春|七海]说(日语)\n" +
"- 让[Sua|Mimiru|Arin|Yeonhwa|Yuhwa|Seonbae]说(韩语)\n" +
"- 让[派蒙|凯亚|安柏|丽莎|琴|香菱|枫原万叶|迪卢克|温迪|可莉|早柚|托马|芭芭拉|优菈|云堇|钟离|魈|凝光|雷电将军|北斗|甘雨|七七|刻晴|神里绫华|雷泽|神里绫人|罗莎莉亚|阿贝多|八重神子|宵宫|荒泷一斗|九条裟罗|夜兰|珊瑚宫心海|五郎|达达利亚|莫娜|班尼特|申鹤|行秋|烟绯|久岐忍|辛焱|砂糖|胡桃|重云|菲谢尔|诺艾尔|迪奥娜|鹿野院平藏]说(中文)",
}).ApplySingle(ctxext.DefaultSingle)
en.OnRegex("^让(宁宁|爱瑠|芳乃|茉子|丛雨|小春|七海)说([A-Za-z\\s\\d\u3005\u3040-\u30ff\u4e00-\u9fff\uff11-\uff19\uff21-\uff3a\uff41-\uff5a\uff66-\uff9d\\pP]+)$").Limit(ctxext.LimitByGroup).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
text := ctx.State["regex_matched"].([]string)[2]
id := speakers[ctx.State["regex_matched"].([]string)[1]]
ctx.SendChain(message.Record(fmt.Sprintf(jpapi, url.QueryEscape(text), id)))
})
en.OnRegex("^让(Sua|Mimiru|Arin|Yeonhwa|Yuhwa|Seonbae)说([A-Za-z\\s\\d\u3131-\u3163\uac00-\ud7ff\\pP]+)$").Limit(ctxext.LimitByGroup).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
text := ctx.State["regex_matched"].([]string)[2]
id := speakers[ctx.State["regex_matched"].([]string)[1]]
ctx.SendChain(message.Record(fmt.Sprintf(krapi, url.QueryEscape(text), id)))
})
en.OnRegex("^让(派蒙|凯亚|安柏|丽莎|琴|香菱|枫原万叶|迪卢克|温迪|可莉|早柚|托马|芭芭拉|优菈|云堇|钟离|魈|凝光|雷电将军|北斗|甘雨|七七|刻晴|神里绫华|雷泽|神里绫人|罗莎莉亚|阿贝多|八重神子|宵宫|荒泷一斗|九条裟罗|夜兰|珊瑚宫心海|五郎|达达利亚|莫娜|班尼特|申鹤|行秋|烟绯|久岐忍|辛焱|砂糖|胡桃|重云|菲谢尔|诺艾尔|迪奥娜|鹿野院平藏)说([\\s\u4e00-\u9fa5\\pP]+)$").Limit(ctxext.LimitByGroup).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
speaker := ctx.State["regex_matched"].([]string)[1]
text := ctx.State["regex_matched"].([]string)[2]
ctx.SendChain(message.Record(fmt.Sprintf(cnapi, url.QueryEscape(speaker), url.QueryEscape(text))))
})
}

View File

@@ -31,15 +31,15 @@ func TestSetHoliday(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = SetHoliday("清明节", 1, 2022, 4, 3)
err = SetHoliday("清明节", 1, 2023, 4, 5)
if err != nil {
t.Fatal(err)
}
err = SetHoliday("劳动节", 1, 2022, 4, 30)
err = SetHoliday("劳动节", 1, 2023, 5, 1)
if err != nil {
t.Fatal(err)
}
err = SetHoliday("端午节", 1, 2022, 6, 3)
err = SetHoliday("端午节", 1, 2023, 6, 22)
if err != nil {
t.Fatal(err)
}

View File

@@ -2,9 +2,9 @@
package moyucalendar
import (
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/web"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
@@ -21,7 +21,7 @@ func init() {
Handle(func(ctx *zero.Ctx) {
data, err := web.GetData("https://api.vvhan.com/api/moyu")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.ImageBytes(data))

View File

@@ -11,7 +11,7 @@ import (
"strings"
"time"
"github.com/FloatTech/zbputils/web"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
@@ -138,10 +138,10 @@ func kugou(keyword string) message.MessageSegment {
// cloud163 返回网易云音乐卡片
func cloud163(keyword string) (msg message.MessageSegment) {
requestURL := "https://autumnfish.cn/search?keywords=" + url.QueryEscape(keyword)
requestURL := "https://music.cyrilstudio.top/search?keywords=" + url.QueryEscape(keyword)
data, err := web.GetData(requestURL)
if err != nil {
msg = message.Text("ERROR:", err)
msg = message.Text("ERROR: ", err)
return
}
msg = message.Music("163", gjson.ParseBytes(data).Get("result.songs.0.id").Int())
@@ -153,7 +153,7 @@ func qqmusic(keyword string) (msg message.MessageSegment) {
requestURL := "https://c.y.qq.com/soso/fcgi-bin/client_search_cp?w=" + url.QueryEscape(keyword)
data, err := web.RequestDataWith(web.NewDefaultClient(), requestURL, "GET", "", web.RandUA())
if err != nil {
msg = message.Text("ERROR:", err)
msg = message.Text("ERROR: ", err)
return
}
info := gjson.ParseBytes(data[9 : len(data)-1]).Get("data.song.list.0")

View File

@@ -13,8 +13,8 @@ import (
"github.com/sirupsen/logrus"
_ "golang.org/x/image/webp" // import webp decoding
"github.com/FloatTech/floatbox/file"
sql "github.com/FloatTech/sqlite"
"github.com/FloatTech/zbputils/file"
)
// setuclass holds setus in a folder, which is the class name.

View File

@@ -10,10 +10,11 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
)
var (
@@ -42,7 +43,7 @@ func init() {
}
}
engine.OnRegex(`^本地(.*)$`, ctxext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, ns)).SetBlock(true).
engine.OnRegex(`^本地(.*)$`, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, ns)).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
imgtype := ctx.State["regex_matched"].([]string)[1]
sc := new(setuclass)
@@ -50,7 +51,7 @@ func init() {
err := ns.db.Pick(imgtype, sc)
ns.mu.RUnlock()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
} else {
p := "file:///" + setupath + "/" + sc.Path
if ctx.Event.GroupID != 0 {
@@ -63,14 +64,14 @@ func init() {
ctx.SendChain(message.Text(imgtype, ": ", sc.Name, "\n"), message.Image(p))
}
})
engine.OnRegex(`^刷新本地(.*)$`, ctxext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, ns), zero.SuperUserPermission).SetBlock(true).
engine.OnRegex(`^刷新本地(.*)$`, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, ns), zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
imgtype := ctx.State["regex_matched"].([]string)[1]
err := ns.scanclass(os.DirFS(setupath), imgtype, imgtype)
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
}
})
engine.OnRegex(`^设置本地setu绝对路径(.*)$`, zero.SuperUserPermission).SetBlock(true).
@@ -80,7 +81,7 @@ func init() {
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
}
})
engine.OnFullMatch("刷新所有本地setu", zero.SuperUserPermission).SetBlock(true).
@@ -89,7 +90,7 @@ func init() {
if err == nil {
ctx.SendChain(message.Text("成功!"))
} else {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
}
})
engine.OnFullMatch("所有本地setu分类").SetBlock(true).

View File

@@ -16,9 +16,9 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/file"
)
func init() {

30
plugin/nihongo/model.go Normal file
View File

@@ -0,0 +1,30 @@
package nihongo
import (
"fmt"
sql "github.com/FloatTech/sqlite"
)
type grammar struct {
ID int `db:"id"`
Tag string `db:"tag"`
Name string `db:"name"`
Pronunciation string `db:"pronunciation"`
Usage string `db:"usage"`
Meaning string `db:"meaning"`
Explanation string `db:"explanation"`
Example string `db:"example"`
GrammarURL string `db:"grammar_url"`
}
func (g *grammar) string() string {
return fmt.Sprintf("ID:\n%d\n\n标签:\n%s\n\n语法名:\n%s\n\n发音:\n%s\n\n用法:\n%s\n\n意思:\n%s\n\n解说:\n%s\n\n示例:\n%s", g.ID, g.Tag, g.Name, g.Pronunciation, g.Usage, g.Meaning, g.Explanation, g.Example)
}
var db = &sql.Sqlite{}
func getRandomGrammarByTag(tag string) (g grammar) {
_ = db.Find("grammar", &g, "where tag LIKE '%"+tag+"%' ORDER BY RANDOM() limit 1")
return
}

66
plugin/nihongo/nihongo.go Normal file
View File

@@ -0,0 +1,66 @@
// Package nihongo 日语学习
package nihongo
import (
"time"
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
log "github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
func init() {
engine := control.Register("nihongo", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "日语学习\n- 日语语法[xxx](使用tag随机)",
PublicDataFolder: "Nihongo",
})
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
db.DBPath = engine.DataFolder() + "nihongo.db"
_, err := engine.GetLazyData("nihongo.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("grammar", &grammar{})
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
n, err := db.Count("grammar")
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
log.Infof("[nihongo]读取%d条语法", n)
return true
})
engine.OnRegex(`^日语语法\s?([0-9A-Za-zぁ-んァ-ヶ]{1,6})$`, getdb).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
g := getRandomGrammarByTag(ctx.State["regex_matched"].([]string)[1])
if g.ID == 0 {
ctx.SendChain(message.Text("未能找到", ctx.State["regex_matched"].([]string)[1], "相关标签的语法"))
return
}
data, err := text.RenderToBase64(g.string(), text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if id := ctx.SendChain(message.Image("base64://" + binary.BytesToString(data))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
}

View File

@@ -15,8 +15,8 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
ub "github.com/FloatTech/floatbox/binary"
ctrl "github.com/FloatTech/zbpctrl"
ub "github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
@@ -46,18 +46,18 @@ func init() {
ctx.SendChain(message.Text("少女祈祷中......"))
err := login(username, password)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
searchKey := ctx.State["regex_matched"].([]string)[1]
searchHTML, err := search(searchKey)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
doc, err := htmlquery.Parse(strings.NewReader(searchHTML))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
htmlTitle := htmlquery.InnerText(htmlquery.FindOne(doc, "/html/head/title"))
@@ -65,7 +65,7 @@ func init() {
case websiteTitle:
list, err := htmlquery.QueryAll(doc, "//dl[@id='nr']")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if len(list) != 0 {
@@ -89,11 +89,11 @@ func init() {
}
data, err := text.RenderToBase64(txt, text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if id := ctx.SendChain(message.Image("base64://" + helper.BytesToString(data))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
} else {
text := htmlquery.InnerText(htmlquery.FindOne(doc, "//div[@id='tipss']"))

View File

@@ -3,10 +3,10 @@ package nsfw
import (
"github.com/FloatTech/AnimeAPI/nsfw"
"github.com/FloatTech/floatbox/process"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/process"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
@@ -26,7 +26,7 @@ func init() {
ctx.SendChain(message.Text("少女祈祷中..."))
p, err := nsfw.Classify(url[0])
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(judge(p))))

View File

@@ -10,6 +10,7 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
@@ -28,34 +29,34 @@ func init() { // 插件主体
engine.OnFullMatchGroup([]string{"求签", "占卜"}).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
i := ctxext.RandSenderPerDayN(ctx.Event.UserID, 100) + 1
i := fcext.RandSenderPerDayN(ctx.Event.UserID, 100) + 1
ctx.SendChain(
message.At(ctx.Event.UserID),
message.Image(fmt.Sprintf(bed, i, 0)),
message.Image(fmt.Sprintf(bed, i, 1)),
)
})
engine.OnFullMatch("解签", ctxext.DoOnceOnSuccess(
engine.OnFullMatch("解签", fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
db.DBPath = engine.DataFolder() + "kuji.db"
_, err := engine.GetLazyData("kuji.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("kuji", &kuji{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
n, err := db.Count("kuji")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Infof("[kuji]读取%d条签文", n)
@@ -65,16 +66,16 @@ func init() { // 插件主体
Handle(func(ctx *zero.Ctx) {
kujiBytes, err := text.RenderToBase64(
getKujiByBango(
uint8(ctxext.RandSenderPerDayN(ctx.Event.UserID, 100)+1),
uint8(fcext.RandSenderPerDayN(ctx.Event.UserID, 100)+1),
),
text.FontFile, 400, 20,
)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if id := ctx.SendChain(message.At(ctx.Event.UserID), message.Image("base64://"+helper.BytesToString(kujiBytes))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
}

480
plugin/qqwife/command.go Normal file
View File

@@ -0,0 +1,480 @@
// Package qqwife 娶群友 基于“翻牌”和江林大佬的“群老婆”插件魔改作品文案采用了Hana的zbp娶群友文案
package qqwife
import (
"math/rand"
"sort"
"strconv"
"time"
"github.com/FloatTech/floatbox/math"
ctrl "github.com/FloatTech/zbpctrl"
control "github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
// 反并发
"github.com/wdvxdr1123/ZeroBot/extension/single"
// 数据库
sql "github.com/FloatTech/sqlite"
// 定时器
"github.com/wdvxdr1123/ZeroBot/extension/rate"
// 画图
"github.com/Coloured-glaze/gg"
fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/img/writer"
"github.com/FloatTech/zbputils/img/text"
)
// nolint: asciicheck
//nolint: asciicheck
var (
民政局 = &婚姻登记{
db: &sql.Sqlite{},
}
skillCD = rate.NewManager[string](time.Hour*12, 1)
sendtext = [...][]string{
{ // 表白成功
"是个勇敢的孩子(*/ω\*) 今天的运气都降临在你的身边~\n\n",
"(´・ω・`)对方答应了你 并表示愿意当今天的CP\n\n",
},
{ // 表白失败
"今天的运气有一点背哦~明天再试试叭",
"_(:з」∠)_下次还有机会 咱抱抱你w",
"今天失败了惹. 摸摸头~咱明天还有机会",
},
{ // ntr成功
"因为你的个人魅力~~今天他就是你的了w\n\n",
},
{ // 离婚失败
"打是情,骂是爱,,不打不亲不相爱。答应我不要分手。",
"床头打架床尾和,夫妻没有隔夜仇。安啦安啦,不要闹变扭。",
},
{ // 离婚成功
"离婚成功力\n天涯何处无芳草何必单恋一枝花不如再摘一支bushi",
"离婚成功力\n话说你不考虑当个1",
},
}
)
func init() {
engine := control.Register("qqwife", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
PrivateDataFolder: "qqwife",
Help: "一群一天一夫一妻制群老婆\n每天凌晨刷新CP\n" +
"- 娶群友\n- 群老婆列表\n- 我群老婆\n" +
"--------------------------------\n以下技能每个CD12H不跨天刷新\n--------------------------------\n" +
"- (娶|嫁)@对方QQ\n- 当[对方Q号|@对方QQ]的小三\n- 闹离婚",
}).ApplySingle(single.New(
single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }),
single.WithPostFn[int64](func(ctx *zero.Ctx) {
ctx.Send(
message.ReplyWithMessage(ctx.Event.MessageID,
message.Text("别着急,民政局门口排长队了!"),
),
)
}),
))
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
民政局.db.DBPath = engine.DataFolder() + "结婚登记表.db"
err := 民政局.db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return false
}
return true
})
engine.OnFullMatch("娶群友", zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
_, err := 民政局.开门时间(gid)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
uid := ctx.Event.UserID
targetinfo, status, err := 民政局.查户口(gid, uid)
switch {
case status == "错":
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
case (status == "攻" && targetinfo.Target == 0) ||
(status == "受" && targetinfo.User == 0): // 如果是单身贵族
ctx.SendChain(message.Text("今天你是单身贵族噢"))
return
case status == "攻": // 娶过别人
ctx.SendChain(
message.At(uid),
message.Text("\n今天你已经娶过了群老婆是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(targetinfo.Target, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", targetinfo.Targetname, "]",
"(", targetinfo.Target, ")哒",
),
)
return
case status == "受": // 嫁给别人
ctx.SendChain(
message.At(uid),
message.Text("\n今天你被娶了群老公是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(targetinfo.User, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", targetinfo.Username, "]",
"(", targetinfo.User, ")哒",
),
)
return
}
// 无缓存获取群员列表
temp := ctx.GetThisGroupMemberListNoCache().Array()
sort.SliceStable(temp, func(i, j int) bool {
return temp[i].Get("last_sent_time").Int() < temp[j].Get("last_sent_time").Int()
})
temp = temp[math.Max(0, len(temp)-30):]
// 将已经娶过的人剔除
qqgrouplist := make([]int64, 0, len(temp))
for k := 0; k < len(temp); k++ {
usr := temp[k].Get("user_id").Int()
_, status, _ := 民政局.查户口(gid, usr)
if status != "单" {
continue
}
qqgrouplist = append(qqgrouplist, usr)
}
// 没有人(只剩自己)的时候
if len(qqgrouplist) == 1 {
ctx.SendChain(message.Text("~群里没有ta人是单身了哦 明天再试试叭"))
return
}
// 随机抽娶
fiancee := qqgrouplist[rand.Intn(len(qqgrouplist))]
if fiancee == uid { // 如果是自己
ctx.SendChain(message.Text("呜...没娶到,你可以再尝试一次"))
return
}
// 去民政局办证
err = 民政局.登记(gid, uid, fiancee, ctx.CardOrNickName(uid), ctx.CardOrNickName(fiancee))
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
// 请大家吃席
ctx.SendChain(
message.At(uid),
message.Text("今天你的群老婆是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
"(", fiancee, ")哒",
),
)
})
// 单身技能
engine.OnRegex(`^(娶|嫁)\[CQ:at,qq=(\d+)\]`, zero.OnlyGroup, getdb, checkdog).SetBlock(true).Limit(cdcheck, iscding).
Handle(func(ctx *zero.Ctx) {
choice := ctx.State["regex_matched"].([]string)[1]
fiancee, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
uid := ctx.Event.UserID
gid := ctx.Event.GroupID
if uid == fiancee { // 如果是自己
switch rand.Intn(3) {
case 1:
err := 民政局.登记(gid, uid, 0, "", "")
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
ctx.SendChain(message.Text("今日获得成就:单身贵族"))
default:
ctx.SendChain(message.Text("今日获得成就:自恋狂"))
}
return
}
if rand.Intn(2) == 0 { // 二分之一的概率表白成功
ctx.SendChain(message.Text(sendtext[1][rand.Intn(len(sendtext[1]))]))
return
}
// 去民政局登记
var choicetext string
switch choice {
case "娶":
err := 民政局.登记(gid, uid, fiancee, ctx.CardOrNickName(uid), ctx.CardOrNickName(fiancee))
if err != nil {
ctx.SendChain(message.Text("结婚登记失败力\n[error]", err))
return
}
choicetext = "\n今天你的群老婆是"
default:
err := 民政局.登记(gid, fiancee, uid, ctx.CardOrNickName(fiancee), ctx.CardOrNickName(uid))
if err != nil {
ctx.SendChain(message.Text("结婚登记失败力\n[error]", err))
return
}
choicetext = "\n今天你的群老公是"
}
// 请大家吃席
ctx.SendChain(
message.Text(sendtext[0][rand.Intn(len(sendtext[0]))]),
message.At(uid),
message.Text(choicetext),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
"(", fiancee, ")哒",
),
)
})
// NTR技能
engine.OnRegex(`^当(\[CQ:at,qq=(\d+)\]\s?|(\d+))的小三`, zero.OnlyGroup, getdb, checkcp).SetBlock(true).Limit(cdcheck2, iscding).
Handle(func(ctx *zero.Ctx) {
fid := ctx.State["regex_matched"].([]string)
fiancee, _ := strconv.ParseInt(fid[2]+fid[3], 10, 64)
uid := ctx.Event.UserID
if fiancee == uid {
ctx.SendChain(message.Text("今日获得成就:自我攻略"))
return
}
if rand.Intn(10)/4 != 0 { // 十分之三的概率NTR成功
ctx.SendChain(message.Text("失败了!可惜"))
return
}
gid := ctx.Event.GroupID
// 判断target是老公还是老婆
var choicetext string
userID := uid
targetID := fiancee
fianceeinfo, gender, err := 民政局.查户口(gid, fiancee)
switch gender {
case "单":
ctx.SendChain(message.Text("ta现在还是单身哦快向ta表白吧"))
return
case "错":
ctx.SendChain(message.Text("对象状态查询失败\n[error]", err))
return
case "攻":
err = 民政局.离婚休妻(gid, fianceeinfo.Target)
if err != nil {
ctx.SendChain(message.Text("ta不想和原来的对象分手...\n[error]", err))
return
}
userID = fiancee
targetID = uid
choicetext = "老公"
case "受":
err = 民政局.离婚休夫(gid, fianceeinfo.User)
if err != nil {
ctx.SendChain(message.Text("ta不想和原来的对象分手...\n[error]", err))
return
}
choicetext = "老婆"
default:
ctx.SendChain(message.Text("数据库发生问题力"))
return
}
err = 民政局.登记(gid, userID, targetID, ctx.CardOrNickName(userID), ctx.CardOrNickName(targetID))
if err != nil {
ctx.SendChain(message.Text("复婚登记失败力\n[error]", err))
return
}
// 输出结果
ctx.SendChain(
message.Text(sendtext[2][rand.Intn(len(sendtext[2]))]),
message.At(uid),
message.Text("今天你的群"+choicetext+"是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
"(", fiancee, ")哒",
),
)
})
engine.OnFullMatch("群老婆列表", zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
ok, err := 民政局.开门时间(gid)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
if ok {
ctx.SendChain(message.Text("今天还没有人结婚哦"))
return
}
list, number, err := 民政局.花名册(gid)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
if number <= 0 {
ctx.SendChain(message.Text("今天还没有人结婚哦"))
return
}
/***********设置图片的大小和底色***********/
fontSize := 50.0
if number < 10 {
number = 10
}
canvas := gg.NewContext(1500, int(250+fontSize*float64(number)))
canvas.SetRGB(1, 1, 1) // 白色
canvas.Clear()
/***********下载字体,可以注销掉***********/
_, err = file.GetLazyData(text.BoldFontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
}
/***********设置字体颜色为黑色***********/
canvas.SetRGB(0, 0, 0)
/***********设置字体大小,并获取字体高度用来定位***********/
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize*2); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
sl, h := canvas.MeasureString("群老婆列表")
/***********绘制标题***********/
canvas.DrawString("群老婆列表", (1500-sl)/2, 160-h) // 放置在中间位置
canvas.DrawString("————————————————————", 0, 250-h)
/***********设置字体大小,并获取字体高度用来定位***********/
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize); err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
_, h = canvas.MeasureString("焯")
for i, info := range list {
canvas.DrawString(slicename(info[0], canvas), 0, float64(260+50*i)-h)
canvas.DrawString("("+info[1]+")", 350, float64(260+50*i)-h)
canvas.DrawString("←→", 700, float64(260+50*i)-h)
canvas.DrawString(slicename(info[2], canvas), 800, float64(260+50*i)-h)
canvas.DrawString("("+info[3]+")", 1150, float64(260+50*i)-h)
}
data, cl := writer.ToBytes(canvas.Image())
ctx.SendChain(message.ImageBytes(data))
cl()
})
engine.OnFullMatchGroup([]string{"闹离婚", "办离婚"}, zero.OnlyGroup, getdb, func(ctx *zero.Ctx) bool {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
_, uidstatus, err := 民政局.查户口(gid, uid)
switch uidstatus {
case "错":
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return false
case "单":
ctx.SendChain(message.Text("今天你还没结婚哦"))
return false
}
return true
}).SetBlock(true).Limit(cdcheck3, iscding2).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
uid := ctx.Event.UserID
info, uidstatus, err := 民政局.查户口(gid, uid)
mun := 2
switch uidstatus {
case "错":
ctx.SendChain(message.Text("用户状态查询失败\n[error]", err))
return
case "攻":
mun = 1
case "受":
mun = 0
}
if rand.Intn(10) != 1 { // 十分之一的概率成功
ctx.SendChain(message.Text(sendtext[3][rand.Intn(len(sendtext[3]))]))
return
}
switch mun {
case 1:
err = 民政局.离婚休妻(gid, info.Target)
case 0:
err = 民政局.离婚休妻(gid, info.Target)
default:
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
ctx.SendChain(message.Text(sendtext[4][mun]))
})
engine.OnRegex(`^重置(所有|本群|/d+)?花名册$`, zero.SuperUserPermission, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
cmd := "0"
switch ctx.State["regex_matched"].([]string)[1] {
case "":
if ctx.Event.GroupID == 0 {
ctx.SendChain(message.Text("该功能只能在群组使用或者指定群组"))
return
}
cmd = strconv.FormatInt(ctx.Event.GroupID, 10)
case "所有":
break
case "本群":
if ctx.Event.GroupID == 0 {
ctx.SendChain(message.Text("该功能只能在群组使用或者指定群组"))
return
}
cmd = strconv.FormatInt(ctx.Event.GroupID, 10)
default:
cmd = ctx.State["regex_matched"].([]string)[1]
}
err := 民政局.清理花名册(cmd)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
ctx.SendChain(message.Text("重置成功"))
})
engine.OnFullMatch("我群老婆", zero.OnlyGroup, getdb).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
ok, err := 民政局.开门时间(gid)
if err != nil {
ctx.SendChain(message.Text("群状态查询失败\n[error]", err))
return
}
if ok {
ctx.SendChain(message.Text("今天你还没结婚哦"))
return
}
uid := ctx.Event.UserID
info, uidstatus, err := 民政局.查户口(gid, uid)
switch uidstatus {
case "错":
ctx.SendChain(message.Text("用户状态查询失败\n[error]", err))
return
case "单":
ctx.SendChain(message.Text("今天你还没结婚哦"))
return
case "攻": // 娶过别人
ctx.SendChain(
message.At(uid),
message.Text("\n今天你的群老婆是"),
message.Text(
"\n",
"[", info.Targetname, "]",
"(", info.Target, ")哒",
),
)
return
case "受": // 嫁给别人
ctx.SendChain(
message.At(uid),
message.Text("\n今天你被娶了群老公是"),
message.Text(
"\n",
"[", info.Username, "]",
"(", info.User, ")哒",
),
)
return
}
})
}

351
plugin/qqwife/function.go Normal file
View File

@@ -0,0 +1,351 @@
package qqwife
import (
"strconv"
"sync"
"time"
sql "github.com/FloatTech/sqlite"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/extension/rate"
"github.com/wdvxdr1123/ZeroBot/message"
// 画图
"github.com/Coloured-glaze/gg"
)
//nolint: asciicheck
// nolint: asciicheck
type 婚姻登记 struct {
db *sql.Sqlite
dbmu sync.RWMutex
}
// 结婚证信息
type userinfo struct {
User int64 // 用户身份证
Target int64 // 对象身份证号
Username string // 户主名称
Targetname string // 对象名称
Updatetime string // 登记时间
}
// 民政局的当前时间
type updateinfo struct {
GID int64
Updatetime string // 登记时间
}
func (sql *婚姻登记) 开门时间(gid int64) (ok bool, err error) {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
ok = false
err = sql.db.Create("updateinfo", &updateinfo{})
if err != nil {
return
}
gidstr := strconv.FormatInt(gid, 10)
dbinfo := updateinfo{}
// 获取表格更新的时间
err = sql.db.Find("updateinfo", &dbinfo, "where gid is "+gidstr)
if err != nil {
// 如果没有登记过就记录
err = sql.db.Insert("updateinfo", &updateinfo{
GID: gid,
Updatetime: time.Now().Format("2006/01/02"),
})
if err == nil {
ok = true
}
return
}
// 开门了就拿新的花名册
if time.Now().Format("2006/01/02") == dbinfo.Updatetime {
return
}
err = sql.db.Drop(gidstr)
if err != nil {
return
}
updateinfo := updateinfo{
GID: gid,
Updatetime: time.Now().Format("2006/01/02"),
}
err = sql.db.Insert("updateinfo", &updateinfo)
if err == nil {
ok = true
}
return
}
func (sql *婚姻登记) 清理花名册(gid string) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
grouplist, err := sql.db.ListTables()
if err != nil {
return err
}
if gid != "0" {
grouplist = []string{gid}
}
for _, gid := range grouplist {
err = sql.db.Drop(gid)
if err != nil {
continue
}
gidint, _ := strconv.ParseInt(gid, 10, 64)
updateinfo := updateinfo{
GID: gidint,
Updatetime: time.Now().Format("2006/01/02"),
}
err = sql.db.Insert("updateinfo", &updateinfo)
}
return err
}
func (sql *婚姻登记) 查户口(gid, uid int64) (info userinfo, status string, err error) {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
uidstr := strconv.FormatInt(uid, 10)
status = "单"
err = sql.db.Create(gidstr, &userinfo{})
if err != nil {
status = "错"
return
}
err = sql.db.Find(gidstr, &info, "where user = "+uidstr)
if err == nil {
status = "攻"
return
}
err = sql.db.Find(gidstr, &info, "where target = "+uidstr)
if err == nil {
status = "受"
}
return
}
func (sql *婚姻登记) 登记(gid, uid, target int64, username, targetname string) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
err := sql.db.Create(gidstr, &userinfo{})
if err != nil {
return err
}
updatetime := time.Now().Format("2006/01/02")
// 填写夫妻信息
uidinfo := userinfo{
User: uid,
Username: username,
Target: target,
Targetname: targetname,
Updatetime: updatetime,
}
// 民政局登记数据
err = sql.db.Insert(gidstr, &uidinfo)
return err
}
func (sql *婚姻登记) 离婚休妻(gid, wife int64) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
wifestr := strconv.FormatInt(wife, 10)
return sql.db.Del(gidstr, "where target = "+wifestr)
}
func (sql *婚姻登记) 离婚休夫(gid, husband int64) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
husbandstr := strconv.FormatInt(husband, 10)
return sql.db.Del(gidstr, "where user = "+husbandstr)
}
func (sql *婚姻登记) 花名册(gid int64) (list [][4]string, number int, err error) {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
err = sql.db.Create(gidstr, &userinfo{})
if err != nil {
return
}
number, err = sql.db.Count(gidstr)
if err != nil || number <= 0 {
return
}
var info userinfo
list = make([][4]string, 0, number)
err = sql.db.FindFor(gidstr, &info, "GROUP BY user", func() error {
if info.Target == 0 {
return nil
}
dbinfo := [4]string{
info.Username,
strconv.FormatInt(info.User, 10),
info.Targetname,
strconv.FormatInt(info.Target, 10),
}
list = append(list, dbinfo)
return nil
})
if len(list) == 0 {
number = 0
}
return
}
func slicename(name string, canvas *gg.Context) (resultname string) {
usermane := []rune(name) // 将每个字符单独放置
widthlen := 0
numberlen := 0
for i, v := range usermane {
width, _ := canvas.MeasureString(string(v)) // 获取单个字符的宽度
widthlen += int(width)
if widthlen > 350 {
break // 总宽度不能超过350
}
numberlen = i
}
if widthlen > 350 {
resultname = string(usermane[:numberlen-1]) + "......" // 名字切片
} else {
resultname = name
}
return
}
// 以群号和昵称为限制
func cdcheck(ctx *zero.Ctx) *rate.Limiter {
limitID := strconv.FormatInt(ctx.Event.GroupID, 10) + strconv.FormatInt(ctx.Event.UserID, 10) + "1"
return skillCD.Load(limitID)
}
func cdcheck2(ctx *zero.Ctx) *rate.Limiter {
limitID := strconv.FormatInt(ctx.Event.GroupID, 10) + strconv.FormatInt(ctx.Event.UserID, 10) + "2"
return skillCD.Load(limitID)
}
func cdcheck3(ctx *zero.Ctx) *rate.Limiter {
limitID := strconv.FormatInt(ctx.Event.GroupID, 10) + strconv.FormatInt(ctx.Event.UserID, 10) + "3"
return skillCD.Load(limitID)
}
func iscding(ctx *zero.Ctx) {
ctx.SendChain(message.Text("你的技能现在正在CD中"))
}
func iscding2(ctx *zero.Ctx) {
ctx.SendChain(message.Text("打灭,禁止离婚 (你的技能正在CD中)"))
}
// 注入判断 是否为单身
func checkdog(ctx *zero.Ctx) bool {
// 得先判断用户是否存在才行在,再重置
fiancee, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
if err != nil {
ctx.SendChain(message.Text("额你的target好像不存在"))
return false
}
// 判断是否需要重置
gid := ctx.Event.GroupID
ok, err := 民政局.开门时间(gid)
if err != nil {
ctx.SendChain(message.Text("群状态查询失败\n[error]", err))
return false
}
if ok {
return true // 重置后也全是单身
}
// 获取用户信息
uid := ctx.Event.UserID
uidtarget, uidstatus, err := 民政局.查户口(gid, uid)
switch {
case uidstatus == "错":
ctx.SendChain(message.Text("用户状态查询失败\n[error]", err))
return false
case uidstatus != "单" && (uidtarget.Target == 0 || uidtarget.User == 0): // 如果是单身贵族
ctx.SendChain(message.Text("今天的你是单身贵族噢"))
return false
case (uidstatus == "攻" && uidtarget.Target == fiancee) ||
(uidstatus == "受" && uidtarget.User == fiancee):
ctx.SendChain(message.Text("笨蛋!你们已经在一起了!"))
return false
case uidstatus == "攻": // 如果如为攻
ctx.SendChain(message.Text("笨蛋~你家里还有个吃白饭的w"))
return false
case uidstatus == "受": // 如果为受
ctx.SendChain(message.Text("该是0就是0当0有什么不好"))
return false
}
fianceeinfo, fianceestatus, err := 民政局.查户口(gid, fiancee)
switch {
case fianceestatus == "错":
ctx.SendChain(message.Text("对象状态查询失败\n[error]", err))
case fianceestatus == "单": // 如果为单身狗
return true
case fianceestatus != "单" && (fianceeinfo.Target == 0 || fianceeinfo.User == 0): // 如果是单身贵族
ctx.SendChain(message.Text("今天的ta是单身贵族噢"))
case fianceestatus == "攻": // 如果如为攻
ctx.SendChain(message.Text("他有别的女人了,你该放下了"))
case fianceestatus == "受": // 如果为受
ctx.SendChain(message.Text("ta被别人娶了你来晚力"))
}
return false
}
// 注入判断 是否满足小三要求
func checkcp(ctx *zero.Ctx) bool {
// 得先判断用户是否存在才行在,再重置
fiancee, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
if err != nil {
ctx.SendChain(message.Text("额你的target好像不存在"))
return false
}
// 判断是否需要重置
gid := ctx.Event.GroupID
ok, err := 民政局.开门时间(gid)
if err != nil {
ctx.SendChain(message.Text("群状态查询失败\n[error]", err))
return false
}
if ok {
ctx.SendChain(message.Text("ta现在还是单身哦快向ta表白吧"))
return false // 重置后也全是单身
}
uid := ctx.Event.UserID
fianceeinfo, fianceestatus, err := 民政局.查户口(gid, fiancee)
switch {
case fianceestatus == "错":
ctx.SendChain(message.Text("对象状态查询失败\n[error]", err))
return false
case fianceestatus == "单": // 如果为单身狗
if fiancee == uid {
return true
}
ctx.SendChain(message.Text("ta现在还是单身哦快向ta表白吧"))
return false
case fianceestatus != "单" && (fianceeinfo.Target == 0 || fianceeinfo.User == 0): // 如果是单身贵族
ctx.SendChain(message.Text("今天的ta是单身贵族噢"))
return false
case (fianceestatus == "攻" && fianceeinfo.Target == fiancee) ||
(fianceestatus == "受" && fianceeinfo.User == fiancee):
ctx.SendChain(message.Text("笨蛋!你们已经在一起了!"))
return false
}
// 获取用户信息
uidtarget, uidstatus, err := 民政局.查户口(gid, uid)
switch {
case uidstatus == "错":
ctx.SendChain(message.Text("用户状态查询失败\n[error]", err))
case uidstatus == "单": // 如果为单身狗
return true
case uidstatus != "单" && (uidtarget.Target == 0 || uidtarget.User == 0): // 如果是单身贵族
ctx.SendChain(message.Text("今天的你是单身贵族噢"))
case uidstatus == "攻": // 如果如为攻
ctx.SendChain(message.Text("打灭,不给纳小妾!"))
case uidstatus == "受": // 如果为受
ctx.SendChain(message.Text("该是0就是0当0有什么不好"))
}
return false
}

View File

@@ -1,781 +0,0 @@
// Package qqwife 娶群友 基于“翻牌”和江林大佬的“群老婆”插件魔改作品文案采用了Hana的zbp娶群友文案
package qqwife
import (
"math/rand"
"sort"
"strconv"
"sync"
"time"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
ctrl "github.com/FloatTech/zbpctrl"
control "github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/math"
// 数据库
sql "github.com/FloatTech/sqlite"
// 定时器
"github.com/wdvxdr1123/ZeroBot/extension/rate"
// 画图
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/img/text"
"github.com/FloatTech/zbputils/img/writer"
"github.com/fogleman/gg"
)
//nolint: asciicheck
type 婚姻登记 struct {
db *sql.Sqlite
dbmu sync.RWMutex
}
// 结婚证信息
type userinfo struct {
User int64 // 用户身份证
Target int64 // 对象身份证号
Username string // 户主名称
Targetname string // 对象名称
Updatetime string // 登记时间
}
// 民政局的当前时间
type updateinfo struct {
GID int64
Updatetime string // 登记时间
}
func (sql *婚姻登记) checkupdate(gid int64) (updatetime string, err error) {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
err = sql.db.Create("updateinfo", &updateinfo{})
if err != nil {
return
}
gidstr := strconv.FormatInt(gid, 10)
dbinfo := updateinfo{}
err = sql.db.Find("updateinfo", &dbinfo, "where gid is "+gidstr) // 获取表格更新的时间
if err != nil {
updatetime = time.Now().Format("2006/01/02")
err = sql.db.Insert("updateinfo", &updateinfo{GID: gid, Updatetime: updatetime})
return
}
updatetime = dbinfo.Updatetime
return
}
func (sql *婚姻登记) 重置(gid string) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
if gid != "ALL" {
err := sql.db.Drop(gid)
if err != nil {
err = sql.db.Create(gid, &userinfo{})
return err
}
gidint, _ := strconv.ParseInt(gid, 10, 64)
updateinfo := updateinfo{
GID: gidint,
Updatetime: time.Now().Format("2006/01/02"),
}
err = sql.db.Insert("updateinfo", &updateinfo)
return err
}
grouplist, err := sql.db.ListTables()
if err != nil {
return err
}
for _, gid := range grouplist {
err = sql.db.Drop(gid)
if err != nil {
continue
}
gidint, _ := strconv.ParseInt(gid, 10, 64)
updateinfo := updateinfo{
GID: gidint,
Updatetime: time.Now().Format("2006/01/02"),
}
err = sql.db.Insert("updateinfo", &updateinfo)
}
return err
}
func (sql *婚姻登记) 离婚休妻(gid, wife int64) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
wifestr := strconv.FormatInt(wife, 10)
// 先判断用户是否存在
err := sql.db.Del(gidstr, "where target = "+wifestr)
return err
}
func (sql *婚姻登记) 离婚休夫(gid, husband int64) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
husbandstr := strconv.FormatInt(husband, 10)
// 先判断用户是否存在
err := sql.db.Del(gidstr, "where target = "+husbandstr)
return err
}
func (sql *婚姻登记) 复婚(gid, uid, target int64, username, targetname string) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
uidstr := strconv.FormatInt(uid, 10)
tagstr := strconv.FormatInt(target, 10)
var info userinfo
err := sql.db.Find(gidstr, &info, "where user = "+uidstr)
if err == nil {
err = sql.db.Find(gidstr, &info, "where user = "+tagstr)
}
if err == nil {
return err
}
updatetime := time.Now().Format("2006/01/02")
// 更改夫妻信息
info.User = uid
info.Username = username
info.Target = target
info.Targetname = targetname
info.Updatetime = updatetime
// 民政局登记数据
err = sql.db.Insert(gidstr, &info)
return err
}
func (sql *婚姻登记) 花名册(gid int64) (list [][4]string, number int, err error) {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
err = sql.db.Create(gidstr, &userinfo{})
if err != nil {
return
}
number, err = sql.db.Count(gidstr)
if err != nil || number <= 0 {
return
}
var info userinfo
list = make([][4]string, 0, number)
err = sql.db.FindFor(gidstr, &info, "GROUP BY user", func() error {
dbinfo := [4]string{
info.Username,
strconv.FormatInt(info.User, 10),
info.Targetname,
strconv.FormatInt(info.Target, 10),
}
list = append(list, dbinfo)
return nil
})
return
}
func slicename(name string, canvas *gg.Context) (resultname string) {
usermane := []rune(name) // 将每个字符单独放置
widthlen := 0
numberlen := 0
for i, v := range usermane {
width, _ := canvas.MeasureString(string(v)) // 获取单个字符的宽度
widthlen += int(width)
if widthlen > 350 {
break // 总宽度不能超过350
}
numberlen = i
}
if widthlen > 350 {
resultname = string(usermane[:numberlen-1]) + "......" // 名字切片
} else {
resultname = name
}
return
}
func (sql *婚姻登记) 查户口(gid, uid int64) (info userinfo, status int, err error) {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
uidstr := strconv.FormatInt(uid, 10)
status = 3
if err = sql.db.Create(gidstr, &userinfo{}); err != nil {
status = 2
return
}
err = sql.db.Find(gidstr, &info, "where user = "+uidstr)
if err == nil {
status = 1
return
}
err = sql.db.Find(gidstr, &info, "where target = "+uidstr)
if err == nil {
status = 0
return
}
return
}
func (sql *婚姻登记) 登记(gid, uid, target int64, username, targetname string) error {
sql.dbmu.Lock()
defer sql.dbmu.Unlock()
gidstr := strconv.FormatInt(gid, 10)
err := sql.db.Create(gidstr, &userinfo{})
if err != nil {
return err
}
updatetime := time.Now().Format("2006/01/02")
// 填写夫妻信息
uidinfo := userinfo{
User: uid,
Username: username,
Target: target,
Targetname: targetname,
Updatetime: updatetime,
}
// 民政局登记数据
err = sql.db.Insert(gidstr, &uidinfo)
return err
}
var (
//nolint: asciicheck
民政局 = &婚姻登记{
db: &sql.Sqlite{},
}
skillCD = rate.NewManager[string](time.Hour*12, 1)
sendtext = [...][]string{
{ // 表白成功
"是个勇敢的孩子(*/ω\*) 今天的运气都降临在你的身边~\n\n",
"(´・ω・`)对方答应了你 并表示愿意当今天的CP\n\n",
},
{ // 表白失败
"今天的运气有一点背哦~明天再试试叭",
"_(:з」∠)_下次还有机会 咱抱抱你w",
"今天失败了惹. 摸摸头~咱明天还有机会",
},
{ // ntr成功
"因为你的个人魅力~~今天他就是你的了w\n\n",
},
{ // 离婚失败
"打是情,骂是爱,,不打不亲不相爱。答应我不要分手。",
"床头打架床尾和,夫妻没有隔夜仇。安啦安啦,不要闹变扭。",
},
{ // 离婚成功
"离婚成功力\n天涯何处无芳草何必单恋一枝花不如再摘一支bushi",
"离婚成功力\n话说你不考虑当个1",
},
}
)
func init() {
engine := control.Register("qqwife", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
PrivateDataFolder: "qqwife",
Help: "一群一天一夫一妻制群老婆\n每天凌晨刷新CP\n" +
"- 娶群友\n- 群老婆列表\n" +
"--------------------------------\n以下技能每人只能三选一\n CD12H不跨天刷新\n--------------------------------\n" +
"- (娶|嫁)@对方QQ\n- 当[对方Q号|@对方QQ]的小三\n- 闹离婚",
})
getdb := ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
民政局.db.DBPath = engine.DataFolder() + "结婚登记表.db"
// 如果数据库不存在则下载
// _, _ = engine.GetLazyData("结婚登记表.db", false)
err := 民政局.db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return false
}
return true
})
engine.OnFullMatch("娶群友", zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
updatetime, err := 民政局.checkupdate(gid)
switch {
case err != nil:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
case time.Now().Format("2006/01/02") != updatetime:
if err := 民政局.重置(strconv.FormatInt(gid, 10)); err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
}
uid := ctx.Event.UserID
targetinfo, status, err := 民政局.查户口(gid, uid)
switch {
case status == 2:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
case status != 3 && targetinfo.Target == 0: // 如果为单身贵族
ctx.SendChain(message.Text("今天你是单身贵族噢"))
return
case status == 1: // 娶过别人
ctx.SendChain(
message.At(uid),
message.Text("\n今天你已经娶过了群老婆是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(targetinfo.Target, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", targetinfo.Targetname, "]",
"(", targetinfo.Target, ")哒",
),
)
return
case status == 0: // 嫁给别人
ctx.SendChain(
message.At(uid),
message.Text("\n今天你被娶了群老公是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(targetinfo.User, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", targetinfo.Username, "]",
"(", targetinfo.User, ")哒",
),
)
return
}
// 无缓存获取群员列表
temp := ctx.GetThisGroupMemberListNoCache().Array()
sort.SliceStable(temp, func(i, j int) bool {
return temp[i].Get("last_sent_time").Int() < temp[j].Get("last_sent_time").Int()
})
temp = temp[math.Max(0, len(temp)-30):]
// 将已经娶过的人剔除
qqgrouplist := make([]int64, 0, len(temp))
for k := 0; k < len(temp); k++ {
usr := temp[k].Get("user_id").Int()
_, status, _ := 民政局.查户口(gid, usr)
if status != 3 {
continue
}
qqgrouplist = append(qqgrouplist, usr)
}
// 没有人(只剩自己)的时候
if len(qqgrouplist) == 1 {
ctx.SendChain(message.Text("~群里没有ta人是单身了哦 明天再试试叭"))
return
}
// 随机抽娶
fiancee := qqgrouplist[rand.Intn(len(qqgrouplist))]
if fiancee == uid { // 如果是自己
ctx.SendChain(message.Text("呜...没娶到,你可以再尝试一次"))
return
}
// 去民政局办证
err = 民政局.登记(gid, uid, fiancee, ctx.CardOrNickName(uid), ctx.CardOrNickName(fiancee))
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
// 请大家吃席
ctx.SendChain(
message.At(uid),
message.Text("今天你的群老婆是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
"(", fiancee, ")哒",
),
)
})
// 单身技能
engine.OnRegex(`^(娶|嫁)\[CQ:at,qq=(\d+)\]`, zero.OnlyGroup, getdb, checkdog).SetBlock(true).Limit(cdcheck, iscding).
Handle(func(ctx *zero.Ctx) {
choice := ctx.State["regex_matched"].([]string)[1]
fiancee, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
uid := ctx.Event.UserID
gid := ctx.Event.GroupID
if uid == fiancee { // 如果是自己
switch rand.Intn(2) { // 二分之一概率浪费技能
case 0:
ctx.SendChain(message.Text("今日获得成就:自恋狂"))
default:
err := 民政局.登记(gid, uid, 0, "", "")
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
ctx.SendChain(message.Text("今日获得成就:单身贵族"))
}
return
}
if rand.Intn(2) == 0 { // 二分之一的概率表白成功
ctx.SendChain(message.Text(sendtext[1][rand.Intn(len(sendtext[1]))]))
return
}
// 去民政局登记
var choicetext string
switch choice {
case "娶":
err := 民政局.登记(gid, uid, fiancee, ctx.CardOrNickName(uid), ctx.CardOrNickName(fiancee))
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
choicetext = "\n今天你的群老婆是"
default:
err := 民政局.登记(gid, fiancee, uid, ctx.CardOrNickName(fiancee), ctx.CardOrNickName(uid))
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
choicetext = "\n今天你的群老公是"
}
// 请大家吃席
ctx.SendChain(
message.Text(sendtext[0][rand.Intn(len(sendtext[0]))]),
message.At(uid),
message.Text(choicetext),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
"(", fiancee, ")哒",
),
)
})
// NTR技能
engine.OnRegex(`^当(\[CQ:at,qq=(\d+)\]\s?|(\d+))的小三`, zero.OnlyGroup, getdb, checkcp).SetBlock(true).Limit(cdcheck, iscding).
Handle(func(ctx *zero.Ctx) {
fid := ctx.State["regex_matched"].([]string)
fiancee, _ := strconv.ParseInt(fid[2]+fid[3], 10, 64)
uid := ctx.Event.UserID
if fiancee == uid {
ctx.SendChain(message.Text("今日获得成就:自我攻略"))
return
}
if rand.Intn(10)/4 != 0 { // 十分之三的概率NTR成功
ctx.SendChain(message.Text("失败了!可惜"))
return
}
gid := ctx.Event.GroupID
// 判断target是老公还是老婆
var choicetext string
_, gender, err := 民政局.查户口(gid, fiancee)
switch gender {
case 3:
ctx.SendChain(message.Text("ta现在还是单身哦快向ta表白吧"))
return
case 2:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
case 1:
// 和对象结婚登记
err = 民政局.复婚(gid, fiancee, uid, ctx.CardOrNickName(fiancee), ctx.CardOrNickName(uid))
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
choicetext = "老公"
case 0:
// 和对象结婚登记
err = 民政局.复婚(gid, uid, fiancee, ctx.CardOrNickName(uid), ctx.CardOrNickName(fiancee))
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
choicetext = "老婆"
default:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员"))
return
}
// 输出结果
ctx.SendChain(
message.Text(sendtext[2][rand.Intn(len(sendtext[2]))]),
message.At(uid),
message.Text("今天你的群"+choicetext+"是"),
message.Image("http://q4.qlogo.cn/g?b=qq&nk="+strconv.FormatInt(fiancee, 10)+"&s=640").Add("cache", 0),
message.Text(
"\n",
"[", ctx.CardOrNickName(fiancee), "]",
"(", fiancee, ")哒",
),
)
})
engine.OnFullMatch("群老婆列表", zero.OnlyGroup, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
updatetime, err := 民政局.checkupdate(gid)
switch {
case err != nil:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
case time.Now().Format("2006/01/02") != updatetime:
if err := 民政局.重置(strconv.FormatInt(gid, 10)); err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
ctx.SendChain(message.Text("今天还没有人结婚哦"))
return
}
list, number, err := 民政局.花名册(gid)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
if number <= 0 {
ctx.SendChain(message.Text("今天还没有人结婚哦"))
return
}
/***********设置图片的大小和底色***********/
fontSize := 50.0
if number < 10 {
number = 10
}
canvas := gg.NewContext(1500, int(250+fontSize*float64(number)))
canvas.SetRGB(1, 1, 1) // 白色
canvas.Clear()
/***********下载字体,可以注销掉***********/
_, err = file.GetLazyData(text.BoldFontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
}
/***********设置字体颜色为黑色***********/
canvas.SetRGB(0, 0, 0)
/***********设置字体大小,并获取字体高度用来定位***********/
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize*2); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
sl, h := canvas.MeasureString("群老婆列表")
/***********绘制标题***********/
canvas.DrawString("群老婆列表", (1500-sl)/2, 160-h) // 放置在中间位置
canvas.DrawString("————————————————————", 0, 250-h)
/***********设置字体大小,并获取字体高度用来定位***********/
if err = canvas.LoadFontFace(text.BoldFontFile, fontSize); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return
}
_, h = canvas.MeasureString("焯")
for i, info := range list {
canvas.DrawString(slicename(info[0], canvas), 0, float64(260+50*i)-h)
canvas.DrawString("("+info[1]+")", 350, float64(260+50*i)-h)
canvas.DrawString("←→", 700, float64(260+50*i)-h)
canvas.DrawString(slicename(info[2], canvas), 800, float64(260+50*i)-h)
canvas.DrawString("("+info[3]+")", 1150, float64(260+50*i)-h)
}
data, cl := writer.ToBytes(canvas.Image())
ctx.SendChain(message.ImageBytes(data))
cl()
})
engine.OnFullMatchGroup([]string{"闹离婚", "办离婚"}, zero.OnlyGroup, getdb).SetBlock(true).Limit(cdcheck, iscding2).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
updatetime, err := 民政局.checkupdate(gid)
switch {
case err != nil:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
case time.Now().Format("2006/01/02") != updatetime:
if err := 民政局.重置(strconv.FormatInt(gid, 10)); err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
ctx.SendChain(message.Text("今天你还没有结婚哦"))
return
}
// 获取用户信息
uid := ctx.Event.UserID
info, uidstatus, err := 民政局.查户口(gid, uid)
switch uidstatus {
case 3:
return
case 2:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
case 1:
if rand.Intn(10) != 1 { // 十分之一的概率成功
ctx.SendChain(message.Text(sendtext[3][rand.Intn(len(sendtext[3]))]))
return
}
err := 民政局.离婚休妻(gid, info.Target)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
ctx.SendChain(message.Text(sendtext[4][0]))
case 0:
if rand.Intn(10) != 0 { // 十分之一的概率成功
ctx.SendChain(message.Text(sendtext[3][rand.Intn(len(sendtext[3]))]))
return
}
err := 民政局.离婚休夫(gid, info.User)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return
}
ctx.SendChain(message.Text(sendtext[4][1]))
}
})
engine.OnRegex(`重置(所有|本群|/d+)?花名册`, zero.SuperUserPermission, getdb).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
cmd := "ALL"
switch ctx.State["regex_matched"].([]string)[1] {
case "":
if ctx.Event.GroupID == 0 {
ctx.SendChain(message.Text("该功能只能在群组使用或者指定群组"))
return
}
cmd = strconv.FormatInt(ctx.Event.GroupID, 10)
case "所有":
break
case "本群":
if ctx.Event.GroupID == 0 {
ctx.SendChain(message.Text("该功能只能在群组使用或者指定群组"))
return
}
cmd = strconv.FormatInt(ctx.Event.GroupID, 10)
default:
cmd = ctx.State["regex_matched"].([]string)[1]
}
err := 民政局.重置(cmd)
if err != nil {
ctx.SendChain(message.Text("数据库发生问题力\n[error]", err))
return
}
ctx.SendChain(message.Text("重置成功"))
})
}
// 以群号和昵称为限制
func cdcheck(ctx *zero.Ctx) *rate.Limiter {
limitID := strconv.FormatInt(ctx.Event.GroupID, 10) + strconv.FormatInt(ctx.Event.UserID, 10)
return skillCD.Load(limitID)
}
func iscding(ctx *zero.Ctx) {
ctx.SendChain(message.Text("你的技能现在正在CD中"))
}
// 注入判断 是否为单身
func checkdog(ctx *zero.Ctx) bool {
// 得先判断用户是否存在才行在,再重置
fiancee, err := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64)
if err != nil {
ctx.SendChain(message.Text("额你的target好像不存在"))
return false
}
// 判断是否需要重置
gid := ctx.Event.GroupID
updatetime, err := 民政局.checkupdate(gid)
switch {
case err != nil:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return false
case time.Now().Format("2006/01/02") != updatetime:
if err := 民政局.重置(strconv.FormatInt(gid, 10)); err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return false
}
return true // 重置后也全是单身
}
uid := ctx.Event.UserID
// 获取用户信息
uidtarget, uidstatus, err1 := 民政局.查户口(gid, uid)
fianceeinfo, fianceestatus, err2 := 民政局.查户口(gid, fiancee)
if uidstatus == 2 || fianceestatus == 2 {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err1, "\n", err2))
return false
}
if uidstatus == 3 && fianceestatus == 3 { // 必须是两个单身
return true
}
switch {
case uidtarget.Target == fiancee: // 如果本就是一块
ctx.SendChain(message.Text("笨蛋~你们明明已经在一起了啊w"))
return false
case uidstatus == 1: // 如果如为攻
ctx.SendChain(message.Text("笨蛋~你家里还有个吃白饭的w"))
return false
case uidstatus == 0: // 如果为受
ctx.SendChain(message.Text("该是0就是0当0有什么不好"))
return false
case uidstatus != 3 && uidtarget.Target == 0: // 如果是单身贵族
ctx.SendChain(message.Text("今天的你是单身贵族噢"))
return false
case fianceestatus == 1: // 如果如为攻
ctx.SendChain(message.Text("他有别的女人了,你该放下了"))
return false
case fianceestatus == 0: // 如果为受
ctx.SendChain(message.Text("这是一个纯爱的世界拒绝NTR"))
return false
case fianceestatus != 3 && fianceeinfo.Target == 0:
ctx.SendChain(message.Text("今天的ta是单身贵族噢"))
return false
}
return true
}
// 注入判断 是否满足小三要求
func checkcp(ctx *zero.Ctx) bool {
gid := ctx.Event.GroupID
updatetime, err := 民政局.checkupdate(gid)
switch {
case err != nil:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return false
case time.Now().Format("2006/01/02") != updatetime:
if err := 民政局.重置(strconv.FormatInt(gid, 10)); err != nil {
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
} else {
ctx.SendChain(message.Text("ta现在还是单身哦快向ta表白吧"))
}
return false // 重置后全是单身
}
// 检查target
fid := ctx.State["regex_matched"].([]string)
fiancee, err := strconv.ParseInt(fid[2]+fid[3], 10, 64)
if err != nil {
ctx.SendChain(message.Text("额,你的对象好像不存在?"))
return false
}
uid := ctx.Event.UserID
if fiancee == uid {
return true
}
// 检查用户是否登记过
userinfo, uidstatus, err := 民政局.查户口(gid, uid)
switch {
case uidstatus == 2:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return false
case userinfo.Target == fiancee: // 如果本就是一块
ctx.SendChain(message.Text("笨蛋~你们明明已经在一起了啊w"))
return false
case uidstatus != 3 && userinfo.Target == 0: // 如果是单身贵族
ctx.SendChain(message.Text("今天的你是单身贵族哦"))
return false
case uidstatus == 1: // 如果如为攻
ctx.SendChain(message.Text("打灭,不给纳小妾!"))
return false
case uidstatus == 0: // 如果为受
ctx.SendChain(message.Text("该是0就是0当0有什么不好"))
return false
}
fianceeinfo, fianceestatus, err := 民政局.查户口(gid, fiancee)
switch {
case fianceestatus == 2:
ctx.SendChain(message.Text("数据库发生问题力请联系bot管理员\n[error]", err))
return false
case fianceestatus == 3:
ctx.SendChain(message.Text("ta现在还是单身哦快向ta表白吧"))
return false
case fianceeinfo.Target == 0:
ctx.SendChain(message.Text("今天的ta是单身贵族哦"))
return false
}
return true
}
func iscding2(ctx *zero.Ctx) {
ctx.SendChain(message.Text("打灭,禁止离婚"))
}

View File

@@ -3,7 +3,7 @@ package reborn
import (
"encoding/json"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/floatbox/file"
)
// load 加载rate数据

View File

@@ -5,9 +5,9 @@ import (
"fmt"
"math/rand"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
wr "github.com/mroth/weightedrand"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
@@ -21,14 +21,14 @@ func init() {
PublicDataFolder: "Reborn",
})
en.OnFullMatch("reborn", ctxext.DoOnceOnSuccess(
en.OnFullMatch("reborn", fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
datapath := en.DataFolder()
jsonfile := datapath + "rate.json"
area := make(rate, 226)
err := load(&area, jsonfile)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
choices := make([]wr.Choice, len(area))
@@ -38,7 +38,7 @@ func init() {
}
areac, err = wr.NewChooser(choices...)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Printf("[Reborn]读取%d个国家/地区", len(area))

View File

@@ -131,7 +131,7 @@ func init() {
// 运行失败
ctx.SendChain(
message.Text("> ", ctx.Event.Sender.NickName, "\n"),
message.Text("ERROR:", err),
message.Text("ERROR: ", err),
)
} else {
// 运行成功

View File

@@ -16,11 +16,11 @@ import (
"github.com/FloatTech/AnimeAPI/pixiv"
"github.com/jozsefsallai/gophersauce"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/img/pool"
)
@@ -58,7 +58,7 @@ func init() { // 插件主体
// 获取P站插图信息
illust, err := pixiv.Works(id)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if illust.Pid > 0 {
@@ -143,8 +143,13 @@ func init() { // 插件主体
} else {
msg = append(msg, message.Text("也许是这个?"))
}
if err == nil && resp.StatusCode == http.StatusOK {
msg = append(msg, message.Image(result.Header.Thumbnail))
if err == nil {
_ = resp.Body.Close()
if resp.StatusCode == http.StatusOK {
msg = append(msg, message.Image(result.Header.Thumbnail))
} else {
msg = append(msg, message.Image(pic))
}
} else {
msg = append(msg, message.Image(pic))
}
@@ -160,7 +165,7 @@ func init() { // 插件主体
}
// ascii2d 搜索
if result, err := ascii2d.Ascii2d(pic); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
continue
} else {
msg := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Text("ascii2d搜图结果"))}
@@ -181,7 +186,7 @@ func init() { // 插件主体
ctx.Event.GroupID,
msg,
).Get("message_id").Int(); id == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
}
}
@@ -194,12 +199,12 @@ func init() { // 插件主体
APIKey: ctx.State["regex_matched"].([]string)[1],
})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
err = os.WriteFile(apikeyfile, binary.StringToBytes(saucenaocli.APIKey), 0644)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("成功!"))

View File

@@ -21,14 +21,14 @@ import (
"github.com/FloatTech/AnimeAPI/nsfw"
"github.com/FloatTech/AnimeAPI/scale"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/img/writer"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/img"
"github.com/FloatTech/zbputils/img/writer"
"github.com/FloatTech/zbputils/web"
)
func init() {
@@ -54,7 +54,7 @@ func init() {
p, err := nsfw.Classify(url[0])
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if p.Drawings < 0.1 || p.Neutral > 0.8 {
@@ -64,12 +64,12 @@ func init() {
data := <-datachan
if errsub != nil {
ctx.SendChain(message.Text("ERROR:", errsub))
ctx.SendChain(message.Text("ERROR: ", errsub))
return
}
im, _, err := image.Decode(bytes.NewReader(data))
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
px := im.Bounds().Size().X * im.Bounds().Size().Y
@@ -85,7 +85,7 @@ func init() {
defer binary.PutWriter(w)
_, err = writer.WriteTo(im, w)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
data, err = scale.Post(bytes.NewReader(w.Bytes()), paras[0], paras[1], 2)
@@ -93,14 +93,14 @@ func init() {
data, err = scale.Get(url[0], paras[0], paras[1], 2)
}
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
n := cachedir + strconv.Itoa(int(ctx.Event.UserID))
f, err := os.Create(n)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
_, _ = f.Write(data)

View File

@@ -7,7 +7,7 @@ import (
"strconv"
"time"
"github.com/fogleman/gg"
"github.com/Coloured-glaze/gg"
"github.com/golang/freetype"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
@@ -15,15 +15,15 @@ import (
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/floatbox/binary"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/img/writer"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/binary"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/img"
"github.com/FloatTech/zbputils/img/text"
"github.com/FloatTech/zbputils/img/writer"
"github.com/FloatTech/zbputils/web"
)
const (
@@ -72,12 +72,12 @@ func init() {
}
err := initPic(picFile)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
back, err := gg.LoadImage(picFile)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if siUpdateTimeStr != today {
@@ -98,11 +98,11 @@ func init() {
hourWord := getHourWord(now)
_, err = file.GetLazyData(text.BoldFontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if err = canvas.LoadFontFace(text.BoldFontFile, float64(back.Bounds().Size().X)*0.1); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
canvas.SetRGB(0, 0, 0)
@@ -111,11 +111,11 @@ func init() {
nickName := ctx.CardOrNickName(uid)
_, err = file.GetLazyData(text.FontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if err = canvas.LoadFontFace(text.FontFile, float64(back.Bounds().Size().X)*0.04); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
add := 1
@@ -156,7 +156,7 @@ func init() {
_, err = writer.WriteTo(canvas.Image(), f)
_ = f.Close()
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
@@ -187,31 +187,31 @@ func init() {
}
st, err := sdb.GetScoreRankByTopN(10)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if len(st) == 0 {
ctx.SendChain(message.Text("ERROR:目前还没有人签到过"))
ctx.SendChain(message.Text("ERROR: 目前还没有人签到过"))
return
}
_, err = file.GetLazyData(text.FontFile, true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
b, err := os.ReadFile(text.FontFile)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
font, err := freetype.ParseFont(b)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
f, err := os.Create(drawedFile)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
bars := make([]chart.Value, len(st))
@@ -234,7 +234,7 @@ func init() {
_ = f.Close()
if err != nil {
_ = os.Remove(drawedFile)
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))

View File

@@ -9,14 +9,15 @@ import (
"time"
"github.com/FloatTech/AnimeAPI/pixiv"
fcext "github.com/FloatTech/floatbox/ctxext"
fileutil "github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/math"
"github.com/FloatTech/floatbox/process"
sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
fileutil "github.com/FloatTech/zbputils/file"
imagepool "github.com/FloatTech/zbputils/img/pool"
"github.com/FloatTech/zbputils/math"
"github.com/FloatTech/zbputils/process"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
@@ -60,25 +61,25 @@ func init() { // 插件主体
PublicDataFolder: "SetuTime",
})
getdb := ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
// 如果数据库不存在则下载
pool.db.DBPath = engine.DataFolder() + "SetuTime.db"
_, _ = engine.GetLazyData("SetuTime.db", false)
err := pool.db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
for _, imgtype := range pool.List() {
if err := pool.db.Create(imgtype, &pixiv.Illust{}); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
}
return true
})
engine.OnRegex(`^来份(.+)$`, getdb, ctxext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, pool)).SetBlock(true).Limit(ctxext.LimitByUser).
engine.OnRegex(`^来份(.+)$`, getdb, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, pool)).SetBlock(true).Limit(ctxext.LimitByUser).
Handle(func(ctx *zero.Ctx) {
var imgtype = ctx.State["regex_matched"].([]string)[1]
// 补充池子
@@ -88,13 +89,13 @@ func init() { // 插件主体
ctx.SendChain(message.Text("INFO: 正在填充弹药......"))
time.Sleep(time.Second * 10)
if pool.size(imgtype) == 0 {
ctx.SendChain(message.Text("ERROR:等待填充,请稍后再试......"))
ctx.SendChain(message.Text("ERROR: 等待填充,请稍后再试......"))
return
}
}
// 从缓冲池里抽一张
if id := ctx.SendChain(*pool.pop(imgtype)); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
@@ -106,13 +107,13 @@ func init() { // 插件主体
)
err := pool.add(ctx, imgtype, id)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("成功向分类", imgtype, "添加图片", id))
})
engine.OnRegex(`^删除\s*([^0-9\s]+)\s*(\d+)$`, getdb, ctxext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, pool), zero.SuperUserPermission).SetBlock(true).
engine.OnRegex(`^删除\s*([^0-9\s]+)\s*(\d+)$`, getdb, fcext.ValueInList(func(ctx *zero.Ctx) string { return ctx.State["regex_matched"].([]string)[1] }, pool), zero.SuperUserPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
var (
imgtype = ctx.State["regex_matched"].([]string)[1]
@@ -120,7 +121,7 @@ func init() { // 插件主体
)
// 查询数据库
if err := pool.remove(imgtype, id); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text("删除成功"))
@@ -161,7 +162,7 @@ func (p *imgpool) push(ctx *zero.Ctx, imgtype string, illust *pixiv.Illust) {
if fileutil.IsNotExist(f) {
// 下载图片
if err := illust.DownloadToCache(0); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
}
@@ -199,7 +200,7 @@ func (p *imgpool) fill(ctx *zero.Ctx, imgtype string) {
illust := &pixiv.Illust{}
// 查询出一张图片
if err := p.db.Pick(imgtype, illust); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
continue
}
// 向缓冲池添加一张图片

View File

@@ -11,12 +11,12 @@ func init() {
engine.OnFullMatch("马丁路德骂我").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
doc, err := htmlquery.LoadURL(ergofabulousURL)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
node, err := htmlquery.Query(doc, "//main[@role=\"main\"]/p[@class=\"larger\"]/text()")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(node.Data))

View File

@@ -2,10 +2,10 @@
package shadiao
import (
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/web"
"github.com/tidwall/gjson"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
@@ -39,7 +39,7 @@ func init() {
requestURL := sdMap[ctx.State["matched"].(string)]
data, err := web.RequestDataWith(web.NewDefaultClient(), requestURL, "GET", sdReferer, ua)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(gjson.GetBytes(data, "data.text").String()))

View File

@@ -6,15 +6,15 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"github.com/FloatTech/floatbox/web"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/web"
)
func init() {
engine.OnFullMatch("来碗绿茶").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
data, err := web.RequestDataWith(web.NewDefaultClient(), chayiURL, "GET", loveliveReferer, ua)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
text := gjson.Get(helper.BytesToString(data), "returnObj.content").String()
@@ -24,7 +24,7 @@ func init() {
engine.OnFullMatch("渣我").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
data, err := web.RequestDataWith(web.NewDefaultClient(), ganhaiURL, "GET", loveliveReferer, ua)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
text := gjson.Get(helper.BytesToString(data), "returnObj.content").String()

View File

@@ -8,15 +8,15 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"github.com/FloatTech/floatbox/web"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/web"
)
func init() {
engine.OnFullMatch("讲个段子").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) {
data, err := web.RequestDataWith(web.NewDefaultClient(), yduanziURL, "POST", yduanziReferer, ua)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
text := gjson.Get(helper.BytesToString(data), "duanzi").String()

View File

@@ -35,7 +35,7 @@ func handletxt(ctx *zero.Ctx) {
// 调用接口
txt, err := shindanmaker.Shindanmaker(ctx.State["id"].(int64), name)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(txt))
@@ -47,7 +47,7 @@ func handlecq(ctx *zero.Ctx) {
// 调用接口
txt, err := shindanmaker.Shindanmaker(ctx.State["id"].(int64), name)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.Send(txt)
@@ -59,16 +59,16 @@ func handlepic(ctx *zero.Ctx) {
// 调用接口
txt, err := shindanmaker.Shindanmaker(ctx.State["id"].(int64), name)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
data, err := text.RenderToBase64(txt, text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if id := ctx.SendChain(message.Image("base64://" + helper.BytesToString(data))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
}

14
plugin/tarot/README.md Normal file
View File

@@ -0,0 +1,14 @@
# ZeroBot-Plugin-Tarot
[ZeroBot QQ机器人](https://github.com/wdvxdr1123/ZeroBot)插件,玄学占卜抽塔罗牌!
## 触发方式
- [x] 抽[塔罗牌|大阿卡纳|小阿卡纳]
- [x] 抽n张[塔罗牌|大阿卡纳|小阿卡纳]
- [x] 解塔罗牌[牌名]
- [x] [塔罗|大阿卡纳|小阿卡纳|混合]牌阵[圣三角|时间之流|四要素|五牌阵|吉普赛十字|马蹄|六芒星]
## 致谢
解牌来自[MinatoAquaCrews/nonebot_plugin_tarot](https://github.com/MinatoAquaCrews/nonebot_plugin_tarot),感谢[KafCoppelia](https://github.com/KafCoppelia)的收集与整理!

View File

@@ -3,14 +3,16 @@ package tarot
import (
"encoding/json"
"fmt"
"math/rand"
"strconv"
"strings"
"github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
@@ -27,139 +29,228 @@ type card struct {
Name string `json:"name"`
cardInfo `json:"info"`
}
type formation struct {
CardsNum int `json:"cards_num"`
IsCut bool `json:"is_cut"`
Represent [][]string `json:"represent"`
}
type cardSet = map[string]card
var cardMap = make(cardSet, 30)
var infoMap = make(map[string]cardInfo, 30)
var cardMap = make(cardSet, 80)
var infoMap = make(map[string]cardInfo, 80)
var formationMap = make(map[string]formation, 10)
// var cardName = make([]string, 22)
var majorArcanaName = make([]string, 0, 80)
var formationName = make([]string, 0, 10)
func init() {
engine := control.Register("tarot", &ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Help: "塔罗牌\n" +
"- 抽塔罗牌\n" +
"- 抽n张塔罗牌\n" +
"- 解塔罗牌[牌名]",
"- 抽[塔罗牌|大阿卡纳|小阿卡纳]\n" +
"- 抽n张[塔罗牌|大阿卡纳|小阿卡纳]\n" +
"- 解塔罗牌[牌名]\n" +
"- [塔罗|大阿卡纳|小阿卡纳|混合]牌阵[圣三角|时间之流|四要素|五牌阵|吉普赛十字|马蹄|六芒星]",
PublicDataFolder: "Tarot",
}).ApplySingle(ctxext.DefaultSingle)
engine.OnRegex(`^抽(\d{1,2}张)?塔罗牌$`, ctxext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
data, err := engine.GetLazyData("tarots.json", true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return false
}
err = json.Unmarshal(data, &cardMap)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return false
}
logrus.Infof("[tarot]读取%d张塔罗牌", len(cardMap))
return true
},
)).SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *zero.Ctx) {
getTarot := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
data, err := engine.GetLazyData("tarots.json", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = json.Unmarshal(data, &cardMap)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
for _, card := range cardMap {
infoMap[card.Name] = card.cardInfo
}
for i := 0; i < 22; i++ {
majorArcanaName = append(majorArcanaName, cardMap[strconv.Itoa(i)].Name)
}
logrus.Infof("[tarot]读取%d张塔罗牌", len(cardMap))
formation, err := engine.GetLazyData("formation.json", true)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = json.Unmarshal(formation, &formationMap)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
for k := range formationMap {
formationName = append(formationName, k)
}
logrus.Infof("[tarot]读取%d组塔罗牌阵", len(formationMap))
return true
})
engine.OnRegex(`^抽(\d{1,2}张)?((塔罗牌|大阿(尔)?卡纳)|小阿(尔)?卡纳)$`, getTarot).SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *zero.Ctx) {
match := ctx.State["regex_matched"].([]string)[1]
cardType := ctx.State["regex_matched"].([]string)[2]
n := 1
reasons := [...]string{"您抽到的是~\n", "锵锵锵,塔罗牌的预言是~\n", "诶,让我看看您抽到了~\n"}
position := [...]string{"正位", "逆位"}
reverse := [...]string{"", "Reverse"}
position := [...]string{"正位", "逆位"}
reverse := [...]string{"", "Reverse/"}
start := 0
length := 22
if match != "" {
var err error
n, err = strconv.Atoi(match[:len(match)-3])
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
if n <= 0 {
ctx.SendChain(message.Text("ERROR:张数必须为正"))
ctx.SendChain(message.Text("ERROR: 张数必须为正"))
return
}
if n > 1 && !zero.OnlyGroup(ctx) {
ctx.SendChain(message.Text("ERROR:抽取多张仅支持群聊"))
ctx.SendChain(message.Text("ERROR: 抽取多张仅支持群聊"))
return
}
if n > 20 {
ctx.SendChain(message.Text("ERROR:抽取张数过多"))
ctx.SendChain(message.Text("ERROR: 抽取张数过多"))
return
}
}
if strings.Contains(cardType, "小") {
start = 22
length = 55
}
if n == 1 {
i := rand.Intn(22)
i := rand.Intn(length) + start
p := rand.Intn(2)
card := cardMap[(strconv.Itoa(i))]
card := cardMap[strconv.Itoa(i)]
name := card.Name
description := card.Description
if p == 1 {
description = card.ReverseDescription
}
if id := ctx.SendChain(
message.Text(reasons[rand.Intn(len(reasons))], position[p], "", name, "\n"),
message.Image(fmt.Sprintf(bed+"MajorArcana%s/%d.png", reverse[p], i))); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR:可能被风控了"))
message.Text(reasons[rand.Intn(len(reasons))], position[p], "的『", name, "\n"),
message.Image(bed+reverse[p]+card.ImgURL),
message.Text("\n其释义为: ", description)); id.ID() == 0 {
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
return
}
msg := make([]message.MessageSegment, n)
randomIntMap := make(map[int]int, 30)
for i := range msg {
j := rand.Intn(22)
j := rand.Intn(length)
_, ok := randomIntMap[j]
for ok {
j = rand.Intn(22)
j = rand.Intn(length)
_, ok = randomIntMap[j]
}
randomIntMap[j] = 0
p := rand.Intn(2)
card := cardMap[(strconv.Itoa(j))]
card := cardMap[strconv.Itoa(j+start)]
name := card.Name
description := card.Description
if p == 1 {
description = card.ReverseDescription
}
tarotMsg := []message.MessageSegment{
message.Text(reasons[rand.Intn(len(reasons))], position[p], "", name, "\n"),
message.Image(fmt.Sprintf(bed+"MajorArcana%s/%d.png", reverse[p], j))}
message.Text(position[p], "的『", name, "\n"),
message.Image(bed + reverse[p] + card.ImgURL),
message.Text("\n其释义为: ", description)}
msg[i] = ctxext.FakeSenderForwardNode(ctx, tarotMsg...)
}
ctx.SendGroupForwardMessage(ctx.Event.GroupID, msg)
})
engine.OnRegex(`^解塔罗牌\s?(.*)`, ctxext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
if len(cardMap) > 0 {
for _, card := range cardMap {
infoMapKey := strings.Split(card.Name, "(")[0]
infoMap[infoMapKey] = card.cardInfo
// 可以拿来显示大阿尔卡纳列表
// cardName = append(cardName, infoMapKey)
}
return true
}
tempMap := make(cardSet, 30)
data, err := engine.GetLazyData("tarots.json", true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return false
}
err = json.Unmarshal(data, &tempMap)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
return false
}
for _, card := range tempMap {
infoMapKey := strings.Split(card.Name, "(")[0]
infoMap[infoMapKey] = card.cardInfo
// 可以拿来显示大阿尔卡纳列表
// cardName = append(cardName, infoMapKey)
}
return true
},
)).SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *zero.Ctx) {
engine.OnRegex(`^解塔罗牌\s?(.*)`, getTarot).SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *zero.Ctx) {
match := ctx.State["regex_matched"].([]string)[1]
info, ok := infoMap[match]
if ok {
ctx.SendChain(
message.Image(bed+info.ImgURL),
message.Text("\n", match, "的含义是~"),
message.Text("\n正位:", info.Description),
message.Text("\n逆位:", info.ReverseDescription))
message.Text("\n正位:", info.Description),
message.Text("\n逆位:", info.ReverseDescription))
} else {
ctx.SendChain(message.Text("没有找到", match, "噢~"))
var build strings.Builder
build.WriteString("塔罗牌列表\n大阿尔卡纳:\n")
build.WriteString(strings.Join(majorArcanaName[:7], " "))
build.WriteString("\n")
build.WriteString(strings.Join(majorArcanaName[7:14], " "))
build.WriteString("\n")
build.WriteString(strings.Join(majorArcanaName[14:22], " "))
build.WriteString("\n小阿尔卡纳:\n[圣杯|星币|宝剑|权杖] [0-10|侍从|骑士|王后|国王]")
txt := build.String()
cardList, err := text.RenderToBase64(txt, text.FontFile, 420, 20)
if err != nil {
ctx.SendChain(message.Text("没有找到", match, "噢~"))
ctx.SendChain(message.Text("ERROR: ", err))
}
ctx.SendChain(message.Text("没有找到", match, "噢~"), message.Image("base64://"+binary.BytesToString(cardList)))
}
})
engine.OnRegex(`^((塔罗|大阿(尔)?卡纳)|小阿(尔)?卡纳|混合)牌阵\s?(.*)`, getTarot).SetBlock(true).Limit(ctxext.LimitByGroup).Handle(func(ctx *zero.Ctx) {
cardType := ctx.State["regex_matched"].([]string)[1]
match := ctx.State["regex_matched"].([]string)[5]
info, ok := formationMap[match]
position := [...]string{"『正位』", "『逆位』"}
reverse := [...]string{"", "Reverse/"}
start, length := 0, 22
if strings.Contains(cardType, "小") {
start = 22
length = 55
} else if cardType == "混合" {
start = 0
length = 77
}
if ok {
ctx.SendChain(message.Text("少女祈祷中..."))
var build strings.Builder
build.WriteString(ctx.CardOrNickName(ctx.Event.UserID))
build.WriteString("---")
build.WriteString(match)
build.WriteString("\n")
msg := make([]message.MessageSegment, info.CardsNum+1)
randomIntMap := make(map[int]int, 30)
for i := 0; i < info.CardsNum; i++ {
j := rand.Intn(length)
_, ok := randomIntMap[j]
for ok {
j = rand.Intn(length)
_, ok = randomIntMap[j]
}
randomIntMap[j] = 0
p := rand.Intn(2)
card := cardMap[strconv.Itoa(j+start)]
name := card.Name
description := card.Description
if p == 1 {
description = card.ReverseDescription
}
tarotMsg := []message.MessageSegment{message.Image(bed + reverse[p] + card.ImgURL)}
build.WriteString(info.Represent[0][i])
build.WriteString(":")
build.WriteString(position[p])
build.WriteString("的『")
build.WriteString(name)
build.WriteString("』\n其释义为: \n")
build.WriteString(description)
build.WriteString("\n")
msg[i] = ctxext.FakeSenderForwardNode(ctx, tarotMsg...)
}
txt := build.String()
formation, err := text.RenderToBase64(txt, text.FontFile, 400, 20)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
msg[info.CardsNum] = ctxext.FakeSenderForwardNode(ctx, []message.MessageSegment{message.Image("base64://" + binary.BytesToString(formation))}...)
ctx.SendGroupForwardMessage(ctx.Event.GroupID, msg)
} else {
ctx.SendChain(message.Text("没有找到", match, "噢~\n现有牌阵列表: \n", strings.Join(formationName, "\n")))
}
})
}

View File

@@ -4,6 +4,7 @@ package tiangou
import (
"time"
fcext "github.com/FloatTech/floatbox/ctxext"
sql "github.com/FloatTech/sqlite"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
@@ -28,27 +29,27 @@ func init() {
PublicDataFolder: "Tiangou",
})
en.OnFullMatch("舔狗日记", ctxext.DoOnceOnSuccess(
en.OnFullMatch("舔狗日记", fcext.DoOnceOnSuccess(
func(ctx *zero.Ctx) bool {
db.DBPath = en.DataFolder() + "tiangou.db"
_, err := en.GetLazyData("tiangou.db", true)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Open(time.Hour * 24)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
err = db.Create("tiangou", &tiangou{})
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
c, err := db.Count("tiangou")
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return false
}
logrus.Infoln("[tiangou]加载", c, "条舔狗日记")
@@ -58,7 +59,7 @@ func init() {
var t tiangou
err := db.Pick("tiangou", &t)
if err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(message.Text(t.Text))

View File

@@ -25,11 +25,11 @@ func init() { // 插件主体
ctx.SendChain(message.Text("少女祈祷中......"))
for _, pic := range ctx.State["image_url"].([]string) {
if result, err := moe.Search(pic, true, true); err != nil {
ctx.SendChain(message.Text("ERROR:", err))
ctx.SendChain(message.Text("ERROR: ", err))
} else if len(result.Result) > 0 {
r := result.Result[0]
hint := "我有把握是这个!"
if r.Similarity < 0.8 {
if r.Similarity < 80 {
hint = "大概是这个?"
}
mf := int(r.From / 60)

View File

@@ -20,7 +20,7 @@ func init() {
msg := []string{ctx.State["regex_matched"].([]string)[2]}
data, err := tl.Translate(msg[0])
if err != nil {
ctx.SendChain(message.Text("Error:", data))
ctx.SendChain(message.Text("ERROR: ", data))
return
}
ctx.SendChain(message.Text(data))

View File

@@ -9,7 +9,7 @@ import (
"strconv"
"strings"
"github.com/FloatTech/zbputils/web"
"github.com/FloatTech/floatbox/web"
_ "github.com/fumiama/sqlite3" // import sql
"github.com/jinzhu/gorm"
log "github.com/sirupsen/logrus"

Some files were not shown because too many files have changed in this diff Show More