diff --git a/.vscode/settings.json b/.vscode/settings.json index 0de9c7e..bdb468c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,17 @@ { "cSpell.words": [ + "Alconna", + "apscheduler", + "arclet", + "Arparma", + "cesaa", + "chatdatanum", "chatrecorder", "dialectlist", - "pygal" + "displayname", + "parameterless", + "pygal", + "sqlalchemy", + "userinfo" ] } \ No newline at end of file diff --git a/nonebot_plugin_dialectlist/__init__.py b/nonebot_plugin_dialectlist/__init__.py index 1cacc02..7d62002 100644 --- a/nonebot_plugin_dialectlist/__init__.py +++ b/nonebot_plugin_dialectlist/__init__.py @@ -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今日|昨日|本周|上周|本月|上月|年度|历史)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) diff --git a/nonebot_plugin_dialectlist/config.py b/nonebot_plugin_dialectlist/config.py index 799e913..fb51663 100644 --- a/nonebot_plugin_dialectlist/config.py +++ b/nonebot_plugin_dialectlist/config.py @@ -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 diff --git a/nonebot_plugin_dialectlist/function.py b/nonebot_plugin_dialectlist/function.py deleted file mode 100644 index 09b69c1..0000000 --- a/nonebot_plugin_dialectlist/function.py +++ /dev/null @@ -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 diff --git a/nonebot_plugin_dialectlist/usage.md b/nonebot_plugin_dialectlist/usage.md new file mode 100644 index 0000000..e69de29 diff --git a/nonebot_plugin_dialectlist/utils.py b/nonebot_plugin_dialectlist/utils.py new file mode 100644 index 0000000..54c2a07 --- /dev/null +++ b/nonebot_plugin_dialectlist/utils.py @@ -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 diff --git a/pdm.lock b/pdm.lock index 8c407e9..975a0f5 100644 --- a/pdm.lock +++ b/pdm.lock @@ -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" diff --git a/pyproject.toml b/pyproject.toml index ff4c980..88ddd43 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"