mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-15 06:12:53 +08:00
update v0.1.5.0
This commit is contained in:
parent
c551e21766
commit
93539be492
12
README.md
12
README.md
@ -242,16 +242,24 @@ __Docker 最新版本由 [Sakuracio](https://github.com/Sakuracio) 提供__
|
||||
|
||||
## 更新
|
||||
|
||||
### 2022/4/26
|
||||
|
||||
* 修复了群白名单无法正确添加
|
||||
* 优化了管理员帮助图片,背景图层将位于最下层
|
||||
* 修复了树脂140时不断提醒(未测试
|
||||
* 新增了消息记录的消息排行
|
||||
* WebUI新增CPU,内存,磁盘监控
|
||||
* WebUI新增资源文件夹统计可视化
|
||||
|
||||
### 2022/4/12
|
||||
|
||||
* 修复b了命令私聊出错
|
||||
|
||||
### 2022/4/10 \[v0.1.4.8]
|
||||
### 2022/4/10 \[v0.1.4.7]
|
||||
|
||||
* 新增消息记录模块
|
||||
* 丰富处理请求操作提示
|
||||
* web ui新增配置项修改
|
||||
* 修复chat_history阻断消息
|
||||
|
||||
### 2022/4/9
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
__version__: v0.1.4.8
|
||||
__version__: v0.1.5.0
|
||||
@ -5,7 +5,6 @@ from utils.utils import get_matchers
|
||||
from utils.manager import group_manager
|
||||
from nonebot.adapters.onebot.v11 import Bot
|
||||
from nonebot import Driver
|
||||
import asyncio
|
||||
import nonebot
|
||||
|
||||
|
||||
@ -27,12 +26,10 @@ async def create_help_image():
|
||||
"""
|
||||
创建管理员帮助图片
|
||||
"""
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None, _create_help_image
|
||||
)
|
||||
await _create_help_image()
|
||||
|
||||
|
||||
def _create_help_image():
|
||||
async def _create_help_image():
|
||||
"""
|
||||
创建管理员帮助图片
|
||||
"""
|
||||
@ -85,9 +82,9 @@ def _create_help_image():
|
||||
height = len(help_str.split("\n")) * 33
|
||||
A = BuildImage(width, height, font_size=24)
|
||||
_background = BuildImage(width, height, background=background)
|
||||
A.text((150, 110), help_str)
|
||||
A.paste(_background, alpha=True)
|
||||
A.save(admin_help_image)
|
||||
await A.apaste(_background, alpha=True)
|
||||
await A.atext((150, 110), help_str)
|
||||
await A.asave(admin_help_image)
|
||||
logger.info(f'已成功加载 {len(_plugin_name_list)} 条管理员命令')
|
||||
|
||||
|
||||
|
||||
@ -1,41 +1,3 @@
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent
|
||||
from models.chat_history import ChatHistory
|
||||
from ._rule import rule
|
||||
from configs.config import Config
|
||||
from nonebot import on_message
|
||||
import nonebot
|
||||
|
||||
__zx_plugin_name__ = "消息存储 [Hidden]"
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
|
||||
|
||||
Config.add_plugin_config(
|
||||
"chat_history",
|
||||
"FLAG",
|
||||
True,
|
||||
help_="是否开启消息自从存储",
|
||||
name="消息存储",
|
||||
default_value=True
|
||||
)
|
||||
|
||||
|
||||
chat_history = on_message(rule=rule, priority=1, block=False)
|
||||
|
||||
# test = on_command("aa")
|
||||
|
||||
|
||||
@chat_history.handle()
|
||||
async def _(event: MessageEvent):
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await ChatHistory.add_chat_msg(event.user_id, event.group_id, str(event.get_message()))
|
||||
else:
|
||||
await ChatHistory.add_chat_msg(event.user_id, None, str(event.get_message()))
|
||||
|
||||
# @test.handle()
|
||||
# async def _(event: MessageEvent):
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_group_msg(event.group_id))
|
||||
# print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||
nonebot.load_plugins("basic_plugins/chat_history")
|
||||
|
||||
38
basic_plugins/chat_history/chat_message.py
Normal file
38
basic_plugins/chat_history/chat_message.py
Normal file
@ -0,0 +1,38 @@
|
||||
from configs.config import Config
|
||||
from models.chat_history import ChatHistory
|
||||
from nonebot import on_message
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent
|
||||
|
||||
from ._rule import rule
|
||||
|
||||
__zx_plugin_name__ = "消息存储 [Hidden]"
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
|
||||
|
||||
Config.add_plugin_config(
|
||||
"chat_history", "FLAG", True, help_="是否开启消息自从存储", name="消息存储", default_value=True
|
||||
)
|
||||
|
||||
|
||||
chat_history = on_message(rule=rule, priority=1, block=False)
|
||||
|
||||
|
||||
@chat_history.handle()
|
||||
async def _(event: MessageEvent):
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
await ChatHistory.add_chat_msg(
|
||||
event.user_id, event.group_id, str(event.get_message())
|
||||
)
|
||||
else:
|
||||
await ChatHistory.add_chat_msg(event.user_id, None, str(event.get_message()))
|
||||
|
||||
|
||||
# @test.handle()
|
||||
# async def _(event: MessageEvent):
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_group_msg(event.group_id))
|
||||
# print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||
108
basic_plugins/chat_history/chat_message_handle.py
Normal file
108
basic_plugins/chat_history/chat_message_handle.py
Normal file
@ -0,0 +1,108 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import pytz
|
||||
from models.chat_history import ChatHistory
|
||||
from models.group_member_info import GroupInfoUser
|
||||
from nonebot import on_regex
|
||||
from nonebot.adapters.onebot.v11 import GroupMessageEvent
|
||||
from nonebot.params import RegexGroup
|
||||
from utils.image_utils import BuildImage, text2image
|
||||
from utils.utils import is_number
|
||||
from utils.message_builder import image
|
||||
from typing import Tuple, Any
|
||||
|
||||
|
||||
__zx_plugin_name__ = "消息统计"
|
||||
__plugin_usage__ = """
|
||||
usage:
|
||||
发言记录统计
|
||||
regex:(周|月)?消息排行(des|DES)?(n=[0-9]{1,2})?
|
||||
指令:
|
||||
消息统计?(des)?(n=?)
|
||||
周消息统计?(des)?(n=?)
|
||||
月消息统计?(des)?(n=?)
|
||||
示例:
|
||||
消息统计
|
||||
消息统计des
|
||||
消息统计DESn=15
|
||||
消息统计n=15
|
||||
""".strip()
|
||||
__plugin_des__ = "发言消息排行"
|
||||
__plugin_cmd__ = [
|
||||
"消息统计",
|
||||
"周消息统计",
|
||||
"月消息统计"
|
||||
]
|
||||
__plugin_type__ = ("数据统计", 1)
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
"level": 5,
|
||||
"cmd": ["消息统计"],
|
||||
}
|
||||
|
||||
|
||||
msg_handler = on_regex(r"(周|月)?消息统计(des|DES)?(n=[0-9]{1,2})?", priority=5, block=True)
|
||||
|
||||
|
||||
@msg_handler.handle()
|
||||
async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()):
|
||||
gid = event.group_id
|
||||
date_scope = None
|
||||
date, order, num = reg_group
|
||||
num = num.split("=")[-1] or 10
|
||||
if num and is_number(num) and 10 < int(num) < 50:
|
||||
num = int(num)
|
||||
if date in ["周"]:
|
||||
date_scope = (datetime.now() - timedelta(days=7), datetime.now())
|
||||
elif date in ["月"]:
|
||||
date_scope = (datetime.now() - timedelta(days=30), datetime.now())
|
||||
if rank_data := await ChatHistory.get_group_msg_rank(
|
||||
gid, num, order or "DESC", date_scope
|
||||
):
|
||||
name = "昵称:\n\n"
|
||||
num_str = "发言次数:\n\n"
|
||||
idx = 1
|
||||
for uid, num in rank_data:
|
||||
try:
|
||||
user_name = (await GroupInfoUser.get_member_info(uid, gid)).user_name
|
||||
except AttributeError:
|
||||
user_name = uid
|
||||
name += f"\t{idx}.{user_name} \n\n"
|
||||
num_str += f"\t{num}\n\n"
|
||||
idx += 1
|
||||
name_img = await text2image(name.strip(), padding=10, color="#f9f6f2")
|
||||
num_img = await text2image(num_str.strip(), padding=10, color="#f9f6f2")
|
||||
if not date_scope:
|
||||
if date_scope := await ChatHistory.get_group_first_msg_datetime(gid):
|
||||
date_scope = date_scope.astimezone(
|
||||
pytz.timezone("Asia/Shanghai")
|
||||
).replace(microsecond=0)
|
||||
else:
|
||||
date_scope = datetime.now().replace(microsecond=0)
|
||||
date_str = f"日期:{date_scope} - 至今"
|
||||
else:
|
||||
date_str = f"日期:{date_scope[0].replace(microsecond=0)} - {date_scope[1].replace(microsecond=0)}"
|
||||
date_w = BuildImage(0, 0, font_size=15).getsize(date_str)[0]
|
||||
img_w = date_w if date_w > name_img.w + num_img.w else name_img.w + num_img.w
|
||||
A = BuildImage(
|
||||
img_w + 15,
|
||||
num_img.h + 30,
|
||||
color="#f9f6f2",
|
||||
font="CJGaoDeGuo.otf",
|
||||
font_size=15,
|
||||
)
|
||||
await A.atext((10, 10), date_str)
|
||||
await A.apaste(name_img, (0, 30))
|
||||
await A.apaste(num_img, (name_img.w, 30))
|
||||
await msg_handler.send(image(b64=A.pic2bs4()))
|
||||
|
||||
|
||||
# @test.handle()
|
||||
# async def _(event: MessageEvent):
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "private"))
|
||||
# print(await ChatHistory.get_user_msg(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_user_msg_count(event.user_id, "group"))
|
||||
# print(await ChatHistory.get_group_msg(event.group_id))
|
||||
# print(await ChatHistory.get_group_msg_count(event.group_id))
|
||||
@ -10,7 +10,7 @@ async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]):
|
||||
r = None
|
||||
if (
|
||||
(
|
||||
(api == "send_msg" and data["message_type"] == "group")
|
||||
(api == "send_msg" and data.get("message_type") == "group")
|
||||
or api == "send_group_msg"
|
||||
)
|
||||
and (
|
||||
|
||||
@ -136,7 +136,7 @@ async def _():
|
||||
@manager_group_whitelist.handle()
|
||||
async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()):
|
||||
cmd = cmd[0]
|
||||
msg = arg.extract_plain_text().strip()
|
||||
msg = arg.extract_plain_text().strip().split()
|
||||
all_group = [
|
||||
g["group_id"] for g in await bot.get_group_list()
|
||||
]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Literal, Optional
|
||||
from typing import List, Literal, Optional, Tuple, Union
|
||||
|
||||
from services.db_context import db
|
||||
|
||||
@ -36,6 +36,86 @@ class ChatHistory(db.Model):
|
||||
"""
|
||||
return await cls._get_msg(uid, None, "user", msg_type, days).gino.all()
|
||||
|
||||
@classmethod
|
||||
async def get_group_user_msg(
|
||||
cls,
|
||||
uid: int,
|
||||
gid: int,
|
||||
limit: int = 10,
|
||||
date_scope: Tuple[datetime, datetime] = None,
|
||||
) -> List["ChatHistory"]:
|
||||
"""
|
||||
说明:
|
||||
获取群聊指定用户聊天记录
|
||||
参数:
|
||||
:param uid: qq
|
||||
:param gid: 群号
|
||||
:param limit: 获取数量
|
||||
:param date_scope: 日期范围,默认None为全搜索
|
||||
"""
|
||||
return (
|
||||
await cls._get_msg(uid, gid, "group", days=date_scope)
|
||||
.limit(limit)
|
||||
.gino.all()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def get_group_user_msg_count(cls, uid: int, gid: int) -> Optional[int]:
|
||||
"""
|
||||
说明:
|
||||
查询群聊指定用户的聊天记录数量
|
||||
参数:
|
||||
:param uid: qq
|
||||
:param gid: 群号
|
||||
"""
|
||||
if x := await db.first(
|
||||
db.text(
|
||||
f"SELECT COUNT(id) as sum FROM public.chat_history WHERE user_qq = {uid} AND group_id = {gid}"
|
||||
)
|
||||
):
|
||||
return x[0]
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def get_group_msg_rank(
|
||||
cls,
|
||||
gid: int,
|
||||
limit: int = 10,
|
||||
order: str = "DESC",
|
||||
date_scope: Optional[Tuple[datetime, datetime]] = None,
|
||||
) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
说明:
|
||||
获取排行数据
|
||||
参数:
|
||||
:param gid: 群号
|
||||
:param limit: 获取数量
|
||||
:param order: 排序类型,desc,des
|
||||
:param date_scope: 日期范围
|
||||
"""
|
||||
sql = f"SELECT user_qq, COUNT(id) as sum FROM public.chat_history WHERE group_id = {gid} "
|
||||
if date_scope:
|
||||
sql += f"AND create_time BETWEEN '{date_scope[0]}' AND '{date_scope[1]}' "
|
||||
sql += f"GROUP BY user_qq ORDER BY sum {order if order and order.upper() != 'DES' else ''} LIMIT {limit}"
|
||||
print(sql)
|
||||
return await db.all(db.text(sql))
|
||||
|
||||
@classmethod
|
||||
async def get_group_first_msg_datetime(cls, gid: int) -> Optional[datetime]:
|
||||
"""
|
||||
说明:
|
||||
获取群第一条记录消息时间
|
||||
参数:
|
||||
:param gid:
|
||||
"""
|
||||
if (
|
||||
msg := await cls.query.where(cls.group_id == gid)
|
||||
.order_by(cls.create_time)
|
||||
.gino.first()
|
||||
):
|
||||
return msg.create_time
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def get_user_msg_count(
|
||||
cls,
|
||||
@ -51,7 +131,9 @@ class ChatHistory(db.Model):
|
||||
:param msg_type: 消息类型,私聊或群聊
|
||||
:param days: 限制日期
|
||||
"""
|
||||
return (await cls._get_msg(uid, None, "user", msg_type, days, True).gino.first())[0]
|
||||
return (
|
||||
await cls._get_msg(uid, None, "user", msg_type, days, True).gino.first()
|
||||
)[0]
|
||||
|
||||
@classmethod
|
||||
async def get_group_msg(
|
||||
@ -81,7 +163,9 @@ class ChatHistory(db.Model):
|
||||
:param gid: 用户qq
|
||||
:param days: 限制日期
|
||||
"""
|
||||
return (await cls._get_msg(None, gid, "group", None, days, True).gino.first())[0]
|
||||
return (await cls._get_msg(None, gid, "group", None, days, True).gino.first())[
|
||||
0
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _get_msg(
|
||||
@ -89,9 +173,9 @@ class ChatHistory(db.Model):
|
||||
uid: Optional[int],
|
||||
gid: Optional[int],
|
||||
type_: Literal["user", "group"],
|
||||
msg_type: Optional[Literal["private", "group"]],
|
||||
days: Optional[int],
|
||||
is_select_count: bool = False
|
||||
msg_type: Optional[Literal["private", "group"]] = None,
|
||||
days: Optional[Union[int, Tuple[datetime, datetime]]] = None,
|
||||
is_select_count: bool = False,
|
||||
):
|
||||
"""
|
||||
说明:
|
||||
@ -104,8 +188,8 @@ class ChatHistory(db.Model):
|
||||
:param days: 限制日期
|
||||
"""
|
||||
if is_select_count:
|
||||
setattr(ChatHistory, 'count', db.func.count(cls.id).label('count'))
|
||||
query = cls.select('count')
|
||||
setattr(ChatHistory, "count", db.func.count(cls.id).label("count"))
|
||||
query = cls.select("count")
|
||||
else:
|
||||
query = cls.query
|
||||
if type_ == "user":
|
||||
@ -116,8 +200,15 @@ class ChatHistory(db.Model):
|
||||
query = query.where(cls.group_id != None)
|
||||
else:
|
||||
query = query.where(cls.group_id == gid)
|
||||
if uid:
|
||||
query = query.where(cls.user_qq == uid)
|
||||
if days:
|
||||
query = query.where(
|
||||
cls.create_time >= datetime.now() - timedelta(days=days)
|
||||
)
|
||||
if isinstance(days, int):
|
||||
query = query.where(
|
||||
cls.create_time >= datetime.now() - timedelta(days=days)
|
||||
)
|
||||
elif isinstance(days, tuple):
|
||||
query = query.where(cls.create_time >= days[0]).where(
|
||||
cls.create_time <= days[1]
|
||||
)
|
||||
return query
|
||||
|
||||
@ -256,9 +256,7 @@ class Genshin(db.Model):
|
||||
if x:
|
||||
await cls._add_query_uid(uid, uid)
|
||||
return x.cookie
|
||||
for u in [
|
||||
x for x in await cls.query.order_by(db.func.random()).gino.all() if x.cookie
|
||||
]:
|
||||
for u in await cls.query.where(cls.cookie != "").order_by(db.func.random()).gino.all():
|
||||
if not u.today_query_uid or len(u.today_query_uid[:-1].split()) < 30:
|
||||
await cls._add_query_uid(uid, u.uid)
|
||||
return u.cookie
|
||||
|
||||
@ -23,17 +23,17 @@ async def _():
|
||||
g_list = await Genshin.get_all_auto_sign_user()
|
||||
for u in g_list:
|
||||
if u.auto_sign_time:
|
||||
date = await Genshin.random_sign_time(u.uid)
|
||||
scheduler.add_job(
|
||||
_sign,
|
||||
"date",
|
||||
run_date=date.replace(microsecond=0),
|
||||
id=f"genshin_auto_sign_{u.uid}_{u.user_qq}_0",
|
||||
args=[u.user_qq, u.uid, 0],
|
||||
)
|
||||
logger.info(
|
||||
f"genshin_sign add_job:USER:{u.user_qq} UID:{u.uid} " f"{date} 原神自动签到"
|
||||
)
|
||||
if date := await Genshin.random_sign_time(u.uid):
|
||||
scheduler.add_job(
|
||||
_sign,
|
||||
"date",
|
||||
run_date=date.replace(microsecond=0),
|
||||
id=f"genshin_auto_sign_{u.uid}_{u.user_qq}_0",
|
||||
args=[u.user_qq, u.uid, 0],
|
||||
)
|
||||
logger.info(
|
||||
f"genshin_sign add_job:USER:{u.user_qq} UID:{u.uid} " f"{date} 原神自动签到"
|
||||
)
|
||||
|
||||
|
||||
def add_job(user_id: int, uid: int, date: datetime):
|
||||
|
||||
@ -299,7 +299,6 @@ def get_country_data_image(world_data_dict: Dict) -> BuildImage:
|
||||
# 层岩巨渊 和 地下矿区 算一个
|
||||
region = BuildImage(790, 267 * (len(world_data_dict) - 1), color="#F9F6F2")
|
||||
height = 0
|
||||
print(world_data_dict)
|
||||
for country in ["蒙德", "龙脊雪山", "璃月", "层岩巨渊", "稻妻", "渊下宫"]:
|
||||
x = BuildImage(790, 250, color="#3A4467")
|
||||
logo = BuildImage(180, 180, background=image_path / "logo" / f"{country}.png")
|
||||
|
||||
@ -20,6 +20,9 @@ driver: Driver = nonebot.get_driver()
|
||||
get_memo = require("query_memo").get_memo
|
||||
|
||||
|
||||
global_map = {}
|
||||
|
||||
|
||||
class UserManager:
|
||||
def __init__(self, max_error_count: int = 3):
|
||||
self._data = []
|
||||
@ -146,8 +149,8 @@ async def _remind(user_id: int, uid: str):
|
||||
if current_resin < max_resin:
|
||||
user_manager.remove(uid)
|
||||
user_manager.remove_overflow(uid)
|
||||
if max_resin - 40 <= current_resin <= max_resin - 20:
|
||||
next_time = now + timedelta(minutes=(max_resin - 20 - current_resin) * 8, seconds=10)
|
||||
if max_resin - 40 < current_resin <= max_resin - 20:
|
||||
next_time = now + timedelta(minutes=(max_resin - 20 - current_resin + 1) * 8, seconds=10)
|
||||
elif current_resin < max_resin:
|
||||
next_time = now + timedelta(minutes=(max_resin - current_resin) * 8, seconds=10)
|
||||
elif current_resin == max_resin:
|
||||
@ -189,6 +192,7 @@ async def _remind(user_id: int, uid: str):
|
||||
user_manager.remove_error_count(uid)
|
||||
await Genshin.set_user_resin_recovery_time(int(uid), next_time)
|
||||
scheduler.add_job(
|
||||
_remind,
|
||||
_remind,
|
||||
"date",
|
||||
run_date=next_time,
|
||||
|
||||
@ -144,7 +144,6 @@ class Setu(db.Model):
|
||||
return _tmp_local_id
|
||||
return -1
|
||||
|
||||
|
||||
@classmethod
|
||||
async def update_setu_data(
|
||||
cls,
|
||||
|
||||
@ -139,7 +139,7 @@ async def update_setu_img(flag: bool = False):
|
||||
f"--> /{path}/{image.local_id}.jpg"
|
||||
)
|
||||
os.rename(
|
||||
TEMP_PATH / f"{image.local_id}.jpg",
|
||||
TEMP_PATH / f"/{image.local_id}.jpg",
|
||||
path / f"{image.local_id}.jpg",
|
||||
)
|
||||
except FileNotFoundError:
|
||||
|
||||
@ -53,7 +53,7 @@ __plugin_cmd__ = [
|
||||
"我的周功能调用统计 ?[功能]",
|
||||
"我的月功能调用统计 ?[功能]",
|
||||
]
|
||||
__plugin_type__ = ("功能调用统计可视化", 1)
|
||||
__plugin_type__ = ("数据统计", 1)
|
||||
__plugin_version__ = 0.1
|
||||
__plugin_author__ = "HibiKier"
|
||||
__plugin_settings__ = {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from .group import *
|
||||
from .plugins import *
|
||||
from .request import *
|
||||
from .system import *
|
||||
|
||||
219
plugins/web_ui/api/system.py
Normal file
219
plugins/web_ui/api/system.py
Normal file
@ -0,0 +1,219 @@
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import psutil
|
||||
import ujson as json
|
||||
from configs.path_config import (
|
||||
DATA_PATH,
|
||||
FONT_PATH,
|
||||
IMAGE_PATH,
|
||||
LOG_PATH,
|
||||
RECORD_PATH,
|
||||
TEMP_PATH,
|
||||
TEXT_PATH,
|
||||
)
|
||||
from services.log import logger
|
||||
from utils.http_utils import AsyncHttpx
|
||||
|
||||
from ..auth import Depends, User, token_to_user
|
||||
from ..config import *
|
||||
|
||||
CPU_DATA_PATH = DATA_PATH / "system" / "cpu.json"
|
||||
MEMORY_DATA_PATH = DATA_PATH / "system" / "memory.json"
|
||||
DISK_DATA_PATH = DATA_PATH / "system" / "disk.json"
|
||||
CPU_DATA_PATH.parent.mkdir(exist_ok=True, parents=True)
|
||||
cpu_data = {"data": []}
|
||||
memory_data = {"data": []}
|
||||
disk_data = {"data": []}
|
||||
|
||||
|
||||
@app.get("/webui/system")
|
||||
async def _(user: User = Depends(token_to_user)) -> Result:
|
||||
return await get_system_data()
|
||||
|
||||
|
||||
@app.get("/webui/system/status")
|
||||
async def _(user: User = Depends(token_to_user)) -> Result:
|
||||
return Result(
|
||||
code=200,
|
||||
data=await asyncio.get_event_loop().run_in_executor(None, _get_system_status),
|
||||
)
|
||||
|
||||
|
||||
@app.get("/webui/system/disk")
|
||||
async def _(type_: Optional[str] = None, user: User = Depends(token_to_user)) -> Result:
|
||||
return Result(
|
||||
code=200,
|
||||
data=await asyncio.get_event_loop().run_in_executor(
|
||||
None, _get_system_disk, type_
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@app.get("/webui/system/statusList")
|
||||
async def _(user: User = Depends(token_to_user)) -> Result:
|
||||
global cpu_data, memory_data, disk_data
|
||||
await asyncio.get_event_loop().run_in_executor(None, _get_system_status)
|
||||
cpu_rst = cpu_data["data"][-10:] if len(cpu_data["data"]) > 10 else cpu_data["data"]
|
||||
memory_rst = (
|
||||
memory_data["data"][-10:]
|
||||
if len(memory_data["data"]) > 10
|
||||
else memory_data["data"]
|
||||
)
|
||||
disk_rst = (
|
||||
disk_data["data"][-10:] if len(disk_data["data"]) > 10 else disk_data["data"]
|
||||
)
|
||||
return Result(
|
||||
code=200,
|
||||
data=SystemStatusList(
|
||||
cpu_data=cpu_rst,
|
||||
memory_data=memory_rst,
|
||||
disk_data=disk_rst,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def get_system_data():
|
||||
"""
|
||||
说明:
|
||||
获取系统信息,资源文件大小,网络状态等
|
||||
"""
|
||||
baidu = 200
|
||||
google = 200
|
||||
try:
|
||||
await AsyncHttpx.get("https://www.baidu.com/", timeout=5)
|
||||
except Exception as e:
|
||||
logger.warning(f"访问BaiDu失败... {type(e)}: {e}")
|
||||
baidu = 404
|
||||
try:
|
||||
await AsyncHttpx.get("https://www.google.com/", timeout=5)
|
||||
except Exception as e:
|
||||
logger.warning(f"访问Google失败... {type(e)}: {e}")
|
||||
google = 404
|
||||
network = SystemNetwork(baidu=baidu, google=google)
|
||||
disk = await asyncio.get_event_loop().run_in_executor(None, _get_system_disk)
|
||||
status = await asyncio.get_event_loop().run_in_executor(None, _get_system_status)
|
||||
return Result(
|
||||
code=200,
|
||||
data=SystemResult(
|
||||
status=status,
|
||||
network=network,
|
||||
disk=disk,
|
||||
check_time=datetime.now().replace(microsecond=0),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _get_system_status() -> SystemStatus:
|
||||
"""
|
||||
说明:
|
||||
获取系统信息等
|
||||
"""
|
||||
cpu = psutil.cpu_percent()
|
||||
memory = psutil.virtual_memory().percent
|
||||
disk = psutil.disk_usage("/").percent
|
||||
save_system_data(cpu, memory, disk)
|
||||
return SystemStatus(
|
||||
cpu=cpu,
|
||||
memory=memory,
|
||||
disk=disk,
|
||||
check_time=datetime.now().replace(microsecond=0),
|
||||
)
|
||||
|
||||
|
||||
def _get_system_disk(
|
||||
type_: Optional[str],
|
||||
) -> Union[SystemFolderSize, Dict[str, Union[float, datetime]]]:
|
||||
"""
|
||||
说明:
|
||||
获取资源文件大小等
|
||||
"""
|
||||
if not type_:
|
||||
disk = SystemFolderSize(
|
||||
font_dir_size=_get_dir_size(FONT_PATH) / 1024 / 1024,
|
||||
image_dir_size=_get_dir_size(IMAGE_PATH) / 1024 / 1024,
|
||||
text_dir_size=_get_dir_size(TEXT_PATH) / 1024 / 1024,
|
||||
record_dir_size=_get_dir_size(RECORD_PATH) / 1024 / 1024,
|
||||
temp_dir_size=_get_dir_size(TEMP_PATH) / 1024 / 102,
|
||||
data_dir_size=_get_dir_size(DATA_PATH) / 1024 / 1024,
|
||||
log_dir_size=_get_dir_size(LOG_PATH) / 1024 / 1024,
|
||||
check_time=datetime.now().replace(microsecond=0),
|
||||
)
|
||||
return disk
|
||||
else:
|
||||
if type_ == "image":
|
||||
dir_path = IMAGE_PATH
|
||||
elif type_ == "font":
|
||||
dir_path = FONT_PATH
|
||||
elif type_ == "text":
|
||||
dir_path = TEXT_PATH
|
||||
elif type_ == "record":
|
||||
dir_path = RECORD_PATH
|
||||
elif type_ == "data":
|
||||
dir_path = DATA_PATH
|
||||
elif type_ == "temp":
|
||||
dir_path = TEMP_PATH
|
||||
else:
|
||||
dir_path = LOG_PATH
|
||||
dir_map = {}
|
||||
other_file_size = 0
|
||||
for file in os.listdir(dir_path):
|
||||
file = Path(dir_path / file)
|
||||
if file.is_dir():
|
||||
dir_map[file.name] = _get_dir_size(file) / 1024 / 1024
|
||||
else:
|
||||
other_file_size += os.path.getsize(file) / 1024 / 1024
|
||||
dir_map["其他文件"] = other_file_size
|
||||
dir_map["check_time"] = datetime.now().replace(microsecond=0)
|
||||
return dir_map
|
||||
|
||||
|
||||
def _get_dir_size(dir_path: Path) -> float:
|
||||
"""
|
||||
说明:
|
||||
获取文件夹大小
|
||||
参数:
|
||||
:param dir_path: 文件夹路径
|
||||
"""
|
||||
size = 0
|
||||
for root, dirs, files in os.walk(dir_path):
|
||||
size += sum([os.path.getsize(os.path.join(root, name)) for name in files])
|
||||
return size
|
||||
|
||||
|
||||
def save_system_data(cpu: float, memory: float, disk: float):
|
||||
"""
|
||||
说明:
|
||||
保存一些系统信息
|
||||
参数:
|
||||
:param cpu: cpu
|
||||
:param memory: memory
|
||||
:param disk: disk
|
||||
"""
|
||||
global cpu_data, memory_data, disk_data
|
||||
if CPU_DATA_PATH.exists() and not cpu_data["data"]:
|
||||
with open(CPU_DATA_PATH, "r") as f:
|
||||
cpu_data = json.load(f)
|
||||
if MEMORY_DATA_PATH.exists() and not memory_data["data"]:
|
||||
with open(MEMORY_DATA_PATH, "r") as f:
|
||||
memory_data = json.load(f)
|
||||
if DISK_DATA_PATH.exists() and not disk_data["data"]:
|
||||
with open(DISK_DATA_PATH, "r") as f:
|
||||
disk_data = json.load(f)
|
||||
now = str(datetime.now().time().replace(microsecond=0))
|
||||
cpu_data["data"].append({"time": now, "data": cpu})
|
||||
memory_data["data"].append({"time": now, "data": memory})
|
||||
disk_data["data"].append({"time": now, "data": disk})
|
||||
if len(cpu_data["data"]) > 50:
|
||||
cpu_data["data"] = cpu_data["data"][-50:]
|
||||
if len(memory_data["data"]) > 50:
|
||||
memory_data["data"] = memory_data["data"][-50:]
|
||||
if len(disk_data["data"]) > 50:
|
||||
disk_data["data"] = disk_data["data"][-50:]
|
||||
with open(CPU_DATA_PATH, "w") as f:
|
||||
json.dump(cpu_data, f, indent=4, ensure_ascii=False)
|
||||
with open(MEMORY_DATA_PATH, "w") as f:
|
||||
json.dump(memory_data, f, indent=4, ensure_ascii=False)
|
||||
with open(DISK_DATA_PATH, "w") as f:
|
||||
json.dump(disk_data, f, indent=4, ensure_ascii=False)
|
||||
@ -1,6 +1,7 @@
|
||||
from typing import Optional, List, Any, Union
|
||||
from typing import Optional, List, Any, Union, Dict
|
||||
from pydantic import BaseModel
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from datetime import datetime
|
||||
import nonebot
|
||||
|
||||
|
||||
@ -18,6 +19,9 @@ app.add_middleware(
|
||||
|
||||
|
||||
class CdLimit(BaseModel):
|
||||
"""
|
||||
Cd 限制
|
||||
"""
|
||||
cd: int
|
||||
status: bool
|
||||
check_type: str
|
||||
@ -26,6 +30,9 @@ class CdLimit(BaseModel):
|
||||
|
||||
|
||||
class BlockLimit(BaseModel):
|
||||
"""
|
||||
Block限制
|
||||
"""
|
||||
status: bool
|
||||
check_type: str
|
||||
limit_type: str
|
||||
@ -33,6 +40,9 @@ class BlockLimit(BaseModel):
|
||||
|
||||
|
||||
class CountLimit(BaseModel):
|
||||
"""
|
||||
Count限制
|
||||
"""
|
||||
max_count: int
|
||||
status: bool
|
||||
limit_type: bool
|
||||
@ -40,6 +50,9 @@ class CountLimit(BaseModel):
|
||||
|
||||
|
||||
class PluginManager(BaseModel):
|
||||
"""
|
||||
插件信息
|
||||
"""
|
||||
plugin_name: str # 插件名称
|
||||
status: Optional[bool] # 插件状态
|
||||
error: Optional[bool] # 加载状态
|
||||
@ -49,6 +62,9 @@ class PluginManager(BaseModel):
|
||||
|
||||
|
||||
class PluginSettings(BaseModel):
|
||||
"""
|
||||
插件基本设置
|
||||
"""
|
||||
level: Optional[int] # 群权限等级
|
||||
default_status: Optional[bool] # 默认开关
|
||||
limit_superuser: Optional[bool] # 是否限制超级用户
|
||||
@ -58,6 +74,9 @@ class PluginSettings(BaseModel):
|
||||
|
||||
|
||||
class PluginConfig(BaseModel):
|
||||
"""
|
||||
插件配置项
|
||||
"""
|
||||
id: int
|
||||
key: str
|
||||
value: Optional[Any]
|
||||
@ -66,6 +85,9 @@ class PluginConfig(BaseModel):
|
||||
|
||||
|
||||
class Plugin(BaseModel):
|
||||
"""
|
||||
插件
|
||||
"""
|
||||
model: str # 模块
|
||||
plugin_settings: Optional[PluginSettings]
|
||||
plugin_manager: Optional[PluginManager]
|
||||
@ -76,6 +98,9 @@ class Plugin(BaseModel):
|
||||
|
||||
|
||||
class Group(BaseModel):
|
||||
"""
|
||||
群组信息
|
||||
"""
|
||||
group_id: int
|
||||
group_name: str
|
||||
member_count: int
|
||||
@ -83,12 +108,18 @@ class Group(BaseModel):
|
||||
|
||||
|
||||
class Task(BaseModel):
|
||||
"""
|
||||
被动技能
|
||||
"""
|
||||
name: str
|
||||
nameZh: str
|
||||
status: bool
|
||||
|
||||
|
||||
class GroupResult(BaseModel):
|
||||
"""
|
||||
群组返回数据
|
||||
"""
|
||||
group: Group
|
||||
level: int
|
||||
status: bool
|
||||
@ -97,6 +128,9 @@ class GroupResult(BaseModel):
|
||||
|
||||
|
||||
class RequestResult(BaseModel):
|
||||
"""
|
||||
好友/群组请求管理
|
||||
"""
|
||||
oid: str
|
||||
id: int
|
||||
flag: str
|
||||
@ -111,11 +145,68 @@ class RequestResult(BaseModel):
|
||||
|
||||
|
||||
class RequestParma(BaseModel):
|
||||
"""
|
||||
操作请求接收数据
|
||||
"""
|
||||
id: int
|
||||
handle: str
|
||||
type: str
|
||||
|
||||
|
||||
class SystemStatus(BaseModel):
|
||||
"""
|
||||
系统状态
|
||||
"""
|
||||
cpu: int
|
||||
memory: int
|
||||
disk: int
|
||||
check_time: datetime
|
||||
|
||||
|
||||
class SystemNetwork(BaseModel):
|
||||
"""
|
||||
系统网络状态
|
||||
"""
|
||||
baidu: int
|
||||
google: int
|
||||
|
||||
|
||||
class SystemFolderSize(BaseModel):
|
||||
"""
|
||||
资源文件占比
|
||||
"""
|
||||
font_dir_size: float
|
||||
image_dir_size: float
|
||||
text_dir_size: float
|
||||
record_dir_size: float
|
||||
temp_dir_size: float
|
||||
data_dir_size: float
|
||||
log_dir_size: float
|
||||
check_time: datetime
|
||||
|
||||
|
||||
class SystemStatusList(BaseModel):
|
||||
"""
|
||||
状态记录
|
||||
"""
|
||||
cpu_data: List[Dict[str, Union[float, str]]]
|
||||
memory_data: List[Dict[str, Union[float, str]]]
|
||||
disk_data: List[Dict[str, Union[float, str]]]
|
||||
|
||||
|
||||
class SystemResult(BaseModel):
|
||||
"""
|
||||
系统api返回
|
||||
"""
|
||||
status: SystemStatus
|
||||
network: SystemNetwork
|
||||
disk: SystemFolderSize
|
||||
check_time: datetime
|
||||
|
||||
|
||||
class Result(BaseModel):
|
||||
"""
|
||||
总体返回
|
||||
"""
|
||||
code: int
|
||||
data: Any
|
||||
|
||||
@ -14,16 +14,20 @@ _browser: Optional[Browser] = None
|
||||
|
||||
|
||||
async def init(**kwargs) -> Optional[Browser]:
|
||||
global _browser
|
||||
if platform.system() == "Windows":
|
||||
return None
|
||||
try:
|
||||
global _browser
|
||||
browser = await async_playwright().start()
|
||||
_browser = await browser.chromium.launch(**kwargs)
|
||||
return _browser
|
||||
except NotImplementedError:
|
||||
logger.warning("win环境下 初始化playwright失败,相关功能将被限制....")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"启动chromium发生错误 {type(e)}:{e}")
|
||||
if _browser:
|
||||
await _browser.close()
|
||||
return None
|
||||
|
||||
|
||||
async def get_browser(**kwargs) -> Browser:
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
import asyncio
|
||||
from configs.path_config import IMAGE_PATH, FONT_PATH
|
||||
from PIL import Image, ImageFile, ImageDraw, ImageFont, ImageFilter
|
||||
from imagehash import ImageHash
|
||||
from io import BytesIO
|
||||
from matplotlib import pyplot as plt
|
||||
from typing import Tuple, Optional, Union, List, Literal
|
||||
from pathlib import Path
|
||||
from math import ceil
|
||||
import random
|
||||
import cv2
|
||||
import base64
|
||||
import imagehash
|
||||
import random
|
||||
import re
|
||||
from io import BytesIO
|
||||
from math import ceil
|
||||
from pathlib import Path
|
||||
from typing import List, Literal, Optional, Tuple, Union
|
||||
|
||||
import cv2
|
||||
import imagehash
|
||||
from configs.path_config import FONT_PATH, IMAGE_PATH
|
||||
from imagehash import ImageHash
|
||||
from matplotlib import pyplot as plt
|
||||
from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont
|
||||
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
Image.MAX_IMAGE_PIXELS = None
|
||||
@ -65,9 +66,7 @@ def compressed_image(
|
||||
"""
|
||||
in_file = IMAGE_PATH / in_file if isinstance(in_file, str) else in_file
|
||||
if out_file:
|
||||
out_file = (
|
||||
IMAGE_PATH / out_file if isinstance(out_file, str) else out_file
|
||||
)
|
||||
out_file = IMAGE_PATH / out_file if isinstance(out_file, str) else out_file
|
||||
else:
|
||||
out_file = in_file
|
||||
h, w, d = cv2.imread(str(in_file.absolute())).shape
|
||||
@ -673,9 +672,11 @@ class BuildImage:
|
||||
ellipse_box = [0, 0, r2 - 2, r2 - 2]
|
||||
mask = Image.new(
|
||||
size=[int(dim * antialias) for dim in self.markImg.size],
|
||||
mode='L', color='black')
|
||||
mode="L",
|
||||
color="black",
|
||||
)
|
||||
draw = ImageDraw.Draw(mask)
|
||||
for offset, fill in (width / -2.0, 'black'), (width / 2.0, 'white'):
|
||||
for offset, fill in (width / -2.0, "black"), (width / 2.0, "white"):
|
||||
left, top = [(value + offset) * antialias for value in ellipse_box[:2]]
|
||||
right, bottom = [(value - offset) * antialias for value in ellipse_box[2:]]
|
||||
draw.ellipse([left, top, right, bottom], fill=fill)
|
||||
@ -1490,6 +1491,7 @@ async def text2image(
|
||||
height = 0
|
||||
_tmp = BuildImage(0, 0, font_size=font_size)
|
||||
for x in text.split("\n"):
|
||||
x = x if x.strip() else "正"
|
||||
w, h = _tmp.getsize(x)
|
||||
height += h
|
||||
width = width if width > w else w
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
from configs.path_config import IMAGE_PATH, RECORD_PATH
|
||||
from nonebot.adapters.onebot.v11.message import MessageSegment
|
||||
from configs.config import NICKNAME
|
||||
from services.log import logger
|
||||
from typing import Union, List
|
||||
from pathlib import Path
|
||||
import os
|
||||
from typing import List, Union
|
||||
|
||||
from configs.config import NICKNAME
|
||||
from configs.path_config import IMAGE_PATH, RECORD_PATH
|
||||
from nonebot.adapters.onebot.v11.message import MessageSegment, Message
|
||||
from services.log import logger
|
||||
|
||||
|
||||
def image(
|
||||
file: Union[str, Path, bytes] = None,
|
||||
path: str = None,
|
||||
b64: str = None,
|
||||
file: Union[str, Path, bytes] = None,
|
||||
path: str = None,
|
||||
b64: str = None,
|
||||
) -> Union[MessageSegment, str]:
|
||||
"""
|
||||
说明:
|
||||
@ -63,7 +63,9 @@ def record(voice_name: str, path: str = None) -> MessageSegment or str:
|
||||
if len(voice_name.split(".")) == 1:
|
||||
voice_name += ".mp3"
|
||||
file = (
|
||||
Path(RECORD_PATH) / path / voice_name if path else Path(RECORD_PATH) / voice_name
|
||||
Path(RECORD_PATH) / path / voice_name
|
||||
if path
|
||||
else Path(RECORD_PATH) / voice_name
|
||||
)
|
||||
if "http" in voice_name:
|
||||
return MessageSegment.record(voice_name)
|
||||
@ -96,7 +98,7 @@ def contact_user(qq: int) -> MessageSegment:
|
||||
|
||||
|
||||
def share(
|
||||
url: str, title: str, content: str = None, image_url: str = None
|
||||
url: str, title: str, content: str = None, image_url: str = None
|
||||
) -> MessageSegment:
|
||||
"""
|
||||
说明:
|
||||
@ -155,7 +157,7 @@ def music(type_: str, id_: int) -> MessageSegment:
|
||||
|
||||
|
||||
def custom_forward_msg(
|
||||
msg_list: List[str], uin: Union[int, str], name: str = f"这里是{NICKNAME}"
|
||||
msg_list: List[str], uin: Union[int, str], name: str = f"这里是{NICKNAME}"
|
||||
) -> List[dict]:
|
||||
"""
|
||||
生成自定义合并消息
|
||||
@ -176,3 +178,35 @@ def custom_forward_msg(
|
||||
}
|
||||
mes_list.append(data)
|
||||
return mes_list
|
||||
|
||||
|
||||
class MessageBuilder:
|
||||
"""
|
||||
MessageSegment构建工具
|
||||
"""
|
||||
|
||||
def __init__(self, msg: Union[str, MessageSegment, Message]):
|
||||
if msg:
|
||||
if isinstance(msg, str):
|
||||
self._msg = text(msg)
|
||||
else:
|
||||
self._msg = msg
|
||||
else:
|
||||
self._msg = text("")
|
||||
|
||||
def text(self, msg: str):
|
||||
return MessageBuilder(self._msg + text(msg))
|
||||
|
||||
def image(
|
||||
self,
|
||||
file: Union[str, Path, bytes] = None,
|
||||
path: str = None,
|
||||
b64: str = None,
|
||||
):
|
||||
return MessageBuilder(self._msg + image(file, path, b64))
|
||||
|
||||
def at(self, qq: int):
|
||||
return MessageBuilder(self._msg + at(qq))
|
||||
|
||||
def face(self, id_: int):
|
||||
return MessageBuilder(self._msg + face(id_))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user