mirror of
https://github.com/ChenXu233/nonebot_plugin_dialectlist.git
synced 2026-01-25 21:22:16 +00:00
Compare commits
33 Commits
49ce2ffb7c
...
V3.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b36e32fe19 | ||
|
|
bd195ac87a | ||
|
|
152128391c | ||
|
|
eee9ac2ab3 | ||
|
|
4970a47b83 | ||
|
|
383a3f602a | ||
|
|
b0e84c778b | ||
|
|
1cb2be770d | ||
|
|
d19d65f75e | ||
|
|
a7bdd6033e | ||
|
|
0d4f69356a | ||
|
|
0348a5d66c | ||
|
|
aff02f2666 | ||
|
|
2f725e0808 | ||
|
|
3434a5352f | ||
|
|
0954eb4f3e | ||
|
|
5b94bc0fff | ||
|
|
e1a20922c4 | ||
|
|
5e00605dfb | ||
|
|
833cdb4a75 | ||
|
|
29b4598e5f | ||
|
|
10cc5cf278 | ||
|
|
ed167a56b5 | ||
|
|
b40c7be1f9 | ||
|
|
07f85be527 | ||
|
|
f2cdfe7f8a | ||
|
|
e0ca7907c0 | ||
|
|
53b2a33923 | ||
|
|
4e339bcdb1 | ||
|
|
0b85f6fb34 | ||
|
|
83d9cda2eb | ||
|
|
dfde832dba | ||
|
|
987b34e282 |
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -19,6 +19,7 @@
|
|||||||
"pygal",
|
"pygal",
|
||||||
"sqlalchemy",
|
"sqlalchemy",
|
||||||
"timecost",
|
"timecost",
|
||||||
|
"uninfo",
|
||||||
"userinfo",
|
"userinfo",
|
||||||
"whereclause",
|
"whereclause",
|
||||||
"xaxis",
|
"xaxis",
|
||||||
|
|||||||
213
README.md
213
README.md
@@ -6,14 +6,15 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
# 📃话痨排行榜
|
# 📃 话痨排行榜
|
||||||
|
|
||||||
nonebot-plugin-dialectlist
|
nonebot-plugin-dialectlist
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://pypi.python.org/pypi/nonebot-plugin-dialectlist">
|
<a href="https://pypi.python.org/pypi/nonebot-plugin-dialectlist">
|
||||||
<img src="https://img.shields.io/pypi/v/nonebot-plugin-dialectlist.svg" alt="pypi">
|
<img src="https://img.shields.io/pypi/v/nonebot-plugin-dialectlist.svg" alt="pypi">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
|
||||||
|
|
||||||
<a href="https://qm.qq.com/q/Yty2yc9Bee">
|
<a href="https://qm.qq.com/q/Yty2yc9Bee">
|
||||||
@@ -21,20 +22,27 @@ nonebot-plugin-dialectlist
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
[](https://wakatime.com/badge/user/114aee90-a5f9-49aa-b441-9eca7eebfda3/project/68aaa2a1-fb8a-4b00-bb66-ee67e8a3f3b0)
|
||||||
|
|
||||||
\>💬**看看群友们这些天在群里水了多少话**💬<
|
\>💬**看看群友们这些天在群里水了多少话**💬<
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 💿 安装
|
## 💿 安装
|
||||||
|
|
||||||
通过`pip`或`nb`安装;
|
通过`pip`或`nb`安装:
|
||||||
|
|
||||||
>**通过 pip **安装
|
- 通过 pip 安装
|
||||||
|
|
||||||
`pip install nonebot-plugin-dialectlist`
|
```bash
|
||||||
|
pip install nonebot-plugin-dialectlist
|
||||||
|
```
|
||||||
|
|
||||||
>**通过 nb **安装
|
- 通过 nb-cli 安装
|
||||||
|
|
||||||
`nb plugin install nonebot-plugin-dialectlist`
|
```bash
|
||||||
|
nb plugin install nonebot-plugin-dialectlist
|
||||||
|
```
|
||||||
|
|
||||||
### ✅ 插件依赖于
|
### ✅ 插件依赖于
|
||||||
|
|
||||||
@@ -45,93 +53,122 @@ nonebot-plugin-dialectlist
|
|||||||
5. [nonebot-plugin-chatrecorder](https://github.com/noneplugin/nonebot-plugin-chatrecorder) ————实现消息储存
|
5. [nonebot-plugin-chatrecorder](https://github.com/noneplugin/nonebot-plugin-chatrecorder) ————实现消息储存
|
||||||
6. [nonebot-plugin-cesaa](https://github.com/MountainDash/nonebot-plugin-send-anything-anywhere) ————实现多平台
|
6. [nonebot-plugin-cesaa](https://github.com/MountainDash/nonebot-plugin-send-anything-anywhere) ————实现多平台
|
||||||
|
|
||||||
**⚠注意** 若先前没有安装过```nonebot-plugin-chatrecorder```或者```nonebot-plugin-orm```,则会在启动时报错,请按报错的提示安装数据库!
|
**⚠ 注意** 若先前没有安装过`nonebot-plugin-chatrecorder`或者`nonebot-plugin-orm`,则会在启动时报错,请按报错的提示安装数据库!
|
||||||
|
|
||||||
## ⚙ 配置
|
## ⚙ 配置
|
||||||
|
|
||||||
需要**提前配置**本插件所**依赖的插件**!
|
需要**提前配置**本插件所**依赖的插件**!
|
||||||
|
|
||||||
在 .env 中,可以添加以下配置项
|
在 .env 中,可以添加以下配置项
|
||||||
```python
|
|
||||||
dialectlist__string_format = "第{index}名:\n{nickname},{chatdatanum}条消息、\n" #消息格式
|
|
||||||
dialectlist__get_num = 10 #获取人数数量
|
|
||||||
dialectlist__visualization = True #是否可视化
|
|
||||||
# dialectlist__visualization_type = "圆环图" #可视化方案 (不再支持)
|
|
||||||
dialectlist__font = "SimHei" #字体格式
|
|
||||||
dialectlist__excluded_people = [] #排除的人的 QQ 号(或频道号?(未经测试))
|
|
||||||
dialectlist__excluded_self = True #是否排除机器人自己 QQ
|
|
||||||
dialectlist__suffix: bool = False # 是否显示后缀
|
|
||||||
dialectlist__string_suffix: str = "统计花费时间{timecost}" # 消息格式后缀
|
|
||||||
```
|
|
||||||
💭也可以不进行配置,这将会使插件按照默认配置运行
|
|
||||||
|
|
||||||
### ⚠ 注意!!
|
```python
|
||||||
|
dialectlist__get_num: int = 5 # 获取人数数量
|
||||||
|
dialectlist__font: str = "SimHei" # 字体格式
|
||||||
|
dialectlist__suffix: bool = True # 是否显示后缀
|
||||||
|
dialectlist__excluded_self: bool = True # 是否排除自己
|
||||||
|
dialectlist__visualization: bool = True # 是否可视化
|
||||||
|
dialectlist__show_text_rank: bool = True # 是否显示文本排名
|
||||||
|
dialectlist__excluded_people: List[str] = [] # 排除的人的 QQ 号
|
||||||
|
dialectlist__use_user_info_cache: bool = False # 是否使用用户信息缓存
|
||||||
|
dialectlist__aggregate_transmission: bool = False # 是否聚合转发消息
|
||||||
|
dialectlist__timezone: Optional[str] = "Asia/Shanghai" # 时区,影响统计时间
|
||||||
|
dialectlist__string_suffix: str = "统计花费时间:{timecost}秒" # 消息格式后缀
|
||||||
|
dialectlist__template_path: str = "./template/rank_template.j2" # 模板路径
|
||||||
|
dialectlist__string_format: str = "第{index}名:\n{nickname},{chatdatanum}条消息、n" # 消息格式
|
||||||
|
```
|
||||||
|
|
||||||
|
💭 也可以不进行配置,这将会使插件按照默认配置运行
|
||||||
|
|
||||||
|
### ⚠ 注意!!
|
||||||
|
|
||||||
> 在旧版插件(2.0.0 以下)中,dialectlist 与后面的配置项只隔了一个下划线,若更新到新版本以后需要俩个下划线。
|
> 在旧版插件(2.0.0 以下)中,dialectlist 与后面的配置项只隔了一个下划线,若更新到新版本以后需要俩个下划线。
|
||||||
|
|
||||||
## 🗨命令
|
> 由于插件依赖 chatrecord 多次引入爆炸性修改,建议在遇到问题后优先尝试降级 chatrecord 插件
|
||||||
__!!注意!!__
|
|
||||||
|
## 🗨 命令
|
||||||
|
|
||||||
|
**!!注意!!**
|
||||||
新版本指令调用方式改变,改为更易理解也更好打的 B 话榜。
|
新版本指令调用方式改变,改为更易理解也更好打的 B 话榜。
|
||||||
同时也可以用类似 `/今日废话榜` 的方式(只要改前面的就好了)(算是给 [盘古之白](https://github.com/vinta/pangu.js) 风格爱好者的福利吧?)
|
同时也可以用类似 `/今日废话榜` 的方式(只要改前面的就好了)(算是给 [盘古之白](https://github.com/vinta/pangu.js) 风格爱好者的福利吧?)
|
||||||
|
|
||||||
### 🎨一般用法
|
### 🎨 一般用法
|
||||||
|
|
||||||
-`/B话榜` ————看看有史以来(机器人存在以来)群友们发了多少消息! (好像没写)
|
#### B 话榜
|
||||||
|
|
||||||
-`/今日B话榜` ————看看今天的群友发了多少消息!
|
-`/B 话榜` ————看看有史以来(机器人存在以来)群友们发了多少消息! (好像没写)
|
||||||
|
|
||||||
-`/昨日B话榜` ————看看昨天的群友发了多少消息!
|
-`/今日 B 话榜` ————看看今天的群友发了多少消息!
|
||||||
|
|
||||||
-`/前日B话榜` ————看看前天的群友发了多少消息!
|
-`/昨日 B 话榜` ————看看昨天的群友发了多少消息!
|
||||||
|
|
||||||
-`/本周B话榜` ————看看本周的群友发了多少消息!
|
-`/前日 B 话榜` ————看看前天的群友发了多少消息!
|
||||||
|
|
||||||
-`/上周B话榜` ————看看上周的群友发了多少消息!
|
|
||||||
|
|
||||||
-`/本月B话榜` ————看看这个月的群友发了多少消息!
|
-`/本周 B 话榜` ————看看本周的群友发了多少消息!
|
||||||
|
|
||||||
-`/年度B话榜` ————看看今年的群友发了多少消息!
|
-`/上周 B 话榜` ————看看上周的群友发了多少消息!
|
||||||
|
|
||||||
-`/历史B话榜` ————看看历史上(机器人存在以来)的群友发了多少消息!
|
-`/本月 B 话榜` ————看看这个月的群友发了多少消息!
|
||||||
|
|
||||||
### 🚀进阶用法
|
-`/年度 B 话榜` ————看看今年的群友发了多少消息!
|
||||||
|
|
||||||
`/{时间类型(今日|年度)?}{B话榜|废话榜} {时间类型?} {ISO8601 格式时间 ?} {群号} {关键词}`
|
-`/历史 B 话榜` ————看看历史上(机器人存在以来)的群友发了多少消息!
|
||||||
|
|
||||||
如:`/B话榜 历史 2024-01-01~2024-01-02 12345678 女装`
|
#### 看看 B 话(kkb)
|
||||||
|
|
||||||
也可以 `/{时间类型(今日|年度)?}{B话榜|废话榜} {时间类型?} {ISO8601 格式时间 ?} -g {群号} -k {关键词}`
|
-`/看看 B 话 [@某人|QQ 号]` ————看看这个 b 人在这个 b 群发了多少 b 话!
|
||||||
|
|
||||||
|
### 🚀 进阶用法
|
||||||
|
|
||||||
|
#### B 话榜
|
||||||
|
|
||||||
|
`/{时间类型(今日|年度)?}{B 话榜|废话榜} {时间类型?} {ISO8601 格式时间 ?} {群号} {关键词}`
|
||||||
|
|
||||||
|
如:`/B 话榜 历史 2024-01-01~2024-01-02 12345678 女装`
|
||||||
|
|
||||||
|
也可以 `/{时间类型(今日|年度)?}{B 话榜|废话榜} {时间类型?} {ISO8601 格式时间 ?} -g {群号} -k {关键词}`
|
||||||
|
|
||||||
以下调用方法均合法:
|
以下调用方法均合法:
|
||||||
|
|
||||||
`/今日B话榜 -g 12345678 -k 女装`
|
`/今日 B 话榜 -g 12345678 -k 女装`
|
||||||
`/昨日B话榜 -k 女装`
|
`/昨日 B 话榜 -k 女装`
|
||||||
`/本周B话榜 -g 12345678`
|
`/本周 B 话榜 -g 12345678`
|
||||||
|
|
||||||
|
#### 看看 B 话
|
||||||
|
|
||||||
|
`/看看 B 话 {@|QQ 号} {群号?} {关键词?}`
|
||||||
|
|
||||||
|
以下调用方法均合法:
|
||||||
|
|
||||||
|
`/kkb 114514 1919810 ♂`
|
||||||
|
`/kkb @man -k ♂`
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> 关键词支持正则表达式!
|
||||||
|
|
||||||
## 💪 目前支持的平台
|
## 💪 目前支持的平台
|
||||||
|
|
||||||
| 平台 | 是否经过测试 | 是否能够正常工作 | 测试环境 |
|
| 平台 | 是否经过测试 | 是否能够正常工作 | 测试环境 |
|
||||||
|:-----:|:----:|:----:| :----: |
|
| :---------: | :----------: | :--------------: | :---------------: |
|
||||||
| Onebot | ✅ | ✅ | NapCat + Window11|
|
| Onebot | ✅ | ✅ | NapCat + Window11 |
|
||||||
| 飞书 | ❌ | ❓ | 🤔 |
|
| 飞书 | ❌ | ❓ | 🤔 |
|
||||||
| Red | ❌ | ❓ | 🤔 |
|
| Red | ❌ | ❓ | 🤔 |
|
||||||
| DoDo | ❌ | ❓ | 🤔 |
|
| DoDo | ❌ | ❓ | 🤔 |
|
||||||
| Mirai | ❌ | ❓ | 🤔 |
|
| Mirai | ❌ | ❓ | 🤔 |
|
||||||
| 开黑啦 | ❌ | ❓ | 🤔 |
|
| 开黑啦 | ❌ | ❓ | 🤔 |
|
||||||
| Kritor | ❌ | ❓ | 🤔 |
|
| Kritor | ❌ | ❓ | 🤔 |
|
||||||
| Ntchat | ❌ | ❓ | 🤔 |
|
| Ntchat | ❌ | ❓ | 🤔 |
|
||||||
| Satori | ❌ | ❓ | 🤔 |
|
| Satori | ❌ | ❓ | 🤔 |
|
||||||
| Telegram | ❌ | ❓ | 🤔 |
|
| Telegram | ❌ | ❓ | 🤔 |
|
||||||
| Discord | ❌ | ❓ | 🤔 |
|
| Discord | ❌ | ❓ | 🤔 |
|
||||||
| Tailchat | ❌ | ❓ | 🤔 |
|
| Tailchat | ❌ | ❓ | 🤔 |
|
||||||
| QQ 官方接口 | ❌ | ❓ | 🤔 |
|
| QQ 官方接口 | ❌ | ❓ | 🤔 |
|
||||||
| Rocket.Chat | ❌ | ❓ | 🤔 |
|
| Rocket.Chat | ❌ | ❓ | 🤔 |
|
||||||
|
|
||||||
- 如果你测试过能够使用,请在 Issue 中指出
|
- 如果你测试过能够使用,请在 Issue 中指出
|
||||||
|
|
||||||
## 📦另外
|
## 📦 另外
|
||||||
|
|
||||||
### 😳加入作者的 BUG 反馈群 ~~(🥵女装粉丝群)~~
|
### 😳 加入作者的 BUG 反馈群 ~~(🥵 女装粉丝群)~~
|
||||||
|
|
||||||
[群连接](https://qm.qq.com/q/Yty2yc9Bee)
|
[群连接](https://qm.qq.com/q/Yty2yc9Bee)
|
||||||
|
|
||||||
@@ -142,9 +179,11 @@ __!!注意!!__
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 💕感谢
|

|
||||||
|
|
||||||
本插件的__init__.py 中的处理函数参考了词云中的方法 ~~(其实大部分都是 Ctrl+C Ctr+V)~~
|
### 💕 感谢
|
||||||
|
|
||||||
|
本插件的**init**.py 中的处理函数参考了词云中的方法 ~~(其实大部分都是 Ctrl+C Ctr+V)~~
|
||||||
|
|
||||||
[nonebot-plugin-wordcloud](https://github.com/he0119/nonebot-plugin-wordcloud)
|
[nonebot-plugin-wordcloud](https://github.com/he0119/nonebot-plugin-wordcloud)
|
||||||
|
|
||||||
@@ -161,7 +200,6 @@ __!!注意!!__
|
|||||||
- [x] 更好看的图片渲染
|
- [x] 更好看的图片渲染
|
||||||
|
|
||||||
- [x] 添加一些全新的可配置项
|
- [x] 添加一些全新的可配置项
|
||||||
|
|
||||||
- [x] 尝试利用 jinja2 模板引擎制作可视化图片
|
- [x] 尝试利用 jinja2 模板引擎制作可视化图片
|
||||||
|
|
||||||
- [x] 私聊的查询(超级用户可以任意查询群聊的信息)一半完成
|
- [x] 私聊的查询(超级用户可以任意查询群聊的信息)一半完成
|
||||||
@@ -170,53 +208,10 @@ __!!注意!!__
|
|||||||
|
|
||||||
- [x] 查询带某关键词的消息量
|
- [x] 查询带某关键词的消息量
|
||||||
|
|
||||||
- [ ] 合并转发
|
- [x] 合并转发
|
||||||
|
|
||||||
待补充。.....
|
|
||||||
|
|
||||||
## 📖版本日志
|
待补充。.....
|
||||||
|
|
||||||
<details>
|
### 👾 题外话
|
||||||
<summary>点我展开</summary>
|
|
||||||
|
|
||||||
### V1.0
|
|
||||||
|
|
||||||
- 看看群里群友能有多话痨
|
|
||||||
|
|
||||||
### V1.1
|
|
||||||
|
|
||||||
- 支持频道咯!(*^_^*)
|
|
||||||
|
|
||||||
### V1.2
|
|
||||||
|
|
||||||
- 排行榜可视化
|
|
||||||
|
|
||||||
### V1.3
|
|
||||||
|
|
||||||
- 添加了一些可配置项
|
|
||||||
|
|
||||||
### V1.4
|
|
||||||
|
|
||||||
- 适配新版本的 chatrecorder, 暂时停止频道支持
|
|
||||||
|
|
||||||
### V2.0
|
|
||||||
|
|
||||||
- 理论支持全平台!暂停图片支持。
|
|
||||||
|
|
||||||
|
|
||||||
### V2.1
|
|
||||||
|
|
||||||
- 恢复图片支持。
|
|
||||||
|
|
||||||
### V2.2
|
|
||||||
|
|
||||||
- 优化代码,添加一些新的可配置项。
|
|
||||||
|
|
||||||
### V2.4
|
|
||||||
|
|
||||||
- 添加一些新的可配置项。
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### 👾题外话
|
|
||||||
~~整个项目快被我写成屎山了~~
|
~~整个项目快被我写成屎山了~~
|
||||||
|
|||||||
@@ -1,304 +1,352 @@
|
|||||||
from nonebot import require
|
from nonebot import require
|
||||||
|
|
||||||
require("nonebot_plugin_chatrecorder")
|
require('nonebot_plugin_chatrecorder')
|
||||||
require("nonebot_plugin_apscheduler")
|
require('nonebot_plugin_apscheduler')
|
||||||
require("nonebot_plugin_htmlrender")
|
require('nonebot_plugin_htmlrender')
|
||||||
require("nonebot_plugin_userinfo")
|
require('nonebot_plugin_userinfo')
|
||||||
require("nonebot_plugin_alconna")
|
require('nonebot_plugin_alconna')
|
||||||
require("nonebot_plugin_cesaa")
|
require('nonebot_plugin_uninfo')
|
||||||
|
require('nonebot_plugin_cesaa')
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time as t
|
import time as t
|
||||||
import nonebot_plugin_saa as saa
|
|
||||||
|
|
||||||
from typing import Union, Optional
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
import nonebot_plugin_saa as saa
|
||||||
from arclet.alconna import ArparmaBehavior
|
from arclet.alconna import ArparmaBehavior
|
||||||
from arclet.alconna.arparma import Arparma
|
from arclet.alconna.arparma import Arparma
|
||||||
|
|
||||||
from nonebot import on_command
|
|
||||||
from nonebot.log import logger
|
|
||||||
from nonebot.typing import T_State
|
|
||||||
from nonebot.params import Arg, Depends
|
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot, Event
|
||||||
|
from nonebot.log import logger
|
||||||
|
from nonebot.params import Arg, Depends
|
||||||
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
||||||
|
from nonebot.typing import T_State
|
||||||
from nonebot_plugin_alconna import (
|
from nonebot_plugin_alconna import (
|
||||||
Args,
|
Alconna,
|
||||||
Option,
|
Args,
|
||||||
Alconna,
|
At,
|
||||||
on_alconna,
|
Field,
|
||||||
|
Match,
|
||||||
|
Option,
|
||||||
|
on_alconna,
|
||||||
)
|
)
|
||||||
from nonebot_plugin_chatrecorder import get_message_records
|
from nonebot_plugin_uninfo import Session, Uninfo, get_session
|
||||||
from nonebot_plugin_session import Session, SessionIdType, extract_session
|
|
||||||
|
|
||||||
from .storage import get_cache, build_cache
|
|
||||||
from .config import Config, plugin_config
|
from .config import Config, plugin_config
|
||||||
from .usage import __usage__
|
|
||||||
|
# from .storage import build_cache, get_cache
|
||||||
from .time import (
|
from .time import (
|
||||||
get_datetime_fromisoformat_with_timezone,
|
get_datetime_fromisoformat_with_timezone,
|
||||||
get_datetime_now_with_timezone,
|
get_datetime_now_with_timezone,
|
||||||
parse_datetime,
|
parse_datetime,
|
||||||
)
|
)
|
||||||
|
from .usage import __usage__
|
||||||
from .utils import (
|
from .utils import (
|
||||||
got_rank,
|
get_rank_image,
|
||||||
msg_counter,
|
get_user_infos,
|
||||||
get_rank_image,
|
got_rank,
|
||||||
persist_id2user_id,
|
persist_id2user_id,
|
||||||
get_user_infos,
|
get_user_message_counts,
|
||||||
)
|
)
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="B话排行榜",
|
name='B话排行榜',
|
||||||
description="调查群U的B话数量,以一定的顺序排序后排序出来。",
|
description='调查群U的B话数量,以一定的顺序排序后排序出来。',
|
||||||
usage=__usage__,
|
usage=__usage__,
|
||||||
homepage="https://github.com/ChenXu233/nonebot_plugin_dialectlist",
|
homepage='https://github.com/ChenXu233/nonebot_plugin_dialectlist',
|
||||||
type="application",
|
type='application',
|
||||||
supported_adapters=inherit_supported_adapters(
|
supported_adapters=inherit_supported_adapters(
|
||||||
"nonebot_plugin_chatrecorder", "nonebot_plugin_saa", "nonebot_plugin_alconna"
|
'nonebot_plugin_chatrecorder',
|
||||||
),
|
'nonebot_plugin_saa',
|
||||||
config=Config,
|
'nonebot_plugin_alconna',
|
||||||
|
),
|
||||||
|
config=Config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 抄的词云,不过真的很适合B话榜。
|
# 抄的词云,不过真的很适合B话榜。
|
||||||
class SameTime(ArparmaBehavior):
|
class SameTime(ArparmaBehavior):
|
||||||
def operate(self, interface: Arparma):
|
def operate(self, interface: Arparma):
|
||||||
type = interface.query("type")
|
type = interface.query('type')
|
||||||
time = interface.query("time")
|
time = interface.query('time')
|
||||||
if type is None and time:
|
if type is None and time:
|
||||||
interface.behave_fail()
|
interface.behave_fail()
|
||||||
|
|
||||||
|
|
||||||
def wrapper(slot: Union[int, str], content: Optional[str], context) -> str:
|
def wrapper(slot: Union[int, str], content: Optional[str], context) -> str:
|
||||||
if slot == "type" and content:
|
if slot == 'type' and content:
|
||||||
return content
|
return content
|
||||||
return "" # pragma: no cover
|
return '' # pragma: no cover
|
||||||
|
|
||||||
|
|
||||||
build_cache_cmd = on_command("build_cache", aliases={"重建缓存"}, block=True)
|
b_cmd = on_alconna(
|
||||||
|
Alconna(
|
||||||
|
'看看B话',
|
||||||
|
Args['at', [str, At], Field(completion=lambda: '请想要查询的人的QQ号')],
|
||||||
|
Option('-g|--group_id', Args['group_id?', str]),
|
||||||
|
Option('-k|--keyword', Args['keyword?', str]),
|
||||||
|
),
|
||||||
|
aliases={'kkb'},
|
||||||
|
use_cmd_start=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@build_cache_cmd.handle()
|
@b_cmd.handle()
|
||||||
async def _build_cache(bot: Bot, event: Event):
|
async def handle_b_cmd(
|
||||||
await saa.Text("正在重建缓存,请稍等。").send(reply=True)
|
at: Match[str | At],
|
||||||
await build_cache()
|
group_id: Match[str],
|
||||||
await saa.Text("重建缓存完成。").send(reply=True)
|
keyword: Match[str],
|
||||||
|
uninfo: Uninfo,
|
||||||
|
session: Session = Depends(get_session),
|
||||||
|
):
|
||||||
|
id = at.result
|
||||||
|
if isinstance(id, At):
|
||||||
|
id = id.target
|
||||||
|
if group_id.available:
|
||||||
|
gid = group_id.result
|
||||||
|
else:
|
||||||
|
gid = session.scene.id
|
||||||
|
|
||||||
|
if not gid:
|
||||||
|
await b_cmd.finish('请指定群号。')
|
||||||
|
|
||||||
|
_keyword = keyword.result
|
||||||
|
|
||||||
|
d = await get_user_message_counts(
|
||||||
|
keyword=_keyword,
|
||||||
|
scene_ids=[gid],
|
||||||
|
user_ids=[id],
|
||||||
|
types=['message'], # 排除机器人自己发的消息
|
||||||
|
exclude_user_ids=plugin_config.excluded_people,
|
||||||
|
)
|
||||||
|
|
||||||
|
rank = got_rank(d)
|
||||||
|
if not rank:
|
||||||
|
await b_cmd.finish(
|
||||||
|
f'该用户在群“{uninfo.scene.name}”关于“{keyword.result}”的B话数量为0。'
|
||||||
|
)
|
||||||
|
|
||||||
|
await saa.Text(
|
||||||
|
f'该用户在群“{uninfo.scene.name}”关于“{keyword.result}”的B话数量为{rank[0][1]}。'
|
||||||
|
).send(reply=True)
|
||||||
|
|
||||||
|
|
||||||
rank_cmd = on_alconna(
|
rank_cmd = on_alconna(
|
||||||
Alconna(
|
Alconna(
|
||||||
"B话榜",
|
'B话榜',
|
||||||
Args["type?", ["今日", "昨日", "本周", "上周", "本月", "上月", "年度", "历史"]][
|
Args[
|
||||||
"time?",
|
'type?',
|
||||||
str,
|
['今日', '昨日', '本周', '上周', '本月', '上月', '年度', '历史'],
|
||||||
],
|
][
|
||||||
Option("-g|--group_id", Args["group_id?", str]),
|
'time?',
|
||||||
Option("-k|--keyword", Args["keyword?", str]),
|
str,
|
||||||
behaviors=[SameTime()],
|
],
|
||||||
),
|
Option('-g|--group_id', Args['group_id?', str]),
|
||||||
aliases={"废话榜"},
|
Option('-k|--keyword', Args['keyword?', str]),
|
||||||
use_cmd_start=True,
|
behaviors=[SameTime()],
|
||||||
block=True,
|
),
|
||||||
|
aliases={'废话榜'},
|
||||||
|
use_cmd_start=True,
|
||||||
|
block=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
rank_cmd.shortcut(
|
rank_cmd.shortcut(
|
||||||
r"(?P<type>今日|昨日|本周|上周|本月|上月|年度|历史)B话榜",
|
r'(?P<type>今日|昨日|本周|上周|本月|上月|年度|历史)B话榜',
|
||||||
{
|
{
|
||||||
"prefix": True,
|
'prefix': True,
|
||||||
"command": "B话榜",
|
'command': 'B话榜',
|
||||||
"wrapper": wrapper,
|
'wrapper': wrapper,
|
||||||
"args": ["{type}"],
|
'args': ['{type}'],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
rank_cmd.shortcut(
|
rank_cmd.shortcut(
|
||||||
r"(?P<type>今日|昨日|本周|上周|本月|上月|年度|历史)废话榜",
|
r'(?P<type>今日|昨日|本周|上周|本月|上月|年度|历史)废话榜',
|
||||||
{
|
{
|
||||||
"prefix": True,
|
'prefix': True,
|
||||||
"command": "废话榜",
|
'command': '废话榜',
|
||||||
"wrapper": wrapper,
|
'wrapper': wrapper,
|
||||||
"args": ["{type}"],
|
'args': ['{type}'],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 这段函数完全抄的词云
|
# 这段函数完全抄的词云
|
||||||
@rank_cmd.handle()
|
@rank_cmd.handle()
|
||||||
async def _group_message(
|
async def _group_message(
|
||||||
state: T_State,
|
state: T_State,
|
||||||
session: Session = Depends(extract_session),
|
session: Session = Depends(get_session),
|
||||||
type: Optional[str] = None,
|
type: Optional[str] = None,
|
||||||
time: Optional[str] = None,
|
time: Optional[str] = None,
|
||||||
group_id: Optional[str] = None,
|
group_id: Optional[str] = None,
|
||||||
keyword: Optional[str] = None,
|
keyword: Optional[str] = None,
|
||||||
):
|
):
|
||||||
t1 = t.time()
|
t1 = t.time()
|
||||||
state["t1"] = t1
|
state['t1'] = t1
|
||||||
dt = get_datetime_now_with_timezone()
|
dt = get_datetime_now_with_timezone()
|
||||||
|
|
||||||
if not group_id:
|
if not group_id:
|
||||||
group_id = session.id2
|
group_id = session.scene.id
|
||||||
logger.debug(f"session id2: {group_id}")
|
logger.debug(f'session id2: {group_id}')
|
||||||
if group_id:
|
if group_id:
|
||||||
state["group_id"] = group_id
|
state['group_id'] = group_id
|
||||||
|
|
||||||
state["keyword"] = keyword
|
state['keyword'] = keyword
|
||||||
|
|
||||||
if not type:
|
if not type:
|
||||||
await rank_cmd.finish(__plugin_meta__.usage)
|
await rank_cmd.finish(__plugin_meta__.usage)
|
||||||
|
|
||||||
dt = get_datetime_now_with_timezone()
|
dt = get_datetime_now_with_timezone()
|
||||||
|
|
||||||
if type == "今日":
|
if type == '今日':
|
||||||
state["start"] = dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
state['start'] = dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
state["stop"] = dt
|
state['stop'] = dt
|
||||||
elif type == "昨日":
|
elif type == '昨日':
|
||||||
state["stop"] = dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
state['stop'] = dt.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
state["start"] = state["stop"] - timedelta(days=1)
|
state['start'] = state['stop'] - timedelta(days=1)
|
||||||
elif type == "本周":
|
elif type == '本周':
|
||||||
state["start"] = dt.replace(
|
state['start'] = dt.replace(
|
||||||
hour=0, minute=0, second=0, microsecond=0
|
hour=0, minute=0, second=0, microsecond=0
|
||||||
) - timedelta(days=dt.weekday())
|
) - timedelta(days=dt.weekday())
|
||||||
state["stop"] = dt
|
state['stop'] = dt
|
||||||
elif type == "上周":
|
elif type == '上周':
|
||||||
state["stop"] = dt.replace(
|
state['stop'] = dt.replace(
|
||||||
hour=0, minute=0, second=0, microsecond=0
|
hour=0, minute=0, second=0, microsecond=0
|
||||||
) - timedelta(days=dt.weekday())
|
) - timedelta(days=dt.weekday())
|
||||||
state["start"] = state["stop"] - timedelta(days=7)
|
state['start'] = state['stop'] - timedelta(days=7)
|
||||||
elif type == "本月":
|
elif type == '本月':
|
||||||
state["start"] = dt.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
state['start'] = dt.replace(
|
||||||
state["stop"] = dt
|
day=1, hour=0, minute=0, second=0, microsecond=0
|
||||||
elif type == "上月":
|
)
|
||||||
state["stop"] = dt.replace(
|
state['stop'] = dt
|
||||||
day=1, hour=0, minute=0, second=0, microsecond=0
|
elif type == '上月':
|
||||||
) - timedelta(microseconds=1)
|
state['stop'] = dt.replace(
|
||||||
state["start"] = state["stop"].replace(
|
day=1, hour=0, minute=0, second=0, microsecond=0
|
||||||
day=1, hour=0, minute=0, second=0, microsecond=0
|
) - timedelta(microseconds=1)
|
||||||
)
|
state['start'] = state['stop'].replace(
|
||||||
elif type == "年度":
|
day=1, hour=0, minute=0, second=0, microsecond=0
|
||||||
state["start"] = dt.replace(
|
)
|
||||||
month=1, day=1, hour=0, minute=0, second=0, microsecond=0
|
elif type == '年度':
|
||||||
)
|
state['start'] = dt.replace(
|
||||||
state["stop"] = dt
|
month=1, day=1, hour=0, minute=0, second=0, microsecond=0
|
||||||
elif type == "历史":
|
)
|
||||||
if time:
|
state['stop'] = dt
|
||||||
plaintext = time
|
elif type == '历史':
|
||||||
if match := re.match(r"^(.+?)(?:~(.+))?$", plaintext):
|
if time:
|
||||||
start = match[1]
|
plaintext = time
|
||||||
stop = match[2]
|
if match := re.match(r'^(.+?)(?:~(.+))?$', plaintext):
|
||||||
try:
|
start = match[1]
|
||||||
state["start"] = get_datetime_fromisoformat_with_timezone(start)
|
stop = match[2]
|
||||||
if stop:
|
try:
|
||||||
state["stop"] = get_datetime_fromisoformat_with_timezone(stop)
|
state['start'] = get_datetime_fromisoformat_with_timezone(
|
||||||
else:
|
start
|
||||||
# 如果没有指定结束日期,则认为是所给日期的当天的词云
|
)
|
||||||
state["start"] = state["start"].replace(
|
if stop:
|
||||||
hour=0, minute=0, second=0, microsecond=0
|
state['stop'] = (
|
||||||
)
|
get_datetime_fromisoformat_with_timezone(stop)
|
||||||
state["stop"] = state["start"] + timedelta(days=1)
|
)
|
||||||
except ValueError:
|
else:
|
||||||
await rank_cmd.finish("请输入正确的日期,不然我没法理解呢!")
|
# 如果没有指定结束日期,则认为是所给日期的当天的词云
|
||||||
|
state['start'] = state['start'].replace(
|
||||||
|
hour=0, minute=0, second=0, microsecond=0
|
||||||
|
)
|
||||||
|
state['stop'] = state['start'] + timedelta(days=1)
|
||||||
|
except ValueError:
|
||||||
|
await rank_cmd.finish(
|
||||||
|
'请输入正确的日期,不然我没法理解呢!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@rank_cmd.got(
|
@rank_cmd.got(
|
||||||
"start",
|
'start',
|
||||||
prompt="请输入你要查询的起始日期(如 2022-01-01)",
|
prompt='请输入你要查询的起始日期(如 2022-01-01)',
|
||||||
parameterless=[Depends(parse_datetime("start"))],
|
parameterless=[Depends(parse_datetime('start'))],
|
||||||
)
|
)
|
||||||
@rank_cmd.got(
|
@rank_cmd.got(
|
||||||
"stop",
|
'stop',
|
||||||
prompt="请输入你要查询的结束日期(如 2022-02-22)",
|
prompt='请输入你要查询的结束日期(如 2022-02-22)',
|
||||||
parameterless=[Depends(parse_datetime("stop"))],
|
parameterless=[Depends(parse_datetime('stop'))],
|
||||||
)
|
)
|
||||||
@rank_cmd.got("group_id", prompt="请输入你要查询的群号。")
|
@rank_cmd.got('group_id', prompt='请输入你要查询的群号。')
|
||||||
async def handle_rank(
|
async def handle_rank(
|
||||||
state: T_State,
|
state: T_State,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
event: Event,
|
event: Event,
|
||||||
session: Session = Depends(extract_session),
|
session: Session = Depends(get_session),
|
||||||
start: datetime = Arg(),
|
start: datetime = Arg(),
|
||||||
stop: datetime = Arg(),
|
stop: datetime = Arg(),
|
||||||
):
|
):
|
||||||
|
if id := state['group_id']:
|
||||||
|
id = str(id)
|
||||||
|
logger.debug(f'group_id: {id}')
|
||||||
|
else:
|
||||||
|
id = session.scene.id
|
||||||
|
logger.debug(f'group_id: {id}')
|
||||||
|
|
||||||
if id := state["group_id"]:
|
if not id:
|
||||||
id = str(id)
|
await saa.Text('没有指定群哦').finish()
|
||||||
logger.debug(f"group_id: {id}")
|
|
||||||
else:
|
|
||||||
id = session.id2
|
|
||||||
logger.debug(f"group_id: {id}")
|
|
||||||
|
|
||||||
if not id:
|
keyword = state['keyword']
|
||||||
await saa.Text("没有指定群哦").finish()
|
|
||||||
|
|
||||||
keyword = state["keyword"]
|
t1 = t.time()
|
||||||
|
raw_rank = await get_user_message_counts(
|
||||||
|
keyword=keyword,
|
||||||
|
scene_ids=[id],
|
||||||
|
types=['message'], # 排除机器人自己发的消息
|
||||||
|
time_start=start,
|
||||||
|
time_stop=stop,
|
||||||
|
exclude_user_ids=plugin_config.excluded_people,
|
||||||
|
)
|
||||||
|
logger.debug(f'获取计数消息花费时间:{t.time() - t1}')
|
||||||
|
|
||||||
if plugin_config.counting_cache:
|
if not raw_rank:
|
||||||
if keyword:
|
await saa.Text(
|
||||||
await saa.Text("已开启缓存~缓存不支持关键词查询哦").finish()
|
'没有获取到排行榜数据哦,请确认时间范围和群号是否正确或者关键词是否存在~'
|
||||||
t1 = t.time()
|
).finish()
|
||||||
raw_rank = await get_cache(start, stop, id)
|
|
||||||
logger.debug(f"获取计数消息花费时间:{t.time() - t1}")
|
|
||||||
else:
|
|
||||||
t1 = t.time()
|
|
||||||
messages = await get_message_records(
|
|
||||||
id2s=[id],
|
|
||||||
id_type=SessionIdType.GROUP,
|
|
||||||
include_bot_id=False,
|
|
||||||
include_bot_type=False,
|
|
||||||
types=["message"], # 排除机器人自己发的消息
|
|
||||||
time_start=start,
|
|
||||||
time_stop=stop,
|
|
||||||
exclude_id1s=plugin_config.excluded_people,
|
|
||||||
)
|
|
||||||
raw_rank = msg_counter(messages, keyword)
|
|
||||||
logger.debug(f"获取计数消息花费时间:{t.time() - t1}")
|
|
||||||
|
|
||||||
if not raw_rank:
|
rank = got_rank(raw_rank)
|
||||||
await saa.Text(
|
ids = await persist_id2user_id([int(i[0]) for i in rank])
|
||||||
"没有获取到排行榜数据哦,请确认时间范围和群号是否正确或者关键词是否存在~"
|
for i in range(len(rank)):
|
||||||
).finish()
|
rank[i][0] = str(ids[i])
|
||||||
|
logger.debug(rank[i])
|
||||||
|
|
||||||
rank = got_rank(raw_rank)
|
t1 = t.time()
|
||||||
ids = await persist_id2user_id([int(i[0]) for i in rank])
|
rank2 = await get_user_infos(bot, event, rank)
|
||||||
for i in range(len(rank)):
|
logger.debug(f'获取用户信息花费时间:{t.time() - t1}')
|
||||||
rank[i][0] = str(ids[i])
|
|
||||||
logger.debug(rank[i])
|
|
||||||
|
|
||||||
t1 = t.time()
|
string: str = ''
|
||||||
rank2 = await get_user_infos(bot, event, rank)
|
if plugin_config.show_text_rank:
|
||||||
logger.debug(f"获取用户信息花费时间:{t.time() - t1}")
|
if keyword:
|
||||||
|
string += f'关于{keyword}的话痨榜结果:\n'
|
||||||
string: str = ""
|
else:
|
||||||
if plugin_config.show_text_rank:
|
string += '话痨榜:\n'
|
||||||
|
|
||||||
if keyword:
|
for i in rank2:
|
||||||
string += f"关于{keyword}的话痨榜结果:\n"
|
logger.debug(i.user_name)
|
||||||
else:
|
for i in range(len(rank2)):
|
||||||
string += "话痨榜:\n"
|
str_example = plugin_config.string_format.format(
|
||||||
|
index=rank2[i].user_index,
|
||||||
for i in rank2:
|
nickname=rank2[i].user_nickname,
|
||||||
logger.debug(i.user_name)
|
chatdatanum=rank2[i].user_bnum,
|
||||||
for i in range(len(rank2)):
|
)
|
||||||
str_example = plugin_config.string_format.format(
|
string += str_example
|
||||||
index=rank2[i].user_index,
|
|
||||||
nickname=rank2[i].user_nickname,
|
|
||||||
chatdatanum=rank2[i].user_bnum,
|
|
||||||
)
|
|
||||||
string += str_example
|
|
||||||
|
|
||||||
msg = saa.Text(string)
|
msg = saa.Text(string)
|
||||||
|
|
||||||
if plugin_config.visualization:
|
if plugin_config.visualization:
|
||||||
t1 = t.time()
|
t1 = t.time()
|
||||||
image = await get_rank_image(rank2)
|
image = await get_rank_image(rank2)
|
||||||
msg += saa.Image(image)
|
msg += saa.Image(image)
|
||||||
logger.debug(f"群聊消息渲染图片花费时间:{t.time() - t1}")
|
logger.debug(f'群聊消息渲染图片花费时间:{t.time() - t1}')
|
||||||
|
|
||||||
if plugin_config.suffix:
|
if plugin_config.suffix:
|
||||||
timecost = t.time() - state["t1"]
|
timecost = t.time() - state['t1']
|
||||||
suffix = saa.Text(plugin_config.string_suffix.format(timecost=timecost))
|
suffix = saa.Text(plugin_config.string_suffix.format(timecost=timecost))
|
||||||
msg += suffix
|
msg += suffix
|
||||||
if not msg:
|
if not msg:
|
||||||
await saa.Text("你把可视化都关了哪来的排行榜?").finish()
|
await saa.Text('你把可视化都关了哪来的排行榜?').finish()
|
||||||
|
|
||||||
await msg.finish(reply=True)
|
if plugin_config.aggregate_transmission:
|
||||||
|
await saa.AggregatedMessageFactory([msg]).finish()
|
||||||
|
else:
|
||||||
|
await msg.finish(reply=True)
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
from pydantic import BaseModel
|
from typing import List, Optional
|
||||||
from typing import Optional, List
|
|
||||||
from nonebot import get_driver, get_plugin_config
|
from nonebot import get_driver, get_plugin_config
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class ScopedConfig(BaseModel):
|
class ScopedConfig(BaseModel):
|
||||||
get_num: int = 5 # 获取人数数量
|
get_num: int = 5 # 获取人数数量
|
||||||
font: str = "SimHei" # 字体格式
|
font: str = 'SimHei' # 字体格式
|
||||||
suffix: bool = True # 是否显示后缀
|
suffix: bool = True # 是否显示后缀
|
||||||
excluded_self: bool = True # 是否排除自己
|
excluded_self: bool = True # 是否排除自己
|
||||||
visualization: bool = True # 是否可视化
|
visualization: bool = True # 是否可视化
|
||||||
show_text_rank:bool = True # 是否显示文本排名
|
show_text_rank: bool = True # 是否显示文本排名
|
||||||
counting_cache: bool = False # 计数缓存(能够提高回复速度)
|
excluded_people: List[str] = [] # 排除的人的QQ号
|
||||||
excluded_people: List[str] = [] # 排除的人的QQ号
|
use_user_info_cache: bool = False # 是否使用用户信息缓存
|
||||||
use_user_info_cache: bool = False # 是否使用用户信息缓存
|
aggregate_transmission: bool = False # 是否聚合转发消息
|
||||||
timezone: Optional[str] = "Asia/Shanghai" # 时区,影响统计时间
|
timezone: Optional[str] = 'Asia/Shanghai' # 时区,影响统计时间
|
||||||
string_suffix: str = "统计花费时间:{timecost}秒" # 消息格式后缀
|
string_suffix: str = '统计花费时间:{timecost}秒' # 消息格式后缀
|
||||||
template_path: str = "./template/rank_template.j2" # 模板路径
|
template_path: str = './template/rank_template.j2' # 模板路径
|
||||||
string_format: str = "第{index}名:\n{nickname},{chatdatanum}条消息\n" # 消息格式
|
string_format: str = (
|
||||||
|
'第{index}名:\n{nickname},{chatdatanum}条消息\n' # 消息格式
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
dialectlist: ScopedConfig = ScopedConfig()
|
dialectlist: ScopedConfig = ScopedConfig()
|
||||||
|
|
||||||
|
|
||||||
global_config = get_driver().config
|
global_config = get_driver().config
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Union
|
|
||||||
from sqlalchemy import Integer
|
|
||||||
from nonebot_plugin_orm import Model
|
|
||||||
from nonebot_plugin_userinfo import UserInfo
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
|
||||||
|
|
||||||
|
|
||||||
class UserRankInfo(UserInfo):
|
|
||||||
user_bnum: int
|
|
||||||
user_proportion: float
|
|
||||||
user_nickname: str
|
|
||||||
user_index: Union[int, str]
|
|
||||||
user_avatar_bytes: bytes
|
|
||||||
|
|
||||||
|
|
||||||
class MessageCountCache(Model):
|
|
||||||
__table_args__ = {"extend_existing": True}
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
||||||
time: Mapped[datetime]
|
|
||||||
session_id: Mapped[int] = mapped_column(Integer, index=True)
|
|
||||||
session_bnum: Mapped[int] = mapped_column(Integer)
|
|
||||||
11
nonebot_plugin_dialectlist/schema.py
Normal file
11
nonebot_plugin_dialectlist/schema.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from typing import Union
|
||||||
|
|
||||||
|
from nonebot_plugin_userinfo import UserInfo
|
||||||
|
|
||||||
|
|
||||||
|
class UserRankInfo(UserInfo):
|
||||||
|
user_bnum: int
|
||||||
|
user_proportion: float
|
||||||
|
user_nickname: str
|
||||||
|
user_index: Union[int, str]
|
||||||
|
user_avatar_bytes: bytes
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import os
|
|
||||||
import json
|
|
||||||
from datetime import datetime
|
|
||||||
from sqlalchemy import delete, or_, select
|
|
||||||
|
|
||||||
from nonebot import get_driver
|
|
||||||
from nonebot.log import logger
|
|
||||||
from nonebot.params import Depends
|
|
||||||
from nonebot.adapters import Event, Bot
|
|
||||||
from nonebot.message import event_postprocessor
|
|
||||||
|
|
||||||
from .model import MessageCountCache
|
|
||||||
from .config import plugin_config
|
|
||||||
|
|
||||||
from nonebot_plugin_localstore import get_data_file
|
|
||||||
from nonebot_plugin_chatrecorder import get_message_records
|
|
||||||
from nonebot_plugin_chatrecorder.utils import remove_timezone
|
|
||||||
from nonebot_plugin_session import extract_session, Session
|
|
||||||
from nonebot_plugin_session_orm import SessionModel, get_session_persist_id
|
|
||||||
from nonebot_plugin_orm import get_session
|
|
||||||
|
|
||||||
|
|
||||||
async def get_cache(time_start: datetime, time_stop: datetime, group_id: str):
|
|
||||||
async with get_session() as db_session:
|
|
||||||
where = [or_(SessionModel.id2 == group_id)]
|
|
||||||
statement = select(SessionModel).where(*where)
|
|
||||||
|
|
||||||
sessions = (await db_session.scalars(statement)).all()
|
|
||||||
|
|
||||||
where = [
|
|
||||||
or_(*[MessageCountCache.session_id == session.id for session in sessions])
|
|
||||||
]
|
|
||||||
statement = select(MessageCountCache).where(*where)
|
|
||||||
where.append(or_(MessageCountCache.time >= remove_timezone(time_start)))
|
|
||||||
where.append(or_(MessageCountCache.time <= remove_timezone(time_stop)))
|
|
||||||
statement = select(MessageCountCache).where(*where)
|
|
||||||
|
|
||||||
user_caches = (await db_session.scalars(statement)).all()
|
|
||||||
raw_rank = {}
|
|
||||||
for i in user_caches:
|
|
||||||
raw_rank[i.session_id] = raw_rank.get(i.session_id, 0) + i.session_bnum
|
|
||||||
return raw_rank
|
|
||||||
|
|
||||||
|
|
||||||
async def build_cache():
|
|
||||||
async with get_session() as db_session:
|
|
||||||
await db_session.execute(delete(MessageCountCache))
|
|
||||||
await db_session.commit()
|
|
||||||
logger.info("先前可能存在的缓存已清空")
|
|
||||||
messages = await get_message_records(types=["message"])
|
|
||||||
async with get_session() as db_session:
|
|
||||||
for msg in messages:
|
|
||||||
msg_session_id = msg.session_persist_id
|
|
||||||
|
|
||||||
where = [or_(MessageCountCache.session_id == msg_session_id)]
|
|
||||||
where.append(
|
|
||||||
or_(
|
|
||||||
MessageCountCache.time
|
|
||||||
== remove_timezone(
|
|
||||||
msg.time.replace(hour=1, minute=0, second=0, microsecond=0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
statement = select(MessageCountCache).where(*where)
|
|
||||||
|
|
||||||
user_cache = (await db_session.scalars(statement)).all()
|
|
||||||
|
|
||||||
if user_cache:
|
|
||||||
user_cache[0].session_bnum += 1
|
|
||||||
else:
|
|
||||||
user_cache = MessageCountCache(
|
|
||||||
session_id=msg.session_persist_id,
|
|
||||||
time=remove_timezone(
|
|
||||||
msg.time.replace(hour=1, minute=0, second=0, microsecond=0)
|
|
||||||
),
|
|
||||||
session_bnum=1,
|
|
||||||
)
|
|
||||||
db_session.add(user_cache)
|
|
||||||
await db_session.commit()
|
|
||||||
|
|
||||||
logger.info("缓存构建完成")
|
|
||||||
|
|
||||||
|
|
||||||
driver = get_driver()
|
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
|
||||||
async def _():
|
|
||||||
if not plugin_config.counting_cache:
|
|
||||||
return
|
|
||||||
f_name = get_data_file("nonebot-plugin-dialectlist", "is-pre-cached.json")
|
|
||||||
if not os.path.exists(f_name):
|
|
||||||
with open(f_name, "w", encoding="utf-8") as f:
|
|
||||||
s = json.dumps({"is-pre-cached": False}, ensure_ascii=False, indent=4)
|
|
||||||
f.write(s)
|
|
||||||
|
|
||||||
with open(f_name, "r", encoding="utf-8") as f:
|
|
||||||
if json.load(f)["is-pre-cached"]:
|
|
||||||
return
|
|
||||||
logger.info("未检查到缓存,开始构建缓存")
|
|
||||||
with open(f_name, "w", encoding="utf-8") as f:
|
|
||||||
await build_cache()
|
|
||||||
json.dump({"is-pre-cached": True}, f, ensure_ascii=False, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
@event_postprocessor
|
|
||||||
async def _(bot: Bot, event: Event, session: Session = Depends(extract_session)):
|
|
||||||
if not plugin_config.counting_cache:
|
|
||||||
return
|
|
||||||
if not session.id2:
|
|
||||||
return
|
|
||||||
if event.get_type() != "message":
|
|
||||||
return
|
|
||||||
now = datetime.now()
|
|
||||||
now = now.replace(hour=1, minute=0, second=0, microsecond=0)
|
|
||||||
|
|
||||||
async with get_session() as db_session:
|
|
||||||
session_id = await get_session_persist_id(session)
|
|
||||||
logger.debug("session_id:" + str(session_id))
|
|
||||||
where = [or_(MessageCountCache.session_id == session_id)]
|
|
||||||
where.append(or_(MessageCountCache.time == remove_timezone(now)))
|
|
||||||
statement = select(MessageCountCache).where(*where)
|
|
||||||
user_cache = (await db_session.scalars(statement)).first()
|
|
||||||
if user_cache:
|
|
||||||
user_cache.session_bnum += 1
|
|
||||||
else:
|
|
||||||
user_cache = MessageCountCache(
|
|
||||||
session_id=session_id,
|
|
||||||
time=remove_timezone(now),
|
|
||||||
session_bnum=1,
|
|
||||||
)
|
|
||||||
db_session.add(user_cache)
|
|
||||||
await db_session.commit()
|
|
||||||
logger.debug("已计入缓存")
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
from zoneinfo import ZoneInfo
|
|
||||||
from typing import Optional, Union
|
|
||||||
from datetime import datetime, time, tzinfo
|
from datetime import datetime, time, tzinfo
|
||||||
|
from typing import Optional, Union
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
from nonebot.adapters import Message
|
||||||
from nonebot.params import Arg
|
from nonebot.params import Arg
|
||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
from nonebot.adapters import Message
|
|
||||||
|
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
|
||||||
from nonebot_plugin_alconna import AlconnaMatcher
|
from nonebot_plugin_alconna import AlconnaMatcher
|
||||||
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
from .config import plugin_config
|
from .config import plugin_config
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
from inspect import cleandoc
|
from inspect import cleandoc
|
||||||
|
|
||||||
__usage__ = cleandoc(
|
__usage__ = cleandoc(
|
||||||
"""
|
"""
|
||||||
快速调用:
|
快速调用:
|
||||||
/今日B话榜 ————看看今天群友发了多少消息。
|
/今日B话榜 ————看看今天群友发了多少消息。
|
||||||
|
|
||||||
### 🎨一般用法
|
### 🎨一般用法
|
||||||
|
|
||||||
|
#### B话榜
|
||||||
|
|
||||||
-`/B话榜` ————看看有史以来(机器人存在以来)群友们发了多少消息! (好像没写)
|
-`/B话榜` ————看看有史以来(机器人存在以来)群友们发了多少消息! (好像没写)
|
||||||
|
|
||||||
-`/今日B话榜` ————看看今天的群友发了多少消息!
|
-`/今日B话榜` ————看看今天的群友发了多少消息!
|
||||||
@@ -16,7 +18,7 @@ __usage__ = cleandoc(
|
|||||||
-`/前日B话榜` ————看看前天的群友发了多少消息!
|
-`/前日B话榜` ————看看前天的群友发了多少消息!
|
||||||
|
|
||||||
-`/本周B话榜` ————看看本周的群友发了多少消息!
|
-`/本周B话榜` ————看看本周的群友发了多少消息!
|
||||||
|
|
||||||
-`/上周B话榜` ————看看上周的群友发了多少消息!
|
-`/上周B话榜` ————看看上周的群友发了多少消息!
|
||||||
|
|
||||||
-`/本月B话榜` ————看看这个月的群友发了多少消息!
|
-`/本月B话榜` ————看看这个月的群友发了多少消息!
|
||||||
@@ -25,8 +27,14 @@ __usage__ = cleandoc(
|
|||||||
|
|
||||||
-`/历史B话榜` ————看看历史上(机器人存在以来)的群友发了多少消息!
|
-`/历史B话榜` ————看看历史上(机器人存在以来)的群友发了多少消息!
|
||||||
|
|
||||||
|
#### 看看B话(kkb)
|
||||||
|
|
||||||
|
-`/看看B话 [@某人|QQ号]` ————看看这个b人在这个b群发了多少b话!
|
||||||
|
|
||||||
### 🚀进阶用法
|
### 🚀进阶用法
|
||||||
|
|
||||||
|
#### B话榜
|
||||||
|
|
||||||
`/{时间类型(今日|年度)?}{B话榜|废话榜} {时间类型?} {ISO8601 格式时间 ?} {群号} {关键词}`
|
`/{时间类型(今日|年度)?}{B话榜|废话榜} {时间类型?} {ISO8601 格式时间 ?} {群号} {关键词}`
|
||||||
|
|
||||||
如:`/B话榜 历史 2024-01-01~2024-01-02 12345678 女装`
|
如:`/B话榜 历史 2024-01-01~2024-01-02 12345678 女装`
|
||||||
@@ -39,5 +47,14 @@ __usage__ = cleandoc(
|
|||||||
`/昨日B话榜 -k 女装`
|
`/昨日B话榜 -k 女装`
|
||||||
`/本周B话榜 -g 12345678`
|
`/本周B话榜 -g 12345678`
|
||||||
|
|
||||||
|
#### 看看B话
|
||||||
|
|
||||||
|
`/看看B话 {@|QQ号} {群号?} {关键词?}`
|
||||||
|
|
||||||
|
以下调用方法均合法:
|
||||||
|
|
||||||
|
`/kkb 114514 1919810 ♂`
|
||||||
|
`/kkb @man -k ♂`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,277 +1,269 @@
|
|||||||
import os
|
|
||||||
import httpx
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
|
||||||
from typing import Dict, List, Optional
|
import httpx
|
||||||
from sqlalchemy import or_, select
|
from sqlalchemy import select, func
|
||||||
from sqlalchemy.sql import ColumnElement
|
|
||||||
|
|
||||||
from nonebot.log import logger
|
|
||||||
from nonebot.params import Depends
|
|
||||||
from nonebot.compat import model_dump
|
|
||||||
from nonebot.matcher import Matcher
|
|
||||||
from nonebot.adapters import Bot, Event
|
from nonebot.adapters import Bot, Event
|
||||||
|
from nonebot.compat import model_dump
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot.log import logger
|
||||||
from nonebot_plugin_session import Session, SessionLevel, extract_session
|
from nonebot.matcher import Matcher
|
||||||
from nonebot_plugin_userinfo import get_user_info, UserInfo
|
from nonebot.params import Depends
|
||||||
from nonebot_plugin_userinfo.exception import NetworkError
|
|
||||||
from nonebot_plugin_localstore import get_cache_dir
|
|
||||||
from nonebot_plugin_htmlrender import template_to_pic
|
|
||||||
from nonebot_plugin_session_orm import SessionModel
|
|
||||||
from nonebot_plugin_chatrecorder import MessageRecord
|
from nonebot_plugin_chatrecorder import MessageRecord
|
||||||
|
from nonebot_plugin_htmlrender import template_to_pic
|
||||||
|
from nonebot_plugin_localstore import get_cache_dir
|
||||||
|
from nonebot_plugin_orm import get_session
|
||||||
|
from nonebot_plugin_userinfo import UserInfo, get_user_info
|
||||||
|
from nonebot_plugin_uninfo import Session
|
||||||
|
from nonebot_plugin_uninfo.model import SceneType
|
||||||
|
from nonebot_plugin_uninfo.orm import SessionModel, UserModel
|
||||||
|
from nonebot_plugin_uninfo import get_session as extract_session
|
||||||
|
from nonebot_plugin_userinfo.exception import NetworkError
|
||||||
|
from nonebot_plugin_chatrecorder.record import filter_statement
|
||||||
|
|
||||||
|
|
||||||
from .model import UserRankInfo
|
|
||||||
from .config import plugin_config
|
from .config import plugin_config
|
||||||
|
from .schema import UserRankInfo
|
||||||
|
|
||||||
cache_path = get_cache_dir("nonebot_plugin_dialectlist")
|
cache_path = get_cache_dir('nonebot_plugin_dialectlist')
|
||||||
|
|
||||||
|
|
||||||
async def ensure_group(matcher: Matcher, session: Session = Depends(extract_session)):
|
async def ensure_group(
|
||||||
"""确保在群组中使用"""
|
matcher: Matcher, session: Session = Depends(extract_session)
|
||||||
if session.level not in [SessionLevel.LEVEL2, SessionLevel.LEVEL3]:
|
):
|
||||||
await matcher.finish("请在群组中使用!")
|
"""确保在群组中使用"""
|
||||||
|
if session.scene.type not in [SceneType.GROUP, SceneType.GUILD]:
|
||||||
|
await matcher.finish('请在群组中使用!')
|
||||||
|
|
||||||
|
|
||||||
async def persist_id2user_id(ids: List) -> List[str]:
|
async def persist_id2user_id(ids: List) -> List[str]:
|
||||||
user_ids = []
|
user_ids = []
|
||||||
async with get_session() as db_session:
|
if not ids:
|
||||||
for i in ids:
|
return user_ids
|
||||||
user_id = (await db_session.scalar(select(SessionModel).where(or_(*[SessionModel.id == i])))).id1 # type: ignore
|
|
||||||
user_ids.append(user_id)
|
async with get_session() as db_session:
|
||||||
return user_ids
|
statement = (
|
||||||
|
select(UserModel.user_id)
|
||||||
|
.join(SessionModel, UserModel.id == SessionModel.user_persist_id)
|
||||||
|
.where(SessionModel.id.in_(ids))
|
||||||
|
)
|
||||||
|
result = await db_session.scalars(statement)
|
||||||
|
user_ids = result.all()
|
||||||
|
|
||||||
|
return list(user_ids)
|
||||||
|
|
||||||
|
|
||||||
async def user_id2persist_id(ids: List[str]) -> List[int]:
|
def got_rank(msg_dict: Dict[int, int]) -> List[List[Any]]:
|
||||||
whereclause: List[ColumnElement[bool]] = []
|
"""### 获得排行榜
|
||||||
whereclause.append(or_(*[SessionModel.id1 == id for id in ids]))
|
|
||||||
statement = (
|
|
||||||
select(SessionModel).where(*whereclause)
|
|
||||||
# .join(SessionModel, SessionModel.id == MessageRecord.session_persist_id)
|
|
||||||
)
|
|
||||||
async with get_session() as db_session:
|
|
||||||
records = (await db_session.scalars(statement)).all()
|
|
||||||
return [i.id for i in records]
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
msg_dict (Dict[int,int]): 要处理的字典
|
||||||
|
|
||||||
async def group_id2persist_id(ids: List[str]) -> List[int]:
|
Returns:
|
||||||
persist_ids = []
|
List[Tuple[int,int]]: 排行榜列表(已排序)
|
||||||
async with get_session() as db_session:
|
"""
|
||||||
for i in ids:
|
rank = []
|
||||||
persist_id = (await db_session.scalar(select(SessionModel).where(or_(*[SessionModel.id2 == i])))).id # type: ignore
|
while len(rank) < plugin_config.get_num:
|
||||||
persist_ids.append(persist_id)
|
try:
|
||||||
return persist_ids
|
max_key = max(msg_dict.items(), key=lambda x: x[1])
|
||||||
|
rank.append(list(max_key))
|
||||||
|
msg_dict.pop(max_key[0])
|
||||||
|
except ValueError:
|
||||||
|
logger.error(
|
||||||
|
'群内拥有聊天记录的人数不足,无法获取到长度为{}的排行榜,已将长度变化为:{}'.format(
|
||||||
|
plugin_config.get_num, len(rank)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
return rank
|
||||||
async def persist_id2group_id(ids: List[str]) -> List[str]:
|
|
||||||
whereclause: List[ColumnElement[bool]] = []
|
|
||||||
whereclause.append(or_(*[SessionModel.id == id for id in ids]))
|
|
||||||
statement = (
|
|
||||||
select(SessionModel).where(*whereclause)
|
|
||||||
# .join(SessionModel, SessionModel.id == MessageRecord.session_persist_id)
|
|
||||||
)
|
|
||||||
async with get_session() as db_session:
|
|
||||||
records = (await db_session.scalars(statement)).all()
|
|
||||||
return [i.id2 for i in records]
|
|
||||||
|
|
||||||
|
|
||||||
def msg_counter(
|
|
||||||
msg_list: List[MessageRecord], keyword: Optional[str]
|
|
||||||
) -> Dict[str, int]:
|
|
||||||
"""### 计算每个人的消息量
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg_list (list[MessageRecord]): 需要处理的消息列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(dict[str,int]): 处理后的消息数量字典,键为用户,值为消息数量
|
|
||||||
"""
|
|
||||||
|
|
||||||
lst: Dict[str, int] = {}
|
|
||||||
msg_len = len(msg_list)
|
|
||||||
logger.info("wow , there are {} msgs to count !!!".format(msg_len))
|
|
||||||
|
|
||||||
for i in msg_list:
|
|
||||||
logger.debug(f"processing msg {i.plain_text}")
|
|
||||||
if keyword:
|
|
||||||
if keyword not in i.plain_text:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
lst[str(i.session_persist_id)] += 1
|
|
||||||
except KeyError:
|
|
||||||
lst[str(i.session_persist_id)] = 1
|
|
||||||
|
|
||||||
logger.debug(f"finish counting, result is {lst}")
|
|
||||||
|
|
||||||
return lst
|
|
||||||
|
|
||||||
|
|
||||||
def got_rank(msg_dict: Dict[str, int]) -> List:
|
|
||||||
"""### 获得排行榜
|
|
||||||
|
|
||||||
Args:
|
|
||||||
msg_dict (Dict[str,int]): 要处理的字典
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[Tuple[str,int]]: 排行榜列表(已排序)
|
|
||||||
"""
|
|
||||||
rank = []
|
|
||||||
while len(rank) < plugin_config.get_num:
|
|
||||||
try:
|
|
||||||
max_key = max(msg_dict.items(), key=lambda x: x[1])
|
|
||||||
rank.append(list(max_key))
|
|
||||||
msg_dict.pop(max_key[0])
|
|
||||||
except ValueError:
|
|
||||||
logger.error(
|
|
||||||
"群内拥有聊天记录的人数不足,无法获取到长度为{}的排行榜,已将长度变化为:{}".format(
|
|
||||||
plugin_config.get_num, len(rank)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
return rank
|
|
||||||
|
|
||||||
|
|
||||||
def remove_control_characters(string: str) -> str:
|
def remove_control_characters(string: str) -> str:
|
||||||
"""### 将字符串中的控制符去除
|
"""### 将字符串中的控制符去除
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
string (str): 需要去除的字符串
|
string (str): 需要去除的字符串
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(str): 经过处理的字符串
|
(str): 经过处理的字符串
|
||||||
"""
|
"""
|
||||||
return "".join(ch for ch in string if unicodedata.category(ch)[0] != "C")
|
return ''.join(ch for ch in string if unicodedata.category(ch)[0] != 'C')
|
||||||
|
|
||||||
|
|
||||||
async def get_rank_image(rank: List[UserRankInfo]) -> bytes:
|
async def get_rank_image(rank: List[UserRankInfo]) -> bytes:
|
||||||
for i in rank:
|
for i in rank:
|
||||||
if i.user_avatar:
|
if i.user_avatar:
|
||||||
try:
|
try:
|
||||||
user_avatar = i.user_avatar_bytes
|
user_avatar = i.user_avatar_bytes
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
user_avatar = open(
|
user_avatar = open(
|
||||||
os.path.dirname(os.path.abspath(__file__))
|
os.path.dirname(os.path.abspath(__file__))
|
||||||
+ "/template/avatar/default.jpg",
|
+ '/template/avatar/default.jpg',
|
||||||
"rb",
|
'rb',
|
||||||
).read()
|
).read()
|
||||||
# if not os.path.exists(cache_path / str(i.user_id)):
|
# if not os.path.exists(cache_path / str(i.user_id)):
|
||||||
with open(cache_path / (str(i.user_id) + ".jpg"), "wb") as f:
|
with open(cache_path / (str(i.user_id) + '.jpg'), 'wb') as f:
|
||||||
f.write(user_avatar)
|
f.write(user_avatar)
|
||||||
|
|
||||||
if plugin_config.template_path[:2] == "./":
|
if plugin_config.template_path[:2] == './':
|
||||||
path = (
|
path = (
|
||||||
os.path.dirname(os.path.abspath(__file__)) + plugin_config.template_path[1:]
|
os.path.dirname(os.path.abspath(__file__))
|
||||||
)
|
+ plugin_config.template_path[1:]
|
||||||
else:
|
)
|
||||||
path = plugin_config.template_path
|
else:
|
||||||
|
path = plugin_config.template_path
|
||||||
|
|
||||||
path_dir, filename = os.path.split(path)
|
path_dir, filename = os.path.split(path)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
os.path.dirname(os.path.abspath(__file__)) + plugin_config.template_path[1:]
|
os.path.dirname(os.path.abspath(__file__))
|
||||||
)
|
+ plugin_config.template_path[1:]
|
||||||
return await template_to_pic(
|
)
|
||||||
path_dir,
|
return await template_to_pic(
|
||||||
filename,
|
path_dir,
|
||||||
{
|
filename,
|
||||||
"users": rank,
|
{
|
||||||
"cache_path": cache_path,
|
'users': rank,
|
||||||
"file_path": os.path.dirname(os.path.abspath(__file__)),
|
'cache_path': cache_path,
|
||||||
},
|
'file_path': os.path.dirname(os.path.abspath(__file__)),
|
||||||
pages={"viewport": {"width": 1000, "height": 10}},
|
},
|
||||||
)
|
pages={'viewport': {'width': 1000, 'height': 10}},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_user_nickname(user_info: UserInfo) -> str:
|
def _get_user_nickname(user_info: UserInfo) -> str:
|
||||||
user_nickname = (
|
user_nickname = (
|
||||||
user_info.user_displayname
|
user_info.user_displayname
|
||||||
if user_info.user_displayname
|
if user_info.user_displayname
|
||||||
else user_info.user_name if user_info.user_name else user_info.user_id
|
else user_info.user_name
|
||||||
)
|
if user_info.user_name
|
||||||
return user_nickname
|
else user_info.user_id
|
||||||
|
)
|
||||||
|
return user_nickname
|
||||||
|
|
||||||
|
|
||||||
async def _get_user_default_avatar() -> bytes:
|
async def _get_user_default_avatar() -> bytes:
|
||||||
img = open(
|
img = open(
|
||||||
os.path.dirname(os.path.abspath(__file__)) + "/template/avatar/default.jpg",
|
os.path.dirname(os.path.abspath(__file__))
|
||||||
"rb",
|
+ '/template/avatar/default.jpg',
|
||||||
).read()
|
'rb',
|
||||||
return img
|
).read()
|
||||||
|
return img
|
||||||
|
|
||||||
|
|
||||||
async def _get_user_avatar(user: UserInfo, client: httpx.AsyncClient) -> bytes:
|
async def _get_user_avatar(user: UserInfo, client: httpx.AsyncClient) -> bytes:
|
||||||
if not user.user_avatar:
|
if not user.user_avatar:
|
||||||
return await _get_user_default_avatar()
|
return await _get_user_default_avatar()
|
||||||
url = user.user_avatar.get_url()
|
url = user.user_avatar.get_url()
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
try:
|
try:
|
||||||
resp = await client.get(url, timeout=10)
|
resp = await client.get(url, timeout=10)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return resp.content
|
return resp.content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error downloading {url}, retry {i}/3: {e}")
|
logger.warning(f'Error downloading {url}, retry {i}/3: {e}')
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
raise NetworkError(f"{url} 下载失败!")
|
raise NetworkError(f'{url} 下载失败!')
|
||||||
|
|
||||||
|
|
||||||
def get_default_user_info() -> UserInfo:
|
def get_default_user_info() -> UserInfo:
|
||||||
user_info = UserInfo(
|
user_info = UserInfo(
|
||||||
user_id="114514",
|
user_id='114514',
|
||||||
user_name="鬼知道这谁,bot获取不了",
|
user_name='鬼知道这谁,bot获取不了',
|
||||||
)
|
)
|
||||||
return user_info
|
return user_info
|
||||||
|
|
||||||
|
|
||||||
async def get_user_infos(
|
async def get_user_infos(
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
event: Event,
|
event: Event,
|
||||||
rank: List,
|
rank: List,
|
||||||
use_cache: bool = plugin_config.use_user_info_cache,
|
use_cache: bool = plugin_config.use_user_info_cache,
|
||||||
) -> List[UserRankInfo]:
|
) -> List[UserRankInfo]:
|
||||||
|
user_ids = [i[0] for i in rank]
|
||||||
|
pool = [get_user_info(bot, event, id, use_cache) for id in user_ids]
|
||||||
|
user_infos = await asyncio.gather(*pool)
|
||||||
|
|
||||||
user_ids = [i[0] for i in rank]
|
async with httpx.AsyncClient() as client:
|
||||||
pool = [get_user_info(bot, event, id, use_cache) for id in user_ids]
|
pool = []
|
||||||
user_infos = await asyncio.gather(*pool)
|
for i in user_infos:
|
||||||
|
if not i:
|
||||||
|
pool.append(_get_user_default_avatar())
|
||||||
|
continue
|
||||||
|
if i.user_avatar:
|
||||||
|
pool.append(_get_user_avatar(i, client))
|
||||||
|
user_avatars = await asyncio.gather(*pool)
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
for i in user_avatars:
|
||||||
pool = []
|
if not i:
|
||||||
for i in user_infos:
|
user_avatars[
|
||||||
if not i:
|
user_avatars.index(i)
|
||||||
pool.append(_get_user_default_avatar())
|
] = await _get_user_default_avatar()
|
||||||
continue
|
|
||||||
if i.user_avatar:
|
|
||||||
pool.append(_get_user_avatar(i, client))
|
|
||||||
user_avatars = await asyncio.gather(*pool)
|
|
||||||
|
|
||||||
for i in user_avatars:
|
total = sum([i[1] for i in rank])
|
||||||
if not i:
|
rank2 = []
|
||||||
user_avatars[user_avatars.index(i)] = await _get_user_default_avatar()
|
for i in range(len(rank)):
|
||||||
|
user_info = user_infos[i]
|
||||||
|
if not user_info:
|
||||||
|
user_info = get_default_user_info()
|
||||||
|
|
||||||
total = sum([i[1] for i in rank])
|
user = UserRankInfo(
|
||||||
rank2 = []
|
**model_dump(user_info),
|
||||||
for i in range(len(rank)):
|
user_bnum=rank[i][1],
|
||||||
|
user_proportion=round(rank[i][1] / total * 100, 2),
|
||||||
|
user_index=i + 1,
|
||||||
|
user_nickname=_get_user_nickname(user_info),
|
||||||
|
user_avatar_bytes=user_avatars[i],
|
||||||
|
)
|
||||||
|
print(user.user_gender)
|
||||||
|
if user.user_gender == 'male':
|
||||||
|
user.user_gender = '♂'
|
||||||
|
elif user.user_gender == 'female':
|
||||||
|
user.user_gender = '♀'
|
||||||
|
else:
|
||||||
|
user.user_gender = '🤔'
|
||||||
|
rank2.append(user)
|
||||||
|
|
||||||
user_info = user_infos[i]
|
return rank2
|
||||||
if not user_info:
|
|
||||||
user_info = get_default_user_info()
|
|
||||||
|
|
||||||
user = UserRankInfo(
|
|
||||||
**model_dump(user_info),
|
|
||||||
user_bnum=rank[i][1],
|
|
||||||
user_proportion=round(rank[i][1] / total * 100, 2),
|
|
||||||
user_index=i + 1,
|
|
||||||
user_nickname=_get_user_nickname(user_info),
|
|
||||||
user_avatar_bytes=user_avatars[i],
|
|
||||||
)
|
|
||||||
print(user.user_gender)
|
|
||||||
if user.user_gender == "male":
|
|
||||||
user.user_gender = "♂"
|
|
||||||
elif user.user_gender == "female":
|
|
||||||
user.user_gender = "♀"
|
|
||||||
else:
|
|
||||||
user.user_gender = "🤔"
|
|
||||||
rank2.append(user)
|
|
||||||
|
|
||||||
return rank2
|
async def get_user_message_counts(
|
||||||
|
keyword: Optional[str] = None, **kwargs
|
||||||
|
) -> Dict[int, int]:
|
||||||
|
"""获取每个用户的消息数量(直接在数据库层面统计)
|
||||||
|
|
||||||
|
参数:
|
||||||
|
* ``keyword``: 可选,关键词,只统计包含该关键词的消息
|
||||||
|
* ``**kwargs``: 筛选参数,具体查看 `filter_statement` 中的定义
|
||||||
|
|
||||||
|
返回值:
|
||||||
|
* ``Dict[str, int]``: 键为user_persist_id,值为该用户的消息数量
|
||||||
|
"""
|
||||||
|
whereclause = filter_statement(**kwargs)
|
||||||
|
|
||||||
|
# 如果提供了关键词,添加关键词过滤条件
|
||||||
|
if keyword:
|
||||||
|
# 构造LIKE条件,类似于msg_counter函数中的正则匹配
|
||||||
|
# 根据数据库类型不同,可能需要调整LIKE的语法
|
||||||
|
keyword_condition = MessageRecord.plain_text.ilike(f'%{keyword}%')
|
||||||
|
whereclause.append(keyword_condition)
|
||||||
|
|
||||||
|
# 使用SQL的GROUP BY和COUNT进行分组统计
|
||||||
|
statement = (
|
||||||
|
select(
|
||||||
|
SessionModel.user_persist_id,
|
||||||
|
func.count(MessageRecord.id).label('message_count'),
|
||||||
|
)
|
||||||
|
.select_from(MessageRecord)
|
||||||
|
.join(SessionModel, SessionModel.id == MessageRecord.session_persist_id)
|
||||||
|
.where(*whereclause)
|
||||||
|
.group_by(SessionModel.user_persist_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
async with get_session() as db_session:
|
||||||
|
result = await db_session.execute(statement)
|
||||||
|
# 转换为字典格式返回
|
||||||
|
return {user_id: count for user_id, count in result.all()}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "nonebot-plugin-dialectlist"
|
name = "nonebot-plugin-dialectlist"
|
||||||
version = "2.4.5"
|
version = "3.0.1"
|
||||||
description = "看看你群群友有多能说"
|
description = "看看你群群友有多能说"
|
||||||
authors = [
|
authors = [{ name = "Chen_Xu233", email = "woyerpa@outlook.com" }]
|
||||||
{name = "Chen_Xu233", email = "woyerpa@outlook.com"},
|
|
||||||
]
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nonebot-plugin-chatrecorder>=0.6.0",
|
"nonebot-plugin-chatrecorder>=0.7.0",
|
||||||
"requests>=2.32.3",
|
|
||||||
"nonebot-plugin-orm[default]",
|
"nonebot-plugin-orm[default]",
|
||||||
"nonebot-plugin-apscheduler>=0.4.0",
|
"nonebot-plugin-apscheduler>=0.4.0",
|
||||||
"nonebot-plugin-alconna>=0.50.2",
|
"nonebot-plugin-alconna>=0.50.2",
|
||||||
@@ -15,25 +12,23 @@ dependencies = [
|
|||||||
"nonebot-plugin-userinfo>=0.2.6",
|
"nonebot-plugin-userinfo>=0.2.6",
|
||||||
"nonebot-plugin-htmlrender>=0.3.3",
|
"nonebot-plugin-htmlrender>=0.3.3",
|
||||||
"nonebot2>=2.3.2",
|
"nonebot2>=2.3.2",
|
||||||
"pillow>=10.4.0",
|
"pillow>=11.3.0",
|
||||||
"nonebot-plugin-uninfo>=0.1.1",
|
"nonebot-plugin-uninfo>=0.1.1",
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9,<3.13"
|
requires-python = ">=3.9,<4.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {text = "MIT"}
|
license = { text = "MIT" }
|
||||||
|
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
"ruff>=0.5.5",
|
"ruff>=0.5.5",
|
||||||
"setuptools>=71.1.0",
|
"setuptools>=80.9.0",
|
||||||
"twine>=5.1.0",
|
"twine>=6.2.0",
|
||||||
"nb-cli>=0.7.6",
|
"nb-cli>=1.4.2",
|
||||||
"py-spy>=0.3.14",
|
"py-spy>=0.3.14",
|
||||||
]
|
]
|
||||||
Test = [
|
Test = ["nonebot-adapter-onebot>=2.4.4"]
|
||||||
"nonebot-adapter-onebot>=2.4.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.pdm]
|
[tool.pdm]
|
||||||
distribution = true
|
distribution = true
|
||||||
|
|||||||
Reference in New Issue
Block a user