🔥 ♻️ 暂时停止图片支持+依照词云重写

This commit is contained in:
Chen_Xu233 2024-06-18 16:32:17 +08:00
parent 97c6bbb219
commit e6cc909c88
8 changed files with 1020 additions and 444 deletions

12
.vscode/settings.json vendored
View File

@ -1,7 +1,17 @@
{
"cSpell.words": [
"Alconna",
"apscheduler",
"arclet",
"Arparma",
"cesaa",
"chatdatanum",
"chatrecorder",
"dialectlist",
"pygal"
"displayname",
"parameterless",
"pygal",
"sqlalchemy",
"userinfo"
]
}

View File

@ -1,185 +1,265 @@
import re
import time
from typing import Tuple, Union
from datetime import datetime, timedelta
from nonebot import require
from nonebot import on_command, require
require("nonebot_plugin_chatrecorder")
require("nonebot_plugin_apscheduler")
require("nonebot_plugin_userinfo")
require("nonebot_plugin_alconna")
require("nonebot_plugin_cesaa")
import re
import os
import time
import nonebot_plugin_saa as saa
from typing import Tuple, Union, Optional, List
from datetime import datetime, timedelta
from arclet.alconna import ArparmaBehavior
from arclet.alconna.arparma import Arparma
from nonebot import on_command, get_driver
from nonebot.log import logger
from nonebot.params import Command, CommandArg, Arg, Depends
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.adapters.onebot import V11Bot, V12Bot, V11Event, V12Event, V11Message, V12Message # type: ignore
from nonebot import get_driver
from nonebot.adapters import Bot, Event, Message
from nonebot.params import Arg, Depends
from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
from nonebot.typing import T_State
from nonebot_plugin_alconna import (
Alconna,
AlconnaMatch,
AlconnaMatcher,
AlconnaQuery,
Args,
Match,
Option,
Query,
image_fetch,
on_alconna,
store_true,
)
try:
from zoneinfo import ZoneInfo
except ImportError:
from backports.zoneinfo import ZoneInfo # type: ignore
require("nonebot_plugin_chatrecorder")
from nonebot_plugin_chatrecorder import get_message_records
from .function import *
from .config import plugin_config
from nonebot_plugin_userinfo import EventUserInfo, UserInfo, get_user_info
from nonebot_plugin_session import Session, SessionIdType, extract_session
from nonebot_plugin_cesaa import get_messages_plain_text
ranks = on_command(
"群话痨排行榜",
aliases={
"今日群话痨排行榜",
"昨日群话痨排行榜",
"本周群话痨排行榜",
"上周群话痨排行榜",
"本月群话痨排行榜",
"年度群话痨排行榜",
"历史群话痨排行榜",
},
priority=6,
block=True,
# from . import migrations #抄词云的部分代码,还不知道这有什么用
# from .function import *
from .config import Config, plugin_config
from .utils import (
get_datetime_fromisoformat_with_timezone,
get_datetime_now_with_timezone,
got_rank,
msg_counter,
persist_id2user_id,
)
with open(os.path.dirname(__file__) + "/usage.md") as f:
usage = f.read()
__plugin_meta__ = PluginMetadata(
name="B话排行榜",
description="调查群U的B话数量以一定的顺序排序后排序出来。",
usage=usage,
homepage="https://github.com/ChenXu233/nonebot_plugin_dialectlist",
type="application",
supported_adapters=inherit_supported_adapters(
"nonebot_plugin_chatrecorder", "nonebot_plugin_saa", "nonebot_plugin_alconna"
),
config=Config,
# extra={"orm_version_location": migrations},
)
@ranks.handle()
# 抄的词云不过真的很适合B话榜。
class SameTime(ArparmaBehavior):
def operate(self, interface: Arparma):
type = interface.query("type")
time = interface.query("time")
if type is None and time:
interface.behave_fail()
rank_cmd = on_alconna(
Alconna(
"B话榜",
Args["type?", ["今日", "昨日", "本周", "上周", "本月", "上月", "年度", "历史"]][
"time?", str
],
behaviors=[SameTime()],
),
use_cmd_start=True,
)
def wrapper(slot: Union[int, str], content: Optional[str]) -> str:
if slot == "type" and content:
return content
return "" # pragma: no cover
rank_cmd.shortcut(
r"(?P<type>今日|昨日|本周|上周|本月|上月|年度|历史)B话榜",
{
"prefix": True,
"command": "B话榜 ",
"wrapper": wrapper,
"args": ["{type}"],
},
)
def parse_datetime(key: str):
"""解析数字,并将结果存入 state 中"""
async def _key_parser(
matcher: AlconnaMatcher,
state: T_State,
input: Union[datetime, Message] = Arg(key),
):
if isinstance(input, datetime):
return
plaintext = input.extract_plain_text()
try:
state[key] = get_datetime_fromisoformat_with_timezone(plaintext)
except ValueError:
await matcher.reject_arg(key, "请输入正确的日期,不然我没法理解呢!")
return _key_parser
# TODO 处理函数更新
# 参考词云
# 这段函数完全抄的词云
@rank_cmd.handle()
async def _group_message(
matcher: Matcher,
event: Union[
V11Event.GroupMessageEvent,
V12Event.GroupMessageEvent,
V12Event.ChannelMessageEvent,
],
state: T_State,
commands: Tuple[str, ...] = Command(),
args: Union[V11Message, V11Message] = CommandArg(),
type: Optional[str] = None,
time: Optional[str] = None,
):
if isinstance(event, V11Event.GroupMessageEvent):
logger.debug("handle command from onebotV11 adapter(qq)")
elif isinstance(event, V12Event.GroupMessageEvent):
logger.debug("handle command from onebotV12 adapter")
dt = get_datetime_now_with_timezone()
command = commands[0]
if command == "群话痨排行榜":
state["start"] = dt.replace(
year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
)
state["stop"] = dt
elif command == "今日群话痨排行榜":
if not type:
await rank_cmd.finish(__plugin_meta__.usage)
dt = get_datetime_now_with_timezone()
if type == "今日":
state["start"] = dt.replace(hour=0, minute=0, second=0, microsecond=0)
state["stop"] = dt
elif command == "昨日群话痨排行榜":
elif type == "昨日":
state["stop"] = dt.replace(hour=0, minute=0, second=0, microsecond=0)
state["start"] = state["stop"] - timedelta(days=1)
elif command == "前日群话痨排行榜":
state["stop"] = dt.replace(
hour=0, minute=0, second=0, microsecond=0
) - timedelta(days=1)
state["start"] = state["stop"] - timedelta(days=1)
elif command == "本周群话痨排行榜":
elif type == "本周":
state["start"] = dt.replace(
hour=0, minute=0, second=0, microsecond=0
) - timedelta(days=dt.weekday())
state["stop"] = dt
elif command == "上周群话痨排行榜":
state["start"] = dt.replace(
hour=0, minute=0, second=0, microsecond=0
) - timedelta(days=dt.weekday() + 7)
elif type == "上周":
state["stop"] = dt.replace(
hour=0, minute=0, second=0, microsecond=0
) - timedelta(days=dt.weekday())
elif command == "本月群话痨排行榜":
state["start"] = state["stop"] - timedelta(days=7)
elif type == "本月":
state["start"] = dt.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
state["stop"] = dt
elif command == "年度群话痨排行榜":
elif type == "上月":
state["stop"] = dt.replace(
day=1, hour=0, minute=0, second=0, microsecond=0
) - timedelta(microseconds=1)
state["start"] = state["stop"].replace(
day=1, hour=0, minute=0, second=0, microsecond=0
)
elif type == "年度":
state["start"] = dt.replace(
month=1, day=1, hour=0, minute=0, second=0, microsecond=0
)
state["stop"] = dt
elif command == "历史群话痨排行榜":
plaintext = args.extract_plain_text().strip()
match = re.match(r"^(.+?)(?:~(.+))?$", plaintext)
if match:
start = match.group(1)
stop = match.group(2)
try:
state["start"] = get_datetime_fromisoformat_with_timezone(start)
if stop:
state["stop"] = get_datetime_fromisoformat_with_timezone(stop)
else:
# 如果没有指定结束日期,则认为是指查询这一天的数据
state["start"] = state["start"].replace(
hour=0, minute=0, second=0, microsecond=0
)
state["stop"] = state["start"] + timedelta(days=1)
except ValueError:
await matcher.finish("请输入正确的日期,不然我没法理解呢!")
else:
pass
elif type == "历史":
if time:
plaintext = time
if match := re.match(r"^(.+?)(?:~(.+))?$", plaintext):
start = match[1]
stop = match[2]
try:
state["start"] = get_datetime_fromisoformat_with_timezone(start)
if stop:
state["stop"] = get_datetime_fromisoformat_with_timezone(stop)
else:
# 如果没有指定结束日期,则认为是所给日期的当天的词云
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("请输入正确的日期,不然我没法理解呢!")
@ranks.handle()
async def _private_message(
matcher: Matcher,
event: Union[V11Event.PrivateMessageEvent, V12Event.PrivateMessageEvent],
state: T_State,
commands: Tuple[str, ...] = Command(),
args: Union[V11Message, V12Message] = CommandArg(),
):
# TODO:支持私聊的查询
await matcher.finish("暂不支持私聊查询,今后可能会添加这一项功能")
@ranks.got(
@rank_cmd.got(
"start",
prompt="请输入你要查询的起始日期(如 2022-01-01",
parameterless=[Depends(parse_datetime("start"))],
)
@ranks.got(
@rank_cmd.got(
"stop",
prompt="请输入你要查询的结束日期(如 2022-02-22",
parameterless=[Depends(parse_datetime("stop"))],
)
async def handle_message(
matcher: Matcher,
bot: Union[V11Bot, V12Bot],
event: Union[
V11Event.GroupMessageEvent,
V12Event.GroupMessageEvent,
V12Event.ChannelMessageEvent,
],
stop: datetime = Arg(),
async def handle_rank(
bot: Bot,
event: Event,
session: Session = Depends(extract_session),
start: datetime = Arg(),
stop: datetime = Arg(),
):
st = time.time()
if plugin_config.dialectlist_excluded_self:
bot_id = await bot.call_api("get_login_info")
plugin_config.dialectlist_excluded_people.append(bot_id["user_id"])
msg_list = await get_message_records(
bot_ids=[str(bot.self_id)],
platforms=['qq']
if isinstance(event, V11Event.GroupMessageEvent)
else [str(bot.platform)],
group_ids=[str(event.group_id)]
if isinstance(event, (V11Event.GroupMessageEvent, V12Event.GroupMessageEvent))
else None,
guild_ids=[str(event.guild_id)]
if isinstance(event, V12Event.ChannelMessageEvent)
else None,
exclude_user_ids=plugin_config.dialectlist_excluded_people,
time_start=start.astimezone(ZoneInfo("UTC")),
time_stop=stop.astimezone(ZoneInfo("UTC")),
"""生成词云"""
messages = await get_message_records(
session=session,
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,
)
for i in msg_list:
logger.debug(i.plain_text)
if isinstance(event, V11Event.GroupMessageEvent):
processer = V11GroupMsgProcesser(bot=bot, gid=str(event.group_id), msg_list=msg_list) # type: ignore
elif isinstance(event, V12Event.GroupMessageEvent):
processer = V12GroupMsgProcesser(bot=bot, gid=str(event.group_id), msg_list=msg_list) # type: ignore
elif isinstance(event, V12Event.ChannelMessageEvent):
processer = V12GuildMsgProcesser(bot=bot, gid=str(event.guild_id), msg_list=msg_list) # type: ignore
else:
raise NotImplementedError("没支持呢(())")
rank = got_rank(msg_counter(messages))
ids = await persist_id2user_id([int(i[0]) for i in rank])
for i in range(len(rank)):
rank[i][0] = str(ids[i])
msg = await processer.get_send_msg() # type: ignore
await matcher.send(msg)
string: str = ""
nicknames: List = []
for i in rank:
if user_info := await get_user_info(bot, event, user_id=str(i[0])):
(
nicknames.append(user_info.user_displayname)
if user_info.user_displayname
else (
nicknames.append(user_info.user_name)
if user_info.user_name
else nicknames.append(user_info.user_id)
)
)
else:
nicknames.append(None)
logger.debug(nicknames)
for i in range(len(rank)):
index = i + 1
nickname, chatdatanum = nicknames[i], rank[i][1]
str_example = plugin_config.string_format.format(
index=index, nickname=nickname, chatdatanum=chatdatanum
)
string += str_example
await saa.Text(string).finish(reply=True)

View File

@ -1,18 +1,29 @@
from typing import Optional, Literal, List
from nonebot import get_driver
from pydantic import BaseModel, Extra
from nonebot import get_driver, get_plugin_config
from pydantic import BaseModel, field_validator
class Config(BaseModel, extra=Extra.ignore):
timezone: Optional[str]
dialectlist_string_format: str = "{index}名:\n{nickname},{chatdatanum}条消息\n" # 消息格式
dialectlist_get_num: int = 5 # 获取人数数量
dialectlist_visualization: bool = True # 是否可视化
dialectlist_visualization_type: Literal["饼图", "圆环图", "柱状图"] = "圆环图" # 可视化方案
dialectlist_font: str = "SimHei" # 字体格式
dialectlist_excluded_people: List[str] = [] # 排除的人的QQ号
dialectlist_excluded_self: bool = True
class ScopedConfig(BaseModel):
font: str = "SimHei" # 字体格式
get_num: int = 5 # 获取人数数量
timezone: Optional[str] = "Asia/Shanghai"
excluded_self: bool = True
string_format: str = "{index}名:\n{nickname},{chatdatanum}条消息\n" # 消息格式
visualization: bool = True # 是否可视化
excluded_people: List[str] = [] # 排除的人的QQ号
visualization_type: Literal["饼图", "圆环图", "柱状图"] = "圆环图" # 可视化方案
@field_validator("get_num")
@classmethod
def check_priority(cls, v: int) -> int:
if v >= 1:
return v
raise ValueError("表中的人数必须大于一")
class Config(BaseModel):
dialectlist: ScopedConfig = ScopedConfig()
global_config = get_driver().config
plugin_config = Config(**global_config.dict())
plugin_config = get_plugin_config(Config).dialectlist

View File

@ -1,294 +0,0 @@
import abc
import pygal
import unicodedata
import requests
from datetime import datetime
from typing import List, Dict, Union
from pygal.style import Style
from nonebot import require
from nonebot.log import logger
from nonebot.params import Arg
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.adapters import Bot, Message
from nonebot.adapters.onebot import V11Bot, V12Bot, V11Message, V12Message, V11MessageSegment, V12MessageSegment # type: ignore
from nonebot.exception import ActionFailed
try:
from zoneinfo import ZoneInfo
except ImportError:
from backports.zoneinfo import ZoneInfo # type: ignore
require("nonebot_plugin_chatrecorder")
from nonebot_plugin_chatrecorder import get_message_records
from nonebot_plugin_chatrecorder.model import MessageRecord
from .config import plugin_config
style = Style(font_family=plugin_config.dialectlist_font)
def remove_control_characters(string: str) -> str:
"""### 将字符串中的控制符去除
Args:
string (str): 需要去除的字符串
Returns:
(str): 经过处理的字符串
"""
return "".join(ch for ch in string if unicodedata.category(ch)[0] != "C")
def parse_datetime(key: str):
"""解析数字,并将结果存入 state 中"""
async def _key_parser(
matcher: Matcher,
state: T_State,
input: Union[datetime, Union[V11Message, V12Message]] = Arg(key),
):
if isinstance(input, datetime):
return
plaintext = input.extract_plain_text()
try:
state[key] = get_datetime_fromisoformat_with_timezone(plaintext)
except ValueError:
await matcher.reject_arg(key, "请输入正确的日期,不然我没法理解呢!")
return _key_parser
def get_datetime_now_with_timezone() -> datetime:
"""获取当前时间,并包含时区信息"""
if plugin_config.timezone:
return datetime.now(ZoneInfo(plugin_config.timezone))
else:
return datetime.now().astimezone()
def get_datetime_fromisoformat_with_timezone(date_string: str) -> datetime:
"""从 iso8601 格式字符串中获取时间,并包含时区信息"""
if plugin_config.timezone:
return datetime.fromisoformat(date_string).astimezone(
ZoneInfo(plugin_config.timezone)
)
else:
return datetime.fromisoformat(date_string).astimezone()
def msg_counter(msg_list: List[MessageRecord]) -> 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:
try:
lst[i.user_id] += 1
except KeyError:
lst[i.user_id] = 1
logger.debug(lst)
return lst
def got_rank(msg_dict: Dict[str, int]) -> List[List[Union[str, int]]]:
"""### 获得排行榜
Args:
msg_dict (Dict[str,int]): 要处理的字典
Returns:
List[Tuple[str,int]]: 排行榜列表(已排序)
"""
rank = []
while len(rank) < plugin_config.dialectlist_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:
rank.append(["null", 0])
continue
return rank
class MsgProcesser(abc.ABC):
def __init__(self, bot: Bot, gid: str, msg_list: List[MessageRecord]) -> None:
if isinstance(bot, Bot):
self.bot = bot
else:
self.bot = None
self.gid = gid
self.rank = got_rank(msg_counter(msg_list))
@abc.abstractmethod
async def get_nickname_list(self) -> List:
"""
### 获得昵称
#### 抽象原因
要对onebot协议不同版本进行适配
"""
raise NotImplementedError
@abc.abstractmethod
def get_head_portrait_urls(self) -> List:
raise NotImplementedError
@abc.abstractmethod
async def get_send_msg(self) -> Message:
raise NotImplementedError
async def get_msg(self) -> List[Union[str, bytes, None]]:
str_msg = await self.render_template_msg()
pic_msg = None
if plugin_config.dialectlist_visualization:
try:
pic_msg = await self.render_template_pic()
except OSError:
plugin_config.dialectlist_visualization = False
str_msg += "\n\n无法发送可视化图片请检查是否安装GTK+详细安装教程可见github\nhttps://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer \n若不想安装这个软件,再次使用这个指令不会显示这个提示"
return [str_msg, pic_msg]
async def render_template_msg(self) -> str:
"""渲染文字"""
string: str = ""
rank: List = self.rank
nicknames: List = await self.get_nickname_list()
for i in range(len(rank)):
index = i + 1
nickname, chatdatanum = nicknames[i], rank[i][1]
str_example = plugin_config.dialectlist_string_format.format(
index=index, nickname=nickname, chatdatanum=chatdatanum
)
string += str_example
return string
async def render_template_pic(self) -> bytes:
if plugin_config.dialectlist_visualization_type == "圆环图":
view = pygal.Pie(inner_radius=0.6, style=style)
elif plugin_config.dialectlist_visualization_type == "饼图":
view = pygal.Pie(style=style)
else:
view = pygal.Bar(style=style)
view.title = "消息可视化"
for i, j in zip(self.rank, await self.get_nickname_list()): # type: ignore
view.add(str(j), int(i[1]))
png: bytes = view.render_to_png() # type: ignore
self.img = png
return png
class V11GroupMsgProcesser(MsgProcesser):
def __init__(self, bot: V11Bot, gid: str, msg_list: List[MessageRecord]) -> None:
super().__init__(bot, gid, msg_list)
self.bot = bot
async def get_nickname_list(self) -> List:
nicknames = []
for i in range(len(self.rank)):
try:
member_info = await self.bot.get_group_member_info(
group_id=int(self.gid), user_id=int(self.rank[i][0]), no_cache=True
)
nickname=(
member_info["nickname"]
if not member_info["card"]
else member_info["card"]
)
nicknames.append(remove_control_characters(nickname))
except (ActionFailed,ValueError) as e:
nicknames.append("{}这家伙不在群里了".format(self.rank[i][0]))
return nicknames
def get_head_portrait_urls(self) -> List:
self.portrait_urls = [
"http://q2.qlogo.cn/headimg_dl?dst_uin={}&spec=640".format(i[0])
for i in self.rank
]
return self.portrait_urls
async def get_send_msg(self) -> V11Message:
msgs: List = await self.get_msg()
msg = V11Message()
msg += V11MessageSegment.text(msgs[0]) # type: ignore
msg += V11MessageSegment.image(msgs[1]) # type: ignore
return msg
class V12MsgProcesser(MsgProcesser):
def __init__(self, bot: V12Bot, gid: str, msg_list: List[MessageRecord]) -> None:
super().__init__(bot, gid, msg_list)
self.bot = bot
async def get_send_msg(self) -> V12Message:
msgs: List = await self.get_msg()
msg = V12Message()
msg += V12MessageSegment.text(msgs[0]) # type: ignore
msg += V12MessageSegment.image(msgs[1]) # type: ignore
return msg
def get_head_portrait_urls(self) -> List:
return super().get_head_portrait_urls()
class V12GroupMsgProcesser(V12MsgProcesser):
def __init__(self, bot: V12Bot, gid: str, msg_list: List[MessageRecord]) -> None:
super().__init__(bot, gid, msg_list)
async def get_nickname_list(self) -> List:
nicknames = []
for i in range(len(self.rank)):
try:
member_info = await self.bot.get_group_member_info(
group_id=str(self.gid), user_id=str(self.rank[i][0]), no_cache=True
)
nickname=(
member_info["user_displayname"]
if member_info["user_displayname"]
else member_info["user_name"]
)
nicknames.append(remove_control_characters(nickname))
except ActionFailed as e:
nicknames.append("{}这家伙不在群里了".format(self.rank[i][0]))
return nicknames
class V12GuildMsgProcesser(V12MsgProcesser):
def __init__(self, bot: V12Bot, gid: str, msg_list: List[MessageRecord]) -> None:
super().__init__(bot, gid, msg_list)
async def get_nickname_list(self) -> List:
nicknames = []
for i in range(len(self.rank)):
try:
member_info = await self.bot.get_guild_member_info(
guild_id=str(self.gid), user_id=str(self.rank[i][0]), no_cache=True
)
nickname=(
member_info["user_displayname"]
if member_info["user_displayname"]
else member_info["user_name"]
)
nicknames.append(remove_control_characters(nickname))
except ActionFailed as e:
nicknames.append("{}这家伙不在群里了".format(self.rank[i][0]))
return nicknames

View File

View File

@ -0,0 +1,442 @@
import contextlib
from datetime import datetime, time, tzinfo
from typing import Optional, Dict, List, Union
from zoneinfo import ZoneInfo
from sqlalchemy import or_, select
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.permission import SUPERUSER
from nonebot_plugin_orm import get_session
from nonebot_plugin_saa import PlatformTarget, get_target
from nonebot_plugin_session import Session, SessionLevel, extract_session
from nonebot_plugin_session_orm import SessionModel
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
from nonebot_plugin_apscheduler import scheduler
from nonebot_plugin_chatrecorder import MessageRecord
from .config import plugin_config
def get_datetime_now_with_timezone() -> datetime:
"""获取当前时间,并包含时区信息"""
if plugin_config.timezone:
return datetime.now(ZoneInfo(plugin_config.timezone))
else:
return datetime.now().astimezone()
def get_datetime_fromisoformat_with_timezone(date_string: str) -> datetime:
"""从 ISO-8601 格式字符串中获取时间,并包含时区信息"""
if not plugin_config.timezone:
return datetime.fromisoformat(date_string).astimezone()
raw = datetime.fromisoformat(date_string)
return (
raw.astimezone(ZoneInfo(plugin_config.timezone))
if raw.tzinfo
else raw.replace(tzinfo=ZoneInfo(plugin_config.timezone))
)
def time_astimezone(time: time, tz: Optional[tzinfo] = None) -> time:
"""将 time 对象转换为指定时区的 time 对象
如果 tz None则转换为本地时区
"""
local_time = datetime.combine(datetime.today(), time)
return local_time.astimezone(tz).timetz()
def get_time_fromisoformat_with_timezone(time_string: str) -> time:
"""从 iso8601 格式字符串中获取时间,并包含时区信息"""
if not plugin_config.timezone:
return time_astimezone(time.fromisoformat(time_string))
raw = time.fromisoformat(time_string)
return (
time_astimezone(raw, ZoneInfo(plugin_config.timezone))
if raw.tzinfo
else raw.replace(tzinfo=ZoneInfo(plugin_config.timezone))
)
def get_time_with_scheduler_timezone(time: time) -> time:
"""获取转换到 APScheduler 时区的时间"""
return time_astimezone(time, scheduler.timezone)
# 暂时不做考虑
# def admin_permission():
# permission = SUPERUSER
# with contextlib.suppress(ImportError):
# from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER
# permission = permission | GROUP_ADMIN | GROUP_OWNER
# return permission
async def ensure_group(matcher: Matcher, session: Session = Depends(extract_session)):
"""确保在群组中使用"""
if session.level not in [SessionLevel.LEVEL2, SessionLevel.LEVEL3]:
await matcher.finish("请在群组中使用!")
async def persist_id2user_id(ids: List) -> 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.id1 for i in records]
def msg_counter(msg_list: List[MessageRecord]) -> 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(i.session_persist_id)
try:
lst[str(i.session_persist_id)] += 1
except KeyError:
lst[str(i.session_persist_id)] = 1
logger.debug(lst)
return lst
def got_rank(msg_dict: Dict[str, int]) -> List[List[Union[str, int]]]:
"""### 获得排行榜
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:
break
return rank
# import abc
# import pygal
# import unicodedata
# import requests
# from datetime import datetime
# from typing import List, Dict, Union
# from pygal.style import Style
# from nonebot import require
# from nonebot.log import logger
# from nonebot.params import Arg
# from nonebot.typing import T_State
# from nonebot.matcher import Matcher
# from nonebot.adapters import Bot, Message
# from nonebot.adapters.onebot import V11Bot, V12Bot, V11Message, V12Message, V11MessageSegment, V12MessageSegment # type: ignore
# from nonebot.exception import ActionFailed
# try:
# from zoneinfo import ZoneInfo
# except ImportError:
# from backports.zoneinfo import ZoneInfo # type: ignore
# require("nonebot_plugin_chatrecorder")
# from nonebot_plugin_chatrecorder import get_message_records
# from nonebot_plugin_chatrecorder.model import MessageRecord
# from .config import plugin_config
# style = Style(font_family=plugin_config.dialectlist_font)
# def remove_control_characters(string: str) -> str:
# """### 将字符串中的控制符去除
# Args:
# string (str): 需要去除的字符串
# Returns:
# (str): 经过处理的字符串
# """
# return "".join(ch for ch in string if unicodedata.category(ch)[0] != "C")
# def parse_datetime(key: str):
# """解析数字,并将结果存入 state 中"""
# async def _key_parser(
# matcher: Matcher,
# state: T_State,
# input: Union[datetime, Union[V11Message, V12Message]] = Arg(key),
# ):
# if isinstance(input, datetime):
# return
# plaintext = input.extract_plain_text()
# try:
# state[key] = get_datetime_fromisoformat_with_timezone(plaintext)
# except ValueError:
# await matcher.reject_arg(key, "请输入正确的日期,不然我没法理解呢!")
# return _key_parser
# def get_datetime_now_with_timezone() -> datetime:
# """获取当前时间,并包含时区信息"""
# if plugin_config.timezone:
# return datetime.now(ZoneInfo(plugin_config.timezone))
# else:
# return datetime.now().astimezone()
# def get_datetime_fromisoformat_with_timezone(date_string: str) -> datetime:
# """从 iso8601 格式字符串中获取时间,并包含时区信息"""
# if plugin_config.timezone:
# return datetime.fromisoformat(date_string).astimezone(
# ZoneInfo(plugin_config.timezone)
# )
# else:
# return datetime.fromisoformat(date_string).astimezone()
# def msg_counter(msg_list: List[MessageRecord]) -> 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:
# try:
# lst[i.user_id] += 1
# except KeyError:
# lst[i.user_id] = 1
# logger.debug(lst)
# return lst
# def got_rank(msg_dict: Dict[str, int]) -> List[List[Union[str, int]]]:
# """### 获得排行榜
# Args:
# msg_dict (Dict[str,int]): 要处理的字典
# Returns:
# List[Tuple[str,int]]: 排行榜列表(已排序)
# """
# rank = []
# while len(rank) < plugin_config.dialectlist_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:
# rank.append(["null", 0])
# continue
# return rank
# class MsgProcesser(abc.ABC):
# def __init__(self, bot: Bot, gid: str, msg_list: List[MessageRecord]) -> None:
# if isinstance(bot, Bot):
# self.bot = bot
# else:
# self.bot = None
# self.gid = gid
# self.rank = got_rank(msg_counter(msg_list))
# @abc.abstractmethod
# async def get_nickname_list(self) -> List:
# """
# ### 获得昵称
# #### 抽象原因
# 要对onebot协议不同版本进行适配
# """
# raise NotImplementedError
# @abc.abstractmethod
# def get_head_portrait_urls(self) -> List:
# raise NotImplementedError
# @abc.abstractmethod
# async def get_send_msg(self) -> Message:
# raise NotImplementedError
# async def get_msg(self) -> List[Union[str, bytes, None]]:
# str_msg = await self.render_template_msg()
# pic_msg = None
# if plugin_config.dialectlist_visualization:
# try:
# pic_msg = await self.render_template_pic()
# except OSError:
# plugin_config.dialectlist_visualization = False
# str_msg += "\n\n无法发送可视化图片请检查是否安装GTK+详细安装教程可见github\nhttps://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer \n若不想安装这个软件再次使用这个指令不会显示这个提示"
# return [str_msg, pic_msg]
# async def render_template_msg(self) -> str:
# """渲染文字"""
# string: str = ""
# rank: List = self.rank
# nicknames: List = await self.get_nickname_list()
# for i in range(len(rank)):
# index = i + 1
# nickname, chatdatanum = nicknames[i], rank[i][1]
# str_example = plugin_config.dialectlist_string_format.format(
# index=index, nickname=nickname, chatdatanum=chatdatanum
# )
# string += str_example
# return string
# async def render_template_pic(self) -> bytes:
# if plugin_config.dialectlist_visualization_type == "圆环图":
# view = pygal.Pie(inner_radius=0.6, style=style)
# elif plugin_config.dialectlist_visualization_type == "饼图":
# view = pygal.Pie(style=style)
# else:
# view = pygal.Bar(style=style)
# view.title = "消息可视化"
# for i, j in zip(self.rank, await self.get_nickname_list()): # type: ignore
# view.add(str(j), int(i[1]))
# png: bytes = view.render_to_png() # type: ignore
# self.img = png
# return png
# class V11GroupMsgProcesser(MsgProcesser):
# def __init__(self, bot: V11Bot, gid: str, msg_list: List[MessageRecord]) -> None:
# super().__init__(bot, gid, msg_list)
# self.bot = bot
# async def get_nickname_list(self) -> List:
# nicknames = []
# for i in range(len(self.rank)):
# try:
# member_info = await self.bot.get_group_member_info(
# group_id=int(self.gid), user_id=int(self.rank[i][0]), no_cache=True
# )
# nickname=(
# member_info["nickname"]
# if not member_info["card"]
# else member_info["card"]
# )
# nicknames.append(remove_control_characters(nickname))
# except (ActionFailed,ValueError) as e:
# nicknames.append("{}这家伙不在群里了".format(self.rank[i][0]))
# return nicknames
# def get_head_portrait_urls(self) -> List:
# self.portrait_urls = [
# "http://q2.qlogo.cn/headimg_dl?dst_uin={}&spec=640".format(i[0])
# for i in self.rank
# ]
# return self.portrait_urls
# async def get_send_msg(self) -> V11Message:
# msgs: List = await self.get_msg()
# msg = V11Message()
# msg += V11MessageSegment.text(msgs[0]) # type: ignore
# msg += V11MessageSegment.image(msgs[1]) # type: ignore
# return msg
# class V12MsgProcesser(MsgProcesser):
# def __init__(self, bot: V12Bot, gid: str, msg_list: List[MessageRecord]) -> None:
# super().__init__(bot, gid, msg_list)
# self.bot = bot
# async def get_send_msg(self) -> V12Message:
# msgs: List = await self.get_msg()
# msg = V12Message()
# msg += V12MessageSegment.text(msgs[0]) # type: ignore
# msg += V12MessageSegment.image(msgs[1]) # type: ignore
# return msg
# def get_head_portrait_urls(self) -> List:
# return super().get_head_portrait_urls()
# class V12GroupMsgProcesser(V12MsgProcesser):
# def __init__(self, bot: V12Bot, gid: str, msg_list: List[MessageRecord]) -> None:
# super().__init__(bot, gid, msg_list)
# async def get_nickname_list(self) -> List:
# nicknames = []
# for i in range(len(self.rank)):
# try:
# member_info = await self.bot.get_group_member_info(
# group_id=str(self.gid), user_id=str(self.rank[i][0]), no_cache=True
# )
# nickname=(
# member_info["user_displayname"]
# if member_info["user_displayname"]
# else member_info["user_name"]
# )
# nicknames.append(remove_control_characters(nickname))
# except ActionFailed as e:
# nicknames.append("{}这家伙不在群里了".format(self.rank[i][0]))
# return nicknames
# class V12GuildMsgProcesser(V12MsgProcesser):
# def __init__(self, bot: V12Bot, gid: str, msg_list: List[MessageRecord]) -> None:
# super().__init__(bot, gid, msg_list)
# async def get_nickname_list(self) -> List:
# nicknames = []
# for i in range(len(self.rank)):
# try:
# member_info = await self.bot.get_guild_member_info(
# guild_id=str(self.gid), user_id=str(self.rank[i][0]), no_cache=True
# )
# nickname=(
# member_info["user_displayname"]
# if member_info["user_displayname"]
# else member_info["user_name"]
# )
# nicknames.append(remove_control_characters(nickname))
# except ActionFailed as e:
# nicknames.append("{}这家伙不在群里了".format(self.rank[i][0]))
# return nicknames

316
pdm.lock
View File

@ -2,10 +2,10 @@
# It is not intended for manual editing.
[metadata]
groups = ["default"]
groups = ["default", "dev"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.1"
content_hash = "sha256:24ead5cb6a13a6971197f92131e83f11ae3f8897fe5d7133ced7fa0533258ae4"
content_hash = "sha256:8ea64a585f788e2d455dd3c08f7252ac05328ad42e8d010eb357ddc1aed96869"
[[package]]
name = "alembic"
@ -51,6 +51,64 @@ files = [
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
]
[[package]]
name = "apscheduler"
version = "3.10.4"
requires_python = ">=3.6"
summary = "In-process task scheduler with Cron-like capabilities"
groups = ["default"]
dependencies = [
"pytz",
"six>=1.4.0",
"tzlocal!=3.*,>=2.0",
]
files = [
{file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"},
{file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"},
]
[[package]]
name = "arclet-alconna"
version = "1.8.15"
requires_python = ">=3.8"
summary = "A High-performance, Generality, Humane Command Line Arguments Parser Library."
groups = ["default"]
dependencies = [
"nepattern<1.0.0,>=0.7.3",
"tarina>=0.5.0",
"typing-extensions>=4.5.0",
]
files = [
{file = "arclet_alconna-1.8.15-py3-none-any.whl", hash = "sha256:9cc010e42f00a5201439318df3b2000234dce27fdc90d26813c3a295125e54e8"},
{file = "arclet_alconna-1.8.15.tar.gz", hash = "sha256:713a57bdfed3c3959600384e46358154ce33eec012615554beb5634d436507bc"},
]
[[package]]
name = "arclet-alconna-tools"
version = "0.7.6"
requires_python = ">=3.8"
summary = "Builtin Tools for Alconna"
groups = ["default"]
dependencies = [
"arclet-alconna>=1.8.15",
"nepattern<1.0.0,>=0.7.3",
]
files = [
{file = "arclet_alconna_tools-0.7.6-py3-none-any.whl", hash = "sha256:fdd1cb900603ce6bb00295bf7bf7f60dfdb764f0614abe248cdcb754e5149edd"},
{file = "arclet_alconna_tools-0.7.6.tar.gz", hash = "sha256:7cb7dc54c1c2198529c63227739423401051b8489374f1a7a3efa0c4e70b2a22"},
]
[[package]]
name = "cachetools"
version = "5.3.3"
requires_python = ">=3.7"
summary = "Extensible memoizing collections and decorators"
groups = ["default"]
files = [
{file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"},
{file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"},
]
[[package]]
name = "certifi"
version = "2024.6.2"
@ -170,6 +228,20 @@ files = [
{file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"},
]
[[package]]
name = "emoji"
version = "2.12.1"
requires_python = ">=3.7"
summary = "Emoji for Python"
groups = ["default"]
dependencies = [
"typing-extensions>=4.7.0",
]
files = [
{file = "emoji-2.12.1-py3-none-any.whl", hash = "sha256:a00d62173bdadc2510967a381810101624a2f0986145b8da0cffa42e29430235"},
{file = "emoji-2.12.1.tar.gz", hash = "sha256:4aa0488817691aa58d83764b6c209f8a27c0b3ab3f89d1b8dceca1a62e4973eb"},
]
[[package]]
name = "exceptiongroup"
version = "1.2.1"
@ -220,6 +292,16 @@ files = [
{file = "fastapi_cli-0.0.4.tar.gz", hash = "sha256:e2e9ffaffc1f7767f488d6da34b6f5a377751c996f397902eb6abb99a67bde32"},
]
[[package]]
name = "filetype"
version = "1.2.0"
summary = "Infer file type and MIME type of any file/buffer. No external dependencies."
groups = ["default"]
files = [
{file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"},
{file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"},
]
[[package]]
name = "greenlet"
version = "3.0.3"
@ -534,6 +616,72 @@ files = [
{file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"},
]
[[package]]
name = "nepattern"
version = "0.7.4"
requires_python = ">=3.8"
summary = "a complex pattern, support typing"
groups = ["default"]
dependencies = [
"tarina>=0.5.1",
"typing-extensions>=4.5.0",
]
files = [
{file = "nepattern-0.7.4-py3-none-any.whl", hash = "sha256:ad7287ee2ff46f010b8c758bf9ed8fd8141aa1afce29c5d5a4f94cc85d277e6e"},
{file = "nepattern-0.7.4.tar.gz", hash = "sha256:255a042b45e9d2b04f3c2d73b81912c6b856fae1a10a6e4df30b08ed892d2f9c"},
]
[[package]]
name = "nonebot-plugin-alconna"
version = "0.46.6"
requires_python = ">=3.9"
summary = "Alconna Adapter for Nonebot"
groups = ["default"]
dependencies = [
"arclet-alconna-tools>=0.7.6",
"arclet-alconna>=1.8.15",
"importlib-metadata>=4.13.0",
"nepattern>=0.7.4",
"nonebot-plugin-waiter>=0.6.0",
"nonebot2>=2.3.0",
]
files = [
{file = "nonebot_plugin_alconna-0.46.6-py3-none-any.whl", hash = "sha256:1fc6ccd47b589b7edc342b241e76a877c36328f85a2f97c120b96d23e16fbbb7"},
{file = "nonebot_plugin_alconna-0.46.6.tar.gz", hash = "sha256:f0161aa7ca055b74b4a23c828def6f77942ab852a0e22ae477520b8dd0ef03a9"},
]
[[package]]
name = "nonebot-plugin-apscheduler"
version = "0.4.0"
requires_python = ">=3.8,<4.0"
summary = "APScheduler Support for NoneBot2"
groups = ["default"]
dependencies = [
"apscheduler<4.0.0,>=3.7.0",
"nonebot2<3.0.0,>=2.2.0",
"pydantic!=2.5.0,!=2.5.1,<3.0.0,>=1.10.0",
]
files = [
{file = "nonebot_plugin_apscheduler-0.4.0-py3-none-any.whl", hash = "sha256:f01bb418a5ecf9f04dcadbbc2ff5ba565a48177eb0a758c8c46b13048ac5680c"},
{file = "nonebot_plugin_apscheduler-0.4.0.tar.gz", hash = "sha256:ba91e68809a38e6dbe28906366d47f37f754ded360944b938cd5ac62029a0eb6"},
]
[[package]]
name = "nonebot-plugin-cesaa"
version = "0.4.0"
requires_python = ">=3.8,<4.0"
summary = "为 chatrecorder 添加 send-anything-anywhere 的 PlatformTarget 支持"
groups = ["default"]
dependencies = [
"nonebot-plugin-chatrecorder<1.0.0,>=0.6.0",
"nonebot-plugin-send-anything-anywhere<0.7.0,>=0.6.0",
"nonebot2<3.0.0,>=2.2.0",
]
files = [
{file = "nonebot_plugin_cesaa-0.4.0-py3-none-any.whl", hash = "sha256:4ae34a7d5f023f614f3d3a7207e35f706ae964a203aa582b59f3e0b530a69360"},
{file = "nonebot_plugin_cesaa-0.4.0.tar.gz", hash = "sha256:572a5232262a8d3660a00a32f82041f791587e588046d14b0780fc7d908f1263"},
]
[[package]]
name = "nonebot-plugin-chatrecorder"
version = "0.6.0"
@ -588,6 +736,24 @@ files = [
{file = "nonebot_plugin_orm-0.7.3.tar.gz", hash = "sha256:cbb573598d0ecef2d0e75b5bdebd05297c68a2b368029a8763660f2a45381a2c"},
]
[[package]]
name = "nonebot-plugin-send-anything-anywhere"
version = "0.6.1"
requires_python = "<4.0,>=3.8"
summary = "An adaptor for nonebot2 adaptors"
groups = ["default"]
dependencies = [
"anyio<5.0.0,>=3.3.0",
"filetype<2.0.0,>=1.2.0",
"nonebot2<3.0.0,>=2.0.0",
"pydantic!=2.5.0,!=2.5.1,<3.0.0,>=1.10.0",
"strenum<0.5.0,>=0.4.8",
]
files = [
{file = "nonebot_plugin_send_anything_anywhere-0.6.1-py3-none-any.whl", hash = "sha256:d1ac0df520f950b61ff27d3abda39c4bb2023797f3b7df2a23517ad53f3a7f29"},
{file = "nonebot_plugin_send_anything_anywhere-0.6.1.tar.gz", hash = "sha256:89a695c5e356a423b8641f79082353b50ee431585988de2c2ae77f8ea00ac3e9"},
]
[[package]]
name = "nonebot-plugin-session"
version = "0.3.1"
@ -619,6 +785,38 @@ files = [
{file = "nonebot_plugin_session_orm-0.2.0.tar.gz", hash = "sha256:420e210898a3f348cebbb4ea0816bab66f3299c7b0c01e929a01967d60cc438c"},
]
[[package]]
name = "nonebot-plugin-userinfo"
version = "0.2.4"
requires_python = "<4.0,>=3.8"
summary = "Nonebot2 用户信息获取插件"
groups = ["default"]
dependencies = [
"cachetools<6.0.0,>=5.0.0",
"emoji<3.0.0,>=2.0.0",
"httpx<1.0.0,>=0.20.0",
"nonebot2<3.0.0,>=2.0.0",
"strenum<0.5.0,>=0.4.8",
]
files = [
{file = "nonebot_plugin_userinfo-0.2.4-py3-none-any.whl", hash = "sha256:f08dac58759b859f8bf1d1c17e96cdcee92b10613631a430405364a40181f9e4"},
{file = "nonebot_plugin_userinfo-0.2.4.tar.gz", hash = "sha256:1d49ff00ce38c856be4388fc2a954656f07cc529ce38ef9593e3a0ea40f26b6a"},
]
[[package]]
name = "nonebot-plugin-waiter"
version = "0.6.2"
requires_python = ">=3.9"
summary = "An alternative for got-and-reject in Nonebot"
groups = ["default"]
dependencies = [
"nonebot2>=2.3.0",
]
files = [
{file = "nonebot_plugin_waiter-0.6.2-py3-none-any.whl", hash = "sha256:599251f02d074ab7142e2144f68d63f41104bddcd359051f63979a8163538eac"},
{file = "nonebot_plugin_waiter-0.6.2.tar.gz", hash = "sha256:02017a1613d1273be3535b28a4d8e823f2dbaba26fab524bc8207f50a25ec8e4"},
]
[[package]]
name = "nonebot2"
version = "2.3.1"
@ -833,6 +1031,16 @@ files = [
{file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"},
]
[[package]]
name = "pytz"
version = "2024.1"
summary = "World timezone definitions, modern and historical"
groups = ["default"]
files = [
{file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
{file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
]
[[package]]
name = "pyyaml"
version = "6.0.1"
@ -898,6 +1106,32 @@ files = [
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
]
[[package]]
name = "ruff"
version = "0.4.9"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["dev"]
files = [
{file = "ruff-0.4.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b262ed08d036ebe162123170b35703aaf9daffecb698cd367a8d585157732991"},
{file = "ruff-0.4.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:98ec2775fd2d856dc405635e5ee4ff177920f2141b8e2d9eb5bd6efd50e80317"},
{file = "ruff-0.4.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4555056049d46d8a381f746680db1c46e67ac3b00d714606304077682832998e"},
{file = "ruff-0.4.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e91175fbe48f8a2174c9aad70438fe9cb0a5732c4159b2a10a3565fea2d94cde"},
{file = "ruff-0.4.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8e7b95673f22e0efd3571fb5b0cf71a5eaaa3cc8a776584f3b2cc878e46bff"},
{file = "ruff-0.4.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2d45ddc6d82e1190ea737341326ecbc9a61447ba331b0a8962869fcada758505"},
{file = "ruff-0.4.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78de3fdb95c4af084087628132336772b1c5044f6e710739d440fc0bccf4d321"},
{file = "ruff-0.4.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06b60f91bfa5514bb689b500a25ba48e897d18fea14dce14b48a0c40d1635893"},
{file = "ruff-0.4.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88bffe9c6a454bf8529f9ab9091c99490578a593cc9f9822b7fc065ee0712a06"},
{file = "ruff-0.4.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:673bddb893f21ab47a8334c8e0ea7fd6598ecc8e698da75bcd12a7b9d0a3206e"},
{file = "ruff-0.4.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8c1aff58c31948cc66d0b22951aa19edb5af0a3af40c936340cd32a8b1ab7438"},
{file = "ruff-0.4.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:784d3ec9bd6493c3b720a0b76f741e6c2d7d44f6b2be87f5eef1ae8cc1d54c84"},
{file = "ruff-0.4.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:732dd550bfa5d85af8c3c6cbc47ba5b67c6aed8a89e2f011b908fc88f87649db"},
{file = "ruff-0.4.9-py3-none-win32.whl", hash = "sha256:8064590fd1a50dcf4909c268b0e7c2498253273309ad3d97e4a752bb9df4f521"},
{file = "ruff-0.4.9-py3-none-win_amd64.whl", hash = "sha256:e0a22c4157e53d006530c902107c7f550b9233e9706313ab57b892d7197d8e52"},
{file = "ruff-0.4.9-py3-none-win_arm64.whl", hash = "sha256:5d5460f789ccf4efd43f265a58538a2c24dbce15dbf560676e430375f20a8198"},
{file = "ruff-0.4.9.tar.gz", hash = "sha256:f1cb0828ac9533ba0135d148d214e284711ede33640465e706772645483427e3"},
]
[[package]]
name = "shellingham"
version = "1.5.4"
@ -909,6 +1143,17 @@ files = [
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
]
[[package]]
name = "six"
version = "1.16.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
summary = "Python 2 and 3 compatibility utilities"
groups = ["default"]
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "sniffio"
version = "1.3.1"
@ -983,6 +1228,47 @@ files = [
{file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"},
]
[[package]]
name = "tarina"
version = "0.5.2"
requires_python = ">=3.8"
summary = "A collection of common utils for Arclet"
groups = ["default"]
dependencies = [
"typing-extensions>=4.4.0",
]
files = [
{file = "tarina-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0ceb642f8b08649afb84b7b5419ac20a20e4b5cb3ccf70adadfbf677219d5c34"},
{file = "tarina-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90ab4fc0b64ddca3544a4f80f5747b725576e7e5b9723d375691e0acdcfc7317"},
{file = "tarina-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5c08bbfeae78f5f97971c4f3a6af18dd0c56d20379dd1302632180dbbc27b6ad"},
{file = "tarina-0.5.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d84de2660b05ce6a5195e3b9cb451aa69c530e0c5e6d2672b702f57062f8e7c"},
{file = "tarina-0.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3de93f3ad255e35b5228c926316d06f58a77752be5ad5bd3f52ad4781baa2c11"},
{file = "tarina-0.5.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:204d3229d1fa20d235f05054622b23b3d2b43010e882e401f844a950a65edfe7"},
{file = "tarina-0.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11fb54d0d6f1692606bacac197f7fdb151dd7b2cf6365efceced1c63dd308ad8"},
{file = "tarina-0.5.2-cp310-cp310-win32.whl", hash = "sha256:0142e3ad8c1b85d4b6aeaee36f2102cf891db05efeccc7f445fbba86c84defd5"},
{file = "tarina-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:16d712303b1c5431812bcc85a6ad6801dafd2a9cb125207f01316dca4bf19dfb"},
{file = "tarina-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6452c02fc1d3503336b70aaeeffc978c46e1ac69ad32124a85b135690be84aa1"},
{file = "tarina-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:25c0c7551ce603cfd31fbb3ded86b2d357f59db2735279e60486a45d06bfa3e0"},
{file = "tarina-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3909e0175bab0ee88ef908bdc1df91fdcd39181833d25cb820876436126c7a2a"},
{file = "tarina-0.5.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1faceea384a150c958471a1c86676b96e7543b60f0e66196777f0c0ebb6207a"},
{file = "tarina-0.5.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce9fafc4da64e692e11eb75da65906387b86123acd190f0be5176d91f0c523a2"},
{file = "tarina-0.5.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:deb0848058843104727b8bb210d3b004d8aa4dcbc48e31abd65881eaa02a3a22"},
{file = "tarina-0.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e07c227ae43482bc738532400840ae718c259e585903094fa758fec258ccd6e"},
{file = "tarina-0.5.2-cp311-cp311-win32.whl", hash = "sha256:bd775ac25ffce24f820e071786ceebab61fc7c2cc3102f43fabd664e2ce808dd"},
{file = "tarina-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ff7b0774233ea031a6192bcfca8cea3f01fb9d206447719e0764609ccbac243"},
{file = "tarina-0.5.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2955d68597e319e7ba1f009d333f75ea13e7eb11050044e33ac6b3970eb4ce20"},
{file = "tarina-0.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:40adb676c0649fa666ee2522d546183fb27b3448b3b222ceeb57c0d164b6902a"},
{file = "tarina-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9e959d1e5e2203beab515bdf540d21f2c744c7e661515f2ad130f2a2f0612ace"},
{file = "tarina-0.5.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84c4ffca7f56c464f209fb3bd828cb4f23de54625239a5fd45a6fb3d003e8f1c"},
{file = "tarina-0.5.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:900a41de49dc0af1f05906248c1331818fe685003149e9acbcc815b47efeffc8"},
{file = "tarina-0.5.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8ba80ee60ff18dd23431024b8708021522608e08908f882ed9521fffc3df1b4d"},
{file = "tarina-0.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:959c38a97cf64d10764525f8aa1212f464213212a49a10363dff1989ad18b3cb"},
{file = "tarina-0.5.2-cp312-cp312-win32.whl", hash = "sha256:4aa94f80d8442ff246289702994c12476dcbdeca3d0d2b7924af4c3e6929eb7f"},
{file = "tarina-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:56f898f938e33b1b06133d39e7f3e9016e94c56358870c27c9f6cf0d56e66797"},
{file = "tarina-0.5.2-py3-none-any.whl", hash = "sha256:a1a83f355b3725efcf5a1b24c8808508af1dd154528aae03263a9e04a5a3c2a3"},
{file = "tarina-0.5.2.tar.gz", hash = "sha256:7d5d93d73422e97b2409e6a43bf4d11296fe2dac90a5b4dcbc19e56bc1b55298"},
]
[[package]]
name = "tomli"
version = "2.0.1"
@ -1023,6 +1309,32 @@ files = [
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "tzdata"
version = "2024.1"
requires_python = ">=2"
summary = "Provider of IANA time zone data"
groups = ["default"]
marker = "platform_system == \"Windows\""
files = [
{file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
{file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
]
[[package]]
name = "tzlocal"
version = "5.2"
requires_python = ">=3.8"
summary = "tzinfo object for the local timezone"
groups = ["default"]
dependencies = [
"tzdata; platform_system == \"Windows\"",
]
files = [
{file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"},
{file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"},
]
[[package]]
name = "ujson"
version = "5.10.0"

View File

@ -10,11 +10,26 @@ dependencies = [
"nonebot-plugin-chatrecorder>=0.6.0",
"pygal>=3.0.4",
"requests>=2.32.3",
"nonebot-plugin-apscheduler>=0.4.0",
"nonebot-plugin-alconna>=0.46.6",
"nonebot-plugin-cesaa>=0.4.0",
"nonebot-plugin-userinfo>=0.2.4",
]
requires-python = ">=3.10,<4.0"
readme = "README.md"
license = {text = "MIT"}
[project.optional-dependencies]
dev = [
"ruff>=0.4.9",
]
[tool.pdm]
distribution = false
[tool.ruff]
line-length = 80
[tool.ruff.format]
quote-style = "single"
indent-style = "tab"