From 5d84a504448506cd8f2d353d7ebe2e7831ac6aef Mon Sep 17 00:00:00 2001 From: meetwq Date: Tue, 13 Aug 2024 22:52:02 +0800 Subject: [PATCH] add statistics and plot --- nonebot_plugin_memes/config.py | 3 +- nonebot_plugin_memes/manager.py | 1 - nonebot_plugin_memes/matchers/__init__.py | 2 +- nonebot_plugin_memes/matchers/help.py | 11 +- nonebot_plugin_memes/matchers/statistics.py | 218 ++++++++++++++++++++ nonebot_plugin_memes/plot.py | 76 +++++++ nonebot_plugin_memes/recorder.py | 91 +++++++- nonebot_plugin_memes/utils.py | 17 ++ poetry.lock | 93 +++++---- pyproject.toml | 2 + 10 files changed, 457 insertions(+), 57 deletions(-) create mode 100644 nonebot_plugin_memes/matchers/statistics.py create mode 100644 nonebot_plugin_memes/plot.py diff --git a/nonebot_plugin_memes/config.py b/nonebot_plugin_memes/config.py index f7ee6c6..36560b1 100644 --- a/nonebot_plugin_memes/config.py +++ b/nonebot_plugin_memes/config.py @@ -13,7 +13,8 @@ class MemeListImageConfig(BaseModel): text_template: str = "{keywords}" add_category_icon: bool = True label_new_timedelta: timedelta = timedelta(days=30) - label_hot_frequency: int = 24 + label_hot_threshold: int = 21 + label_hot_days: int = 7 class Config(BaseModel): diff --git a/nonebot_plugin_memes/manager.py b/nonebot_plugin_memes/manager.py index 54bbdd9..4aea45c 100644 --- a/nonebot_plugin_memes/manager.py +++ b/nonebot_plugin_memes/manager.py @@ -97,7 +97,6 @@ def search( meme_names = process.extract( meme_name, self.__meme_names.keys(), limit=limit, score_cutoff=score_cutoff ) - logger.debug(meme_names) result: dict[str, Meme] = {} for name, _, _ in meme_names: for meme in self.__meme_names[name]: diff --git a/nonebot_plugin_memes/matchers/__init__.py b/nonebot_plugin_memes/matchers/__init__.py index 3489d12..cbc73a5 100644 --- a/nonebot_plugin_memes/matchers/__init__.py +++ b/nonebot_plugin_memes/matchers/__init__.py @@ -1 +1 @@ -from . import info, manage, search, help, command # noqa +from . import info, manage, search, help, command, statistics # noqa diff --git a/nonebot_plugin_memes/matchers/help.py b/nonebot_plugin_memes/matchers/help.py index be2dff1..f984369 100644 --- a/nonebot_plugin_memes/matchers/help.py +++ b/nonebot_plugin_memes/matchers/help.py @@ -1,5 +1,5 @@ import hashlib -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from io import BytesIO from itertools import chain @@ -50,10 +50,13 @@ async def _(user_id: UserId, session: EventSession): memes = sorted(memes, key=lambda meme: meme.date_modified, reverse=sort_reverse) label_new_timedelta = list_image_config.label_new_timedelta - label_hot_frequency = list_image_config.label_hot_frequency + label_hot_threshold = list_image_config.label_hot_threshold + label_hot_days = list_image_config.label_hot_days meme_generation_keys = await get_meme_generation_keys( - session, SessionIdType.GLOBAL, timedelta(days=1) + session, + SessionIdType.GLOBAL, + time_start=datetime.now(timezone.utc) - timedelta(days=label_hot_days), ) meme_list: list[tuple[Meme, MemeProperties]] = [] @@ -61,7 +64,7 @@ async def _(user_id: UserId, session: EventSession): labels = [] if datetime.now() - meme.date_created < label_new_timedelta: labels.append("new") - if meme_generation_keys.count(meme.key) >= label_hot_frequency: + if meme_generation_keys.count(meme.key) >= label_hot_threshold: labels.append("hot") disabled = not meme_manager.check(user_id, meme.key) meme_list.append((meme, MemeProperties(disabled=disabled, labels=labels))) diff --git a/nonebot_plugin_memes/matchers/statistics.py b/nonebot_plugin_memes/matchers/statistics.py new file mode 100644 index 0000000..2725e09 --- /dev/null +++ b/nonebot_plugin_memes/matchers/statistics.py @@ -0,0 +1,218 @@ +from datetime import datetime, timedelta +from typing import Any, Optional, Union + +from dateutil.relativedelta import relativedelta +from nonebot.matcher import Matcher +from nonebot_plugin_alconna import ( + Alconna, + AlconnaQuery, + Args, + Option, + Query, + UniMessage, + on_alconna, + store_true, +) +from nonebot_plugin_session import EventSession, SessionIdType + +from ..plot import plot_duration_counts, plot_key_and_duration_counts +from ..recorder import get_meme_generation_records, get_meme_generation_times +from ..utils import add_timezone +from .utils import find_meme + +statistics_matcher = on_alconna( + Alconna( + "表情调用统计", + Args["meme_name?", str], + Option("-g|--global", default=False, action=store_true, help_text="全局统计"), + Option("--my", default=False, action=store_true, help_text="我的"), + Option( + "-t|--type", + Args["type", ["day", "week", "month", "year", "24h", "7d", "30d", "1y"]], + help_text="统计类型", + ), + ), + aliases={"表情使用统计"}, + block=True, + priority=11, + use_cmd_start=True, +) + + +def wrapper( + slot: Union[int, str], content: Optional[str], context: dict[str, Any] +) -> str: + if slot == "my" and content: + return "--my" + elif slot == "global" and content: + return "--global" + elif slot == "type" and content: + if content in ["日", "24小时", "1天"]: + return "--type 24h" + elif content in ["本日", "今日"]: + return "--type day" + elif content in ["周", "一周", "7天"]: + return "--type 7d" + elif content in ["本周"]: + return "--type week" + elif content in ["月", "30天"]: + return "--type 30d" + elif content in ["本月", "月度"]: + return "--type month" + elif content in ["年", "一年"]: + return "--type 1y" + elif content in ["本年", "年度"]: + return "--type year" + return "" + + +pattern_my = r"(?P我的)" +pattern_type = r"(?P日|24小时|1天|本日|今日|周|一周|7天|本周|月|30天|本月|月度|年|一年|本年|年度)" # noqa E501 +pattern_global = r"(?P全局)" +pattern_cmd = r"表情(?:调用|使用)统计" + +statistics_matcher.shortcut( + rf"{pattern_my}{pattern_cmd}", + prefix=True, + wrapper=wrapper, + arguments=["{my}"], +).shortcut( + rf"{pattern_global}{pattern_cmd}", + prefix=True, + wrapper=wrapper, + arguments=["{global}"], +).shortcut( + rf"{pattern_my}{pattern_global}{pattern_cmd}", + prefix=True, + wrapper=wrapper, + arguments=["{my}", "{global}"], +).shortcut( + rf"{pattern_my}?{pattern_global}?{pattern_type}{pattern_cmd}", + prefix=True, + wrapper=wrapper, + arguments=["{my}", "{global}", "{type}"], +) + + +@statistics_matcher.handle() +async def _( + matcher: Matcher, + session: EventSession, + meme_name: Optional[str] = None, + query_global: Query[bool] = AlconnaQuery("global.value", False), + query_my: Query[bool] = AlconnaQuery("my.value", False), + query_type: Query[str] = AlconnaQuery("type", "24h"), +): + meme = await find_meme(matcher, meme_name) if meme_name else None + + is_my = query_my.result + is_global = query_global.result + type = query_type.result + + if is_my and is_global: + id_type = SessionIdType.USER + elif is_my: + id_type = SessionIdType.GROUP_USER + elif is_global: + id_type = SessionIdType.GLOBAL + else: + id_type = SessionIdType.GROUP + + now = datetime.now().astimezone() + if type == "24h": + start = now - timedelta(days=1) + td = timedelta(hours=1) + fmt = "%H:%M" + humanized = "24小时" + elif type == "day": + start = now.replace(hour=0, minute=0, second=0, microsecond=0) + td = timedelta(hours=1) + fmt = "%H:%M" + humanized = "本日" + elif type == "7d": + start = now - timedelta(days=7) + td = timedelta(days=1) + fmt = "%m/%d" + humanized = "7天" + elif type == "week": + start = now.replace(hour=0, minute=0, second=0, microsecond=0) - timedelta( + days=now.weekday() + ) + td = timedelta(days=1) + fmt = "%a" + humanized = "本周" + elif type == "30d": + start = now - timedelta(days=30) + td = timedelta(days=1) + fmt = "%m/%d" + humanized = "30天" + elif type == "month": + start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + td = timedelta(days=1) + fmt = "%m/%d" + humanized = "本月" + elif type == "1y": + start = now - relativedelta(years=1) + td = relativedelta(months=1) + fmt = "%y/%m" + humanized = "一年" + else: + start = now.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) + td = relativedelta(months=1) + fmt = "%b" + humanized = "本年" + + if meme: + meme_times = await get_meme_generation_times( + session, id_type, meme_key=meme.key, time_start=start + ) + meme_keys = [meme.key] * len(meme_times) + else: + meme_records = await get_meme_generation_records( + session, id_type, time_start=start + ) + meme_times = [record.time for record in meme_records] + meme_keys = [record.meme_key for record in meme_records] + + if not meme_times: + await matcher.finish("暂时没有表情调用记录") + + meme_times = [add_timezone(time) for time in meme_times] + meme_times.sort() + + def fmt_time(time: datetime) -> str: + if type in ["24h", "7d", "30d", "1y"]: + return (time + td).strftime(fmt) + return time.strftime(fmt) + + duration_counts: dict[str, int] = {} + stop = start + td + count = 0 + key = fmt_time(start) + for time in meme_times: + while time >= stop: + duration_counts[key] = count + key = fmt_time(stop) + stop += td + count = 0 + count += 1 + duration_counts[key] = count + while stop <= now: + key = fmt_time(stop) + stop += td + duration_counts[key] = 0 + + key_counts: dict[str, int] = {} + for key in meme_keys: + key_counts[key] = key_counts.get(key, 0) + 1 + + if meme: + title = ( + f"表情“{meme.key}”{humanized}调用统计" + f"(总调用次数为 {key_counts.get(meme.key, 0)})" + ) + output = await plot_duration_counts(duration_counts, title) + else: + title = f"{humanized}表情调用统计" + output = await plot_key_and_duration_counts(key_counts, duration_counts, title) + await UniMessage.image(raw=output).send() diff --git a/nonebot_plugin_memes/plot.py b/nonebot_plugin_memes/plot.py new file mode 100644 index 0000000..0cb4ea4 --- /dev/null +++ b/nonebot_plugin_memes/plot.py @@ -0,0 +1,76 @@ +from io import BytesIO + +import matplotlib +from matplotlib import pyplot as plt +from matplotlib.axes import Axes +from matplotlib.font_manager import fontManager +from matplotlib.ticker import MaxNLocator +from nonebot.utils import run_sync + +matplotlib.use("agg") +fallback_fonts = [ + "PingFang SC", + "Hiragino Sans GB", + "Microsoft YaHei", + "Source Han Sans SC", + "Noto Sans SC", + "Noto Sans CJK SC", + "WenQuanYi Micro Hei", +] +for fontfamily in fallback_fonts.copy(): + try: + fontManager.findfont(fontfamily, fallback_to_default=False) + except ValueError: + fallback_fonts.remove(fontfamily) +matplotlib.rcParams["font.family"] = fallback_fonts + + +@run_sync +def plot_key_and_duration_counts( + key_counts: dict[str, int], duration_counts: dict[str, int], title: str +) -> BytesIO: + up_x = list(key_counts.keys()) + up_y = list(key_counts.values()) + low_x = list(duration_counts.keys()) + low_y = list(duration_counts.values()) + up_height = len(up_x) * 0.3 + low_height = 3 + fig_width = 8 + fig, axs = plt.subplots( + nrows=2, + figsize=(fig_width, up_height + low_height), + height_ratios=[up_height, low_height], + ) + up: Axes = axs[0] + up.barh(up_x, up_y, height=0.6) + up.xaxis.set_major_locator(MaxNLocator(integer=True)) + low: Axes = axs[1] + low.plot(low_x, low_y, marker="o") + if len(low_x) > 24: + low.set_xticks(low_x[::3]) + elif len(low_x) > 12: + low.set_xticks(low_x[::2]) + low.yaxis.set_major_locator(MaxNLocator(integer=True)) + fig.suptitle(title) + fig.tight_layout() + output = BytesIO() + fig.savefig(output) + return output + + +@run_sync +def plot_duration_counts(duration_counts: dict[str, int], title: str) -> BytesIO: + x = list(duration_counts.keys()) + y = list(duration_counts.values()) + fig, ax = plt.subplots(figsize=(6, 4)) + ax.plot(x, y, marker="o") + if len(x) > 24: + ax.set_xticks(x[::3]) + elif len(x) > 12: + ax.set_xticks(x[::2]) + ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + fig.suptitle(title) + fig.tight_layout() + output = BytesIO() + fig.savefig(output) + return output diff --git a/nonebot_plugin_memes/recorder.py b/nonebot_plugin_memes/recorder.py index 9de15e4..5da4a8b 100644 --- a/nonebot_plugin_memes/recorder.py +++ b/nonebot_plugin_memes/recorder.py @@ -1,11 +1,15 @@ -from datetime import datetime, timedelta, timezone +from dataclasses import dataclass +from datetime import datetime, timezone +from typing import Optional from nonebot_plugin_orm import Model, get_session from nonebot_plugin_session import Session, SessionIdType from nonebot_plugin_session_orm import SessionModel, get_session_persist_id -from sqlalchemy import String, select +from sqlalchemy import ColumnElement, String, select from sqlalchemy.orm import Mapped, mapped_column +from .utils import remove_timezone + class MemeGenerationRecord(Model): """表情调用记录""" @@ -21,6 +25,12 @@ class MemeGenerationRecord(Model): """ 表情名 """ +@dataclass +class MemeRecord: + time: datetime + meme_key: str + + async def record_meme_generation(session: Session, meme_key: str): session_persist_id = await get_session_persist_id(session) @@ -34,14 +44,77 @@ async def record_meme_generation(session: Session, meme_key: str): await db_session.commit() -async def get_meme_generation_keys( - session: Session, id_type: SessionIdType, time_delta: timedelta -) -> list[str]: +def filter_statement( + session: Session, + id_type: SessionIdType, + *, + meme_key: Optional[str] = None, + time_start: Optional[datetime] = None, + time_stop: Optional[datetime] = None, +) -> list[ColumnElement[bool]]: whereclause = SessionModel.filter_statement( session, id_type, include_bot_type=False ) - whereclause.append( - MemeGenerationRecord.time >= (datetime.now(timezone.utc) - time_delta) + if meme_key: + whereclause.append(MemeGenerationRecord.meme_key == meme_key) + if time_start: + whereclause.append(MemeGenerationRecord.time >= remove_timezone(time_start)) + if time_stop: + whereclause.append(MemeGenerationRecord.time <= remove_timezone(time_stop)) + return whereclause + + +async def get_meme_generation_records( + session: Session, + id_type: SessionIdType, + *, + meme_key: Optional[str] = None, + time_start: Optional[datetime] = None, + time_stop: Optional[datetime] = None, +) -> list[MemeRecord]: + whereclause = filter_statement( + session, id_type, meme_key=meme_key, time_start=time_start, time_stop=time_stop + ) + statement = ( + select(MemeGenerationRecord.time, MemeGenerationRecord.meme_key) + .where(*whereclause) + .join(SessionModel, SessionModel.id == MemeGenerationRecord.session_persist_id) + ) + async with get_session() as db_session: + results = (await db_session.execute(statement)).all() + return [MemeRecord(result[0], result[1]) for result in results] + + +async def get_meme_generation_times( + session: Session, + id_type: SessionIdType, + *, + meme_key: Optional[str] = None, + time_start: Optional[datetime] = None, + time_stop: Optional[datetime] = None, +) -> list[datetime]: + whereclause = filter_statement( + session, id_type, meme_key=meme_key, time_start=time_start, time_stop=time_stop + ) + statement = ( + select(MemeGenerationRecord.time) + .where(*whereclause) + .join(SessionModel, SessionModel.id == MemeGenerationRecord.session_persist_id) + ) + async with get_session() as db_session: + results = (await db_session.scalars(statement)).all() + return list(results) + + +async def get_meme_generation_keys( + session: Session, + id_type: SessionIdType, + *, + time_start: Optional[datetime] = None, + time_stop: Optional[datetime] = None, +) -> list[str]: + whereclause = filter_statement( + session, id_type, time_start=time_start, time_stop=time_stop ) statement = ( select(MemeGenerationRecord.meme_key) @@ -49,5 +122,5 @@ async def get_meme_generation_keys( .join(SessionModel, SessionModel.id == MemeGenerationRecord.session_persist_id) ) async with get_session() as db_session: - result = (await db_session.scalars(statement)).all() - return list(result) + results = (await db_session.scalars(statement)).all() + return list(results) diff --git a/nonebot_plugin_memes/utils.py b/nonebot_plugin_memes/utils.py index 94e6e15..51cc559 100644 --- a/nonebot_plugin_memes/utils.py +++ b/nonebot_plugin_memes/utils.py @@ -1,4 +1,5 @@ import asyncio +from datetime import datetime, timezone import httpx from nonebot.log import logger @@ -19,3 +20,19 @@ async def download_url(url: str) -> bytes: logger.warning(f"Error downloading {url}, retry {i}/3: {e}") await asyncio.sleep(3) raise NetworkError(f"{url} 下载失败!") + + +def remove_timezone(dt: datetime) -> datetime: + """移除时区""" + if dt.tzinfo is None: + return dt + # 先转至 UTC 时间,再移除时区 + dt = dt.astimezone(timezone.utc) + return dt.replace(tzinfo=None) + + +def add_timezone(dt: datetime) -> datetime: + """添加时区""" + if dt.tzinfo is not None: + return dt.astimezone() + return dt.replace(tzinfo=timezone.utc).astimezone() diff --git a/poetry.lock b/poetry.lock index a8c2789..803adad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -72,13 +72,13 @@ trio = ["trio (>=0.23)"] [[package]] name = "arclet-alconna" -version = "1.8.24" +version = "1.8.25" description = "A High-performance, Generality, Humane Command Line Arguments Parser Library." optional = false python-versions = ">=3.8" files = [ - {file = "arclet_alconna-1.8.24-py3-none-any.whl", hash = "sha256:ca6e22ea6e2aba954dfc9c2fe47c4eb86ce975b5e2ee94ec13e1f24be51c6280"}, - {file = "arclet_alconna-1.8.24.tar.gz", hash = "sha256:aed11334f7b1c2057e7590868a60ac0bbbfaba8e3816ec9e4d1ad236d77dca25"}, + {file = "arclet_alconna-1.8.25-py3-none-any.whl", hash = "sha256:5f269c547ed2071035c3c53c567f449fa4cbaeb956748c024d2887d73d4da332"}, + {file = "arclet_alconna-1.8.25.tar.gz", hash = "sha256:99f009b2e02e8db6a9cbd6e4c86b6a513311be33307535f518eb5b693a4e2d8d"}, ] [package.dependencies] @@ -857,40 +857,51 @@ files = [ [[package]] name = "matplotlib" -version = "3.9.1.post1" +version = "3.9.2" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.9.1.post1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3779ad3e8b72df22b8a622c5796bbcfabfa0069b835412e3c1dec8ee3de92d0c"}, - {file = "matplotlib-3.9.1.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ec400340f8628e8e2260d679078d4e9b478699f386e5cc8094e80a1cb0039c7c"}, - {file = "matplotlib-3.9.1.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82c18791b8862ea095081f745b81f896b011c5a5091678fb33204fef641476af"}, - {file = "matplotlib-3.9.1.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621a628389c09a6b9f609a238af8e66acecece1cfa12febc5fe4195114ba7446"}, - {file = "matplotlib-3.9.1.post1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9a54734ca761ebb27cd4f0b6c2ede696ab6861052d7d7e7b8f7a6782665115f5"}, - {file = "matplotlib-3.9.1.post1-cp310-cp310-win_amd64.whl", hash = "sha256:0721f93db92311bb514e446842e2b21c004541dcca0281afa495053e017c5458"}, - {file = "matplotlib-3.9.1.post1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b08b46058fe2a31ecb81ef6aa3611f41d871f6a8280e9057cb4016cb3d8e894a"}, - {file = "matplotlib-3.9.1.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22b344e84fcc574f561b5731f89a7625db8ef80cdbb0026a8ea855a33e3429d1"}, - {file = "matplotlib-3.9.1.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b49fee26d64aefa9f061b575f0f7b5fc4663e51f87375c7239efa3d30d908fa"}, - {file = "matplotlib-3.9.1.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89eb7e89e2b57856533c5c98f018aa3254fa3789fcd86d5f80077b9034a54c9a"}, - {file = "matplotlib-3.9.1.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c06e742bade41fda6176d4c9c78c9ea016e176cd338e62a1686384cb1eb8de41"}, - {file = "matplotlib-3.9.1.post1-cp311-cp311-win_amd64.whl", hash = "sha256:c44edab5b849e0fc1f1c9d6e13eaa35ef65925f7be45be891d9784709ad95561"}, - {file = "matplotlib-3.9.1.post1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bf28b09986aee06393e808e661c3466be9c21eff443c9bc881bce04bfbb0c500"}, - {file = "matplotlib-3.9.1.post1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:92aeb8c439d4831510d8b9d5e39f31c16c7f37873879767c26b147cef61e54cd"}, - {file = "matplotlib-3.9.1.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f15798b0691b45c80d3320358a88ce5a9d6f518b28575b3ea3ed31b4bd95d009"}, - {file = "matplotlib-3.9.1.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d59fc6096da7b9c1df275f9afc3fef5cbf634c21df9e5f844cba3dd8deb1847d"}, - {file = "matplotlib-3.9.1.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab986817a32a70ce22302438691e7df4c6ee4a844d47289db9d583d873491e0b"}, - {file = "matplotlib-3.9.1.post1-cp312-cp312-win_amd64.whl", hash = "sha256:0d78e7d2d86c4472da105d39aba9b754ed3dfeaeaa4ac7206b82706e0a5362fa"}, - {file = "matplotlib-3.9.1.post1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd07eba6431b4dc9253cce6374a28c415e1d3a7dc9f8aba028ea7592f06fe172"}, - {file = "matplotlib-3.9.1.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ca230cc4482010d646827bd2c6d140c98c361e769ae7d954ebf6fff2a226f5b1"}, - {file = "matplotlib-3.9.1.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ace27c0fdeded399cbc43f22ffa76e0f0752358f5b33106ec7197534df08725a"}, - {file = "matplotlib-3.9.1.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a4f3aeb7ba14c497dc6f021a076c48c2e5fbdf3da1e7264a5d649683e284a2f"}, - {file = "matplotlib-3.9.1.post1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:23f96fbd4ff4cfa9b8a6b685a65e7eb3c2ced724a8d965995ec5c9c2b1f7daf5"}, - {file = "matplotlib-3.9.1.post1-cp39-cp39-win_amd64.whl", hash = "sha256:2808b95452b4ffa14bfb7c7edffc5350743c31bda495f0d63d10fdd9bc69e895"}, - {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ffc91239f73b4179dec256b01299d46d0ffa9d27d98494bc1476a651b7821cbe"}, - {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f965ebca9fd4feaaca45937c4849d92b70653057497181100fcd1e18161e5f29"}, - {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801ee9323fd7b2da0d405aebbf98d1da77ea430bbbbbec6834c0b3af15e5db44"}, - {file = "matplotlib-3.9.1.post1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50113e9b43ceb285739f35d43db36aa752fb8154325b35d134ff6e177452f9ec"}, - {file = "matplotlib-3.9.1.post1.tar.gz", hash = "sha256:c91e585c65092c975a44dc9d4239ba8c594ba3c193d7c478b6d178c4ef61f406"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"}, + {file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"}, + {file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"}, + {file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"}, + {file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"}, + {file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"}, + {file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"}, + {file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"}, + {file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"}, + {file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"}, + {file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"}, + {file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"}, + {file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"}, + {file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"}, + {file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"}, + {file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"}, + {file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"}, + {file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"}, + {file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"}, + {file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"}, + {file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"}, + {file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"}, + {file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"}, + {file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"}, + {file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"}, + {file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"}, ] [package.dependencies] @@ -1143,17 +1154,17 @@ typing-extensions = ">=4.0.0,<5.0.0" [[package]] name = "nonebot-plugin-alconna" -version = "0.51.2" +version = "0.51.3" description = "Alconna Adapter for Nonebot" optional = false python-versions = ">=3.9" files = [ - {file = "nonebot_plugin_alconna-0.51.2-py3-none-any.whl", hash = "sha256:8e44846d5034623d3c490de3d97efe46edc4fa43c605ecbc6f480f6edfe6e701"}, - {file = "nonebot_plugin_alconna-0.51.2.tar.gz", hash = "sha256:81bda540b2998fde54020275634059fddbef2f71d5bc1fe83fff1d6069b054de"}, + {file = "nonebot_plugin_alconna-0.51.3-py3-none-any.whl", hash = "sha256:4451a494d9b1e997a2fbe669fda007e63f5e67af5b468072f0bd2204c8cee3d9"}, + {file = "nonebot_plugin_alconna-0.51.3.tar.gz", hash = "sha256:e5d274cf005819da0829d07f300c72509284186ffedfd344677c15743dc49fa8"}, ] [package.dependencies] -arclet-alconna = ">=1.8.23" +arclet-alconna = ">=1.8.25" arclet-alconna-tools = ">=0.7.9" importlib-metadata = ">=4.13.0" nepattern = ">=0.7.4" @@ -2286,13 +2297,13 @@ devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3) [[package]] name = "uvicorn" -version = "0.30.5" +version = "0.30.6" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" files = [ - {file = "uvicorn-0.30.5-py3-none-any.whl", hash = "sha256:b2d86de274726e9878188fa07576c9ceeff90a839e2b6e25c917fe05f5a6c835"}, - {file = "uvicorn-0.30.5.tar.gz", hash = "sha256:ac6fdbd4425c5fd17a9fe39daf4d4d075da6fdc80f653e5894cdc2fd98752bee"}, + {file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5"}, + {file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788"}, ] [package.dependencies] @@ -2669,4 +2680,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "65d37d17f95a10efa127a4680216d07f98491444389845625480705017e48c0f" +content-hash = "ce49d80198b28c4aaf3974e2cc49a295f080a6ae750140cda6170163726affd9" diff --git a/pyproject.toml b/pyproject.toml index 4363271..7af635e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,8 @@ meme-generator = "^0.1.0" pypinyin = ">=0.44.0,<1.0.0" pyyaml = "^6.0" rapidfuzz = "^3.9.0" +matplotlib = "^3.7.0" +python-dateutil = "^2.8.2" [tool.poetry.group.dev.dependencies] nonebot-adapter-onebot = "^2.4.0"