diff --git a/.gitignore b/.gitignore index 6597c9cc..4eeca7b3 100644 --- a/.gitignore +++ b/.gitignore @@ -155,4 +155,8 @@ tmp/ # alembic versions alembic_scripts/versions/ +# private script +*.private.py +*.nonpublic.py + # End of https://www.toptal.com/developers/gitignore/api/python \ No newline at end of file diff --git a/README.md b/README.md index e46308b2..ae0ebb37 100644 --- a/README.md +++ b/README.md @@ -11,16 +11,12 @@ _基于 [Nonebot2](https://github.com/nonebot/nonebot2) 的多平台机器人_ ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Ailitonia/omega-miya?include_prereleases) ![GitHub (Pre-)Release Date](https://img.shields.io/github/release-date-pre/Ailitonia/omega-miya)
-![Nonebot2](https://img.shields.io/badge/Nonebot2-Release_v2.3.2-brightgreen) +![Nonebot2](https://img.shields.io/badge/Nonebot2-v2.4.1-lightgrey?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAaVBMVEX//////////////////////////////////////////////////////////////////////////////Pz+9/f++fn84+P729v73t7++Pj+8/P62dn97Oz97e3+9fX+9vb84uL98/P98vKkMaRVAAAAEnRSTlMRh+ztjBP5+o/q7/7x+5OO8hXWMtBoAAAAAWJLR0QAiAUdSAAAAAd0SU1FB+QIBRALHK18bjMAAACFSURBVBjTbY/bEoIwDESDgqjcNCm9kCro/3+kCU4tM9iX7JxMdzcAxQF/71hWUJxUkTGksz7DRcZonffOBpFXaBAnjrKmB09CQPb8/DrMHFZgY/KMVgE5SkAloPE51pt/YPcFl2y6rCmB522sFHulYm8tpqeFXL2Fst4e1/VQDW2OvfX3D9EcEtYPs4uwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA4LTA1VDE2OjExOjI4KzAyOjAwtwGtpQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wOC0wNVQxNjoxMToyOCswMjowMMZcFRkAAABXelRYdFJhdyBwcm9maWxlIHR5cGUgaXB0YwAAeJzj8gwIcVYoKMpPy8xJ5VIAAyMLLmMLEyMTS5MUAxMgRIA0w2QDI7NUIMvY1MjEzMQcxAfLgEigSi4A6hcRdPJCNZUAAAAASUVORK5CYII=) ![OneBot v11](https://img.shields.io/badge/OneBot-v11-black?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIVBMVEUAAAAAAAADAwMHBwceHh4UFBQNDQ0ZGRkoKCgvLy8iIiLWSdWYAAAAAXRSTlMAQObYZgAAAQVJREFUSMftlM0RgjAQhV+0ATYK6i1Xb+iMd0qgBEqgBEuwBOxU2QDKsjvojQPvkJ/ZL5sXkgWrFirK4MibYUdE3OR2nEpuKz1/q8CdNxNQgthZCXYVLjyoDQftaKuniHHWRnPh2GCUetR2/9HsMAXyUT4/3UHwtQT2AggSCGKeSAsFnxBIOuAggdh3AKTL7pDuCyABcMb0aQP7aM4AnAbc/wHwA5D2wDHTTe56gIIOUA/4YYV2e1sg713PXdZJAuncdZMAGkAukU9OAn40O849+0ornPwT93rphWF0mgAbauUrEOthlX8Zu7P5A6kZyKCJy75hhw1Mgr9RAUvX7A3csGqZegEdniCx30c3agAAAABJRU5ErkJggg==) ![QQ频道](https://img.shields.io/badge/QQ%E9%A2%91%E9%81%93-Bot-lightgrey?style=social&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMTIuODIgMTMwLjg5Ij48ZyBkYXRhLW5hbWU9IuWbvuWxgiAyIj48ZyBkYXRhLW5hbWU9IuWbvuWxgiAxIj48cGF0aCBkPSJNNTUuNjMgMTMwLjhjLTcgMC0xMy45LjA4LTIwLjg2IDAtMTkuMTUtLjI1LTMxLjcxLTExLjQtMzQuMjItMzAuMy00LjA3LTMwLjY2IDE0LjkzLTU5LjIgNDQuODMtNjYuNjQgMi0uNTEgNS4yMS0uMzEgNS4yMS0xLjYzIDAtMi4xMy4xNC0yLjEzLjE0LTUuNTcgMC0uODktMS4zLTEuNDYtMi4yMi0yLjMxLTYuNzMtNi4yMy03LjY3LTEzLjQxLTEtMjAuMTggNS40LTUuNTIgMTEuODctNS40IDE3LjgtLjU5IDYuNDkgNS4yNiA2LjMxIDEzLjA4LS44NiAyMS0uNjguNzQtMS43OCAxLjYtMS43OCAyLjY3djQuMjFjMCAxLjM1IDIuMiAxLjYyIDQuNzkgMi4zNSAzMS4wOSA4LjY1IDQ4LjE3IDM0LjEzIDQ1IDY2LjM3LTEuNzYgMTguMTUtMTQuNTYgMzAuMjMtMzIuNyAzMC42My04LjAyLjE5LTE2LjA3LS4wMS0yNC4xMy0uMDF6IiBmaWxsPSIjMDI5OWZlIi8+PHBhdGggZD0iTTMxLjQ2IDExOC4zOGMtMTAuNS0uNjktMTYuOC02Ljg2LTE4LjM4LTE3LjI3LTMtMTkuNDIgMi43OC0zNS44NiAxOC40Ni00Ny44MyAxNC4xNi0xMC44IDI5Ljg3LTEyIDQ1LjM4LTMuMTkgMTcuMjUgOS44NCAyNC41OSAyNS44MSAyNCA0NS4yOS0uNDkgMTUuOS04LjQyIDIzLjE0LTI0LjM4IDIzLjUtNi41OS4xNC0xMy4xOSAwLTE5Ljc5IDAiIGZpbGw9IiNmZWZlZmUiLz48cGF0aCBkPSJNNDYuMDUgNzkuNThjLjA5IDUgLjIzIDkuODItNyA5Ljc3LTcuODItLjA2LTYuMS01LjY5LTYuMjQtMTAuMTktLjE1LTQuODItLjczLTEwIDYuNzMtOS44NHM2LjM3IDUuNTUgNi41MSAxMC4yNnoiIGZpbGw9IiMxMDlmZmUiLz48cGF0aCBkPSJNODAuMjcgNzkuMjdjLS41MyAzLjkxIDEuNzUgOS42NC01Ljg4IDEwLTcuNDcuMzctNi44MS00LjgyLTYuNjEtOS41LjItNC4zMi0xLjgzLTEwIDUuNzgtMTAuNDJzNi41OSA0Ljg5IDYuNzEgOS45MnoiIGZpbGw9IiMwODljZmUiLz48L2c+PC9nPjwvc3ZnPg==) ![Telegram](https://img.shields.io/badge/telegram-Bot-lightgrey?style=social&logo=telegram) -## 当前适配 nonebot2 版本 - -[Nonebot2 Release v2.3.3](https://github.com/nonebot/nonebot2/releases/tag/v2.3.3) - ## 功能 & 特点 - 基于异步 SQLAlchemy ORM, 支持多种数据库连接 @@ -45,12 +41,12 @@ _基于 [Nonebot2](https://github.com/nonebot/nonebot2) 的多平台机器人_ - B站动态订阅 - B站直播间监控 - 微博用户订阅 -- 图站作品预览 (需要 HTTP 代理, 或部署在外网) -- Pixiv用户订阅 (需要 HTTP 代理, 或部署在外网) -- Pixivision特辑订阅 (需要 HTTP 代理, 或部署在外网) +- 图站作品预览 (如不能直接访问各图站, 则需要 HTTP 代理) +- Pixiv用户订阅 (如不能直接访问 Pixiv 主站, 则需要 HTTP 代理) +- Pixivision特辑订阅 (如不能直接访问 Pixiv 主站, 则需要 HTTP 代理) - 签到卡片 - 求签 -- 抽卡 +- 词云 - roll 点抽奖 - 塔罗牌 - 翻译插件 (使用腾讯云 API) @@ -64,7 +60,7 @@ _基于 [Nonebot2](https://github.com/nonebot/nonebot2) 的多平台机器人_ - 来点萌图 / 来点涩图 (需要 HTTP 代理, 除非部署在外网 / 图片数据库需要自己导入) - 表情包制作器 - 今天吃啥 -- 自动锤轴姬 (需要 go-cqhttp v0.9.40 及以上版本) +- 自动锤轴姬 (需要 OneBot V11 协议端支持文件发送 API) - 邮箱插件 (仅支持IMAP收件) ## 如何使用 @@ -100,12 +96,15 @@ danbooru/konachan/yande.re 的高评分作品 #### Classification: 主要体现图片由谁分级以及分级的**可靠性** +- `Ignored = -2` 可能是由于**XP不符**/低质/敏感话题/广告等因素, 被人工手动审核/标记为忽略该作品, 一般情况下不应当使用分类为此等级的图片 - `Unknown = -1` 无法确认分类级别, 一般为本地图片或无确切来源的图片 - `Unclassified = 0` 未分类, 一般为无分级图站作品默认分类级别 - `AIGenerated = 1` 确认/疑似为 AI 生成作品 - `Automatic = 2` 由图站分类/图站分级/第三方接口分类, 可能由人工进行分类但不完全可信, 一般可作为应用层插件使用的最低可信级别 - `Confirmed = 3` 由人工审核/确认为 "人类生成" 的作品, 且分级可信 +~~说白了就是 `Classification = 3` 的才代表本人XP, 其他级别均为未分类/自动爬取/忽略排除, 本人概不负责~~ + #### Rating: 图片分级 - `Unknown = -1` 未知, 可能为下面任意一种分级的其中之一, 绝对不要直接当作 G-rated 作品使用 diff --git a/alembic_scripts/env.py b/alembic_scripts/env.py index af4cdb3f..4e71a36d 100644 --- a/alembic_scripts/env.py +++ b/alembic_scripts/env.py @@ -1,12 +1,11 @@ import asyncio from logging.config import fileConfig +from alembic import context from sqlalchemy import pool from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config -from alembic import context - from src.database.schema_base import OmegaDeclarativeBase # this is the Alembic Config object, which provides @@ -42,12 +41,12 @@ def run_migrations_offline() -> None: script output. """ - url = config.get_main_option("sqlalchemy.url") + url = config.get_main_option('sqlalchemy.url') context.configure( url=url, target_metadata=target_metadata, literal_binds=True, - dialect_opts={"paramstyle": "named"}, + dialect_opts={'paramstyle': 'named'}, ) with context.begin_transaction(): @@ -69,7 +68,7 @@ async def run_async_migrations() -> None: connectable = async_engine_from_config( config.get_section(config.config_ini_section, {}), - prefix="sqlalchemy.", + prefix='sqlalchemy.', poolclass=pool.NullPool, ) diff --git a/bot.py b/bot.py index 23d7e982..e82cb029 100644 --- a/bot.py +++ b/bot.py @@ -8,31 +8,18 @@ @Software : PyCharm """ -import os -import sys -from datetime import datetime - import nonebot from nonebot.log import logger, default_format -# Log file path -bot_log_path = os.path.abspath(os.path.join(sys.path[0], 'log')) -if not os.path.exists(bot_log_path): - os.makedirs(bot_log_path) - -# Custom logger -log_info_name = f'{datetime.now().strftime("%Y%m%d-%H%M%S")}-INFO.log' -log_error_name = f'{datetime.now().strftime("%Y%m%d-%H%M%S")}-ERROR.log' -log_info_path = os.path.join(bot_log_path, log_info_name) -log_error_path = os.path.join(bot_log_path, log_error_name) +from src.resource import LogFileResource -logger.add(log_info_path, rotation='00:00', diagnose=False, level='INFO', format=default_format, encoding='utf-8') -logger.add(log_error_path, rotation='00:00', diagnose=False, level='ERROR', format=default_format, encoding='utf-8') +# Log file path +log_path = LogFileResource() +logger.add(log_path.info, rotation='00:00', diagnose=False, level='INFO', format=default_format, encoding='utf-8') +logger.add(log_path.error, rotation='00:00', diagnose=False, level='ERROR', format=default_format, encoding='utf-8') # Add extra debug log file -# log_debug_name = f'{datetime.now().strftime("%Y%m%d-%H%M%S")}-DEBUG.log' -# log_debug_path = os.path.join(bot_log_path, log_debug_name) -# logger.add(log_debug_path, rotation='00:00', diagnose=False, level='DEBUG', format=default_format, encoding='utf-8') +# logger.add(log_path.debug, rotation='00:00', diagnose=False, level='DEBUG', format=default_format, encoding='utf-8') # You can pass some keyword args config to init function nonebot.init() diff --git a/poetry.lock b/poetry.lock index f5c6e10f..ffeb3421 100644 --- a/poetry.lock +++ b/poetry.lock @@ -27,113 +27,98 @@ files = [ [[package]] name = "aiohappyeyeballs" -version = "2.4.0" +version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - { file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd" }, - { file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2" }, + { file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8" }, + { file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745" }, ] [[package]] name = "aiohttp" -version = "3.10.5" +version = "3.11.11" description = "Async http client/server framework (asyncio)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - { file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3" }, - { file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6" }, - { file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699" }, - { file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6" }, - { file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1" }, - { file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f" }, - { file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb" }, - { file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91" }, - { file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f" }, - { file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c" }, - { file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69" }, - { file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3" }, - { file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683" }, - { file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef" }, - { file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088" }, - { file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2" }, - { file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf" }, - { file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e" }, - { file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77" }, - { file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061" }, - { file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697" }, - { file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7" }, - { file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0" }, - { file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5" }, - { file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e" }, - { file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1" }, - { file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277" }, - { file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058" }, - { file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072" }, - { file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff" }, - { file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487" }, - { file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a" }, - { file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d" }, - { file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75" }, - { file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178" }, - { file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e" }, - { file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f" }, - { file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73" }, - { file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf" }, - { file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820" }, - { file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca" }, - { file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91" }, - { file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6" }, - { file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12" }, - { file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc" }, - { file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092" }, - { file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77" }, - { file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385" }, - { file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972" }, - { file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16" }, - { file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6" }, - { file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa" }, - { file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689" }, - { file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57" }, - { file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f" }, - { file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599" }, - { file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5" }, - { file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987" }, - { file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04" }, - { file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022" }, - { file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569" }, - { file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a" }, - { file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc" }, - { file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3" }, - { file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf" }, - { file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe" }, - { file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5" }, - { file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471" }, - { file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589" }, - { file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae" }, - { file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d" }, - { file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f" }, - { file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511" }, - { file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a" }, - { file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8" }, - { file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e" }, - { file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172" }, - { file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b" }, - { file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b" }, - { file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92" }, - { file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22" }, - { file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f" }, - { file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32" }, - { file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce" }, - { file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db" }, - { file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b" }, - { file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857" }, - { file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11" }, - { file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1" }, - { file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862" }, - { file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691" }, + { file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a60804bff28662cbcf340a4d61598891f12eea3a66af48ecfdc975ceec21e3c8" }, + { file = "aiohttp-3.11.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4b4fa1cb5f270fb3eab079536b764ad740bb749ce69a94d4ec30ceee1b5940d5" }, + { file = "aiohttp-3.11.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:731468f555656767cda219ab42e033355fe48c85fbe3ba83a349631541715ba2" }, + { file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb23d8bb86282b342481cad4370ea0853a39e4a32a0042bb52ca6bdde132df43" }, + { file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f047569d655f81cb70ea5be942ee5d4421b6219c3f05d131f64088c73bb0917f" }, + { file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd7659baae9ccf94ae5fe8bfaa2c7bc2e94d24611528395ce88d009107e00c6d" }, + { file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af01e42ad87ae24932138f154105e88da13ce7d202a6de93fafdafb2883a00ef" }, + { file = "aiohttp-3.11.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5854be2f3e5a729800bac57a8d76af464e160f19676ab6aea74bde18ad19d438" }, + { file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6526e5fb4e14f4bbf30411216780c9967c20c5a55f2f51d3abd6de68320cc2f3" }, + { file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85992ee30a31835fc482468637b3e5bd085fa8fe9392ba0bdcbdc1ef5e9e3c55" }, + { file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:88a12ad8ccf325a8a5ed80e6d7c3bdc247d66175afedbe104ee2aaca72960d8e" }, + { file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0a6d3fbf2232e3a08c41eca81ae4f1dff3d8f1a30bae415ebe0af2d2458b8a33" }, + { file = "aiohttp-3.11.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84a585799c58b795573c7fa9b84c455adf3e1d72f19a2bf498b54a95ae0d194c" }, + { file = "aiohttp-3.11.11-cp310-cp310-win32.whl", hash = "sha256:bfde76a8f430cf5c5584553adf9926534352251d379dcb266ad2b93c54a29745" }, + { file = "aiohttp-3.11.11-cp310-cp310-win_amd64.whl", hash = "sha256:0fd82b8e9c383af11d2b26f27a478640b6b83d669440c0a71481f7c865a51da9" }, + { file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ba74ec819177af1ef7f59063c6d35a214a8fde6f987f7661f4f0eecc468a8f76" }, + { file = "aiohttp-3.11.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4af57160800b7a815f3fe0eba9b46bf28aafc195555f1824555fa2cfab6c1538" }, + { file = "aiohttp-3.11.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffa336210cf9cd8ed117011085817d00abe4c08f99968deef0013ea283547204" }, + { file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81b8fe282183e4a3c7a1b72f5ade1094ed1c6345a8f153506d114af5bf8accd9" }, + { file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3af41686ccec6a0f2bdc66686dc0f403c41ac2089f80e2214a0f82d001052c03" }, + { file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70d1f9dde0e5dd9e292a6d4d00058737052b01f3532f69c0c65818dac26dc287" }, + { file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:249cc6912405917344192b9f9ea5cd5b139d49e0d2f5c7f70bdfaf6b4dbf3a2e" }, + { file = "aiohttp-3.11.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0eb98d90b6690827dcc84c246811feeb4e1eea683c0eac6caed7549be9c84665" }, + { file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82bf1fda6cecce7f7b915f9196601a1bd1a3079796b76d16ae4cce6d0ef89b" }, + { file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9fd46ce0845cfe28f108888b3ab17abff84ff695e01e73657eec3f96d72eef34" }, + { file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd176afcf8f5d2aed50c3647d4925d0db0579d96f75a31e77cbaf67d8a87742d" }, + { file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ec2aa89305006fba9ffb98970db6c8221541be7bee4c1d027421d6f6df7d1ce2" }, + { file = "aiohttp-3.11.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:92cde43018a2e17d48bb09c79e4d4cb0e236de5063ce897a5e40ac7cb4878773" }, + { file = "aiohttp-3.11.11-cp311-cp311-win32.whl", hash = "sha256:aba807f9569455cba566882c8938f1a549f205ee43c27b126e5450dc9f83cc62" }, + { file = "aiohttp-3.11.11-cp311-cp311-win_amd64.whl", hash = "sha256:ae545f31489548c87b0cced5755cfe5a5308d00407000e72c4fa30b19c3220ac" }, + { file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e595c591a48bbc295ebf47cb91aebf9bd32f3ff76749ecf282ea7f9f6bb73886" }, + { file = "aiohttp-3.11.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3ea1b59dc06396b0b424740a10a0a63974c725b1c64736ff788a3689d36c02d2" }, + { file = "aiohttp-3.11.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8811f3f098a78ffa16e0ea36dffd577eb031aea797cbdba81be039a4169e242c" }, + { file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7227b87a355ce1f4bf83bfae4399b1f5bb42e0259cb9405824bd03d2f4336a" }, + { file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d40f9da8cabbf295d3a9dae1295c69975b86d941bc20f0a087f0477fa0a66231" }, + { file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffb3dc385f6bb1568aa974fe65da84723210e5d9707e360e9ecb51f59406cd2e" }, + { file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8f5f7515f3552d899c61202d99dcb17d6e3b0de777900405611cd747cecd1b8" }, + { file = "aiohttp-3.11.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3499c7ffbfd9c6a3d8d6a2b01c26639da7e43d47c7b4f788016226b1e711caa8" }, + { file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8e2bf8029dbf0810c7bfbc3e594b51c4cc9101fbffb583a3923aea184724203c" }, + { file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b6212a60e5c482ef90f2d788835387070a88d52cf6241d3916733c9176d39eab" }, + { file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d119fafe7b634dbfa25a8c597718e69a930e4847f0b88e172744be24515140da" }, + { file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6fba278063559acc730abf49845d0e9a9e1ba74f85f0ee6efd5803f08b285853" }, + { file = "aiohttp-3.11.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92fc484e34b733704ad77210c7957679c5c3877bd1e6b6d74b185e9320cc716e" }, + { file = "aiohttp-3.11.11-cp312-cp312-win32.whl", hash = "sha256:9f5b3c1ed63c8fa937a920b6c1bec78b74ee09593b3f5b979ab2ae5ef60d7600" }, + { file = "aiohttp-3.11.11-cp312-cp312-win_amd64.whl", hash = "sha256:1e69966ea6ef0c14ee53ef7a3d68b564cc408121ea56c0caa2dc918c1b2f553d" }, + { file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:541d823548ab69d13d23730a06f97460f4238ad2e5ed966aaf850d7c369782d9" }, + { file = "aiohttp-3.11.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:929f3ed33743a49ab127c58c3e0a827de0664bfcda566108989a14068f820194" }, + { file = "aiohttp-3.11.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0882c2820fd0132240edbb4a51eb8ceb6eef8181db9ad5291ab3332e0d71df5f" }, + { file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b63de12e44935d5aca7ed7ed98a255a11e5cb47f83a9fded7a5e41c40277d104" }, + { file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa54f8ef31d23c506910c21163f22b124facb573bff73930735cf9fe38bf7dff" }, + { file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a344d5dc18074e3872777b62f5f7d584ae4344cd6006c17ba12103759d407af3" }, + { file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b7fb429ab1aafa1f48578eb315ca45bd46e9c37de11fe45c7f5f4138091e2f1" }, + { file = "aiohttp-3.11.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c341c7d868750e31961d6d8e60ff040fb9d3d3a46d77fd85e1ab8e76c3e9a5c4" }, + { file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed9ee95614a71e87f1a70bc81603f6c6760128b140bc4030abe6abaa988f1c3d" }, + { file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de8d38f1c2810fa2a4f1d995a2e9c70bb8737b18da04ac2afbf3971f65781d87" }, + { file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a9b7371665d4f00deb8f32208c7c5e652059b0fda41cf6dbcac6114a041f1cc2" }, + { file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:620598717fce1b3bd14dd09947ea53e1ad510317c85dda2c9c65b622edc96b12" }, + { file = "aiohttp-3.11.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf8d9bfee991d8acc72d060d53860f356e07a50f0e0d09a8dfedea1c554dd0d5" }, + { file = "aiohttp-3.11.11-cp313-cp313-win32.whl", hash = "sha256:9d73ee3725b7a737ad86c2eac5c57a4a97793d9f442599bea5ec67ac9f4bdc3d" }, + { file = "aiohttp-3.11.11-cp313-cp313-win_amd64.whl", hash = "sha256:c7a06301c2fb096bdb0bd25fe2011531c1453b9f2c163c8031600ec73af1cc99" }, + { file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3e23419d832d969f659c208557de4a123e30a10d26e1e14b73431d3c13444c2e" }, + { file = "aiohttp-3.11.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21fef42317cf02e05d3b09c028712e1d73a9606f02467fd803f7c1f39cc59add" }, + { file = "aiohttp-3.11.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f21bb8d0235fc10c09ce1d11ffbd40fc50d3f08a89e4cf3a0c503dc2562247a" }, + { file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1642eceeaa5ab6c9b6dfeaaa626ae314d808188ab23ae196a34c9d97efb68350" }, + { file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2170816e34e10f2fd120f603e951630f8a112e1be3b60963a1f159f5699059a6" }, + { file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8be8508d110d93061197fd2d6a74f7401f73b6d12f8822bbcd6d74f2b55d71b1" }, + { file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4eed954b161e6b9b65f6be446ed448ed3921763cc432053ceb606f89d793927e" }, + { file = "aiohttp-3.11.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6c9af134da4bc9b3bd3e6a70072509f295d10ee60c697826225b60b9959acdd" }, + { file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44167fc6a763d534a6908bdb2592269b4bf30a03239bcb1654781adf5e49caf1" }, + { file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:479b8c6ebd12aedfe64563b85920525d05d394b85f166b7873c8bde6da612f9c" }, + { file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:10b4ff0ad793d98605958089fabfa350e8e62bd5d40aa65cdc69d6785859f94e" }, + { file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b540bd67cfb54e6f0865ceccd9979687210d7ed1a1cc8c01f8e67e2f1e883d28" }, + { file = "aiohttp-3.11.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1dac54e8ce2ed83b1f6b1a54005c87dfed139cf3f777fdc8afc76e7841101226" }, + { file = "aiohttp-3.11.11-cp39-cp39-win32.whl", hash = "sha256:568c1236b2fde93b7720f95a890741854c1200fba4a3471ff48b2934d2d93fd3" }, + { file = "aiohttp-3.11.11-cp39-cp39-win_amd64.whl", hash = "sha256:943a8b052e54dfd6439fd7989f67fc6a7f2138d0a2cf0a7de5f18aa4fe7eb3b1" }, + { file = "aiohttp-3.11.11.tar.gz", hash = "sha256:bb49c7f1e6ebf3821a42d81d494f538107610c3a705987f53068546b0e90303e" }, ] [package.dependencies] @@ -145,7 +130,8 @@ Brotli = {version = "*", optional = true, markers = "platform_python_implementat brotlicffi = {version = "*", optional = true, markers = "platform_python_implementation != \"CPython\" and extra == \"speedups\""} frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] @@ -170,13 +156,13 @@ sa = ["sqlalchemy (>=1.3,<1.4)"] [[package]] name = "aiosignal" -version = "1.3.1" +version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, + { file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5" }, + { file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54" }, ] [package.dependencies] @@ -202,13 +188,13 @@ docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] [[package]] name = "alembic" -version = "1.13.2" +version = "1.14.0" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" files = [ - { file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953" }, - { file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef" }, + { file = "alembic-1.14.0-py3-none-any.whl", hash = "sha256:99bd884ca390466db5e27ffccff1d179ec5c05c965cfefc0607e69f9e411cb25" }, + { file = "alembic-1.14.0.tar.gz", hash = "sha256:b00892b53b3642d0b8dbedba234dbf1924b69be83a9a769d5a624b01094e304b" }, ] [package.dependencies] @@ -232,187 +218,195 @@ files = [ [[package]] name = "anyio" -version = "4.4.0" +version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"}, - {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"}, + { file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352" }, + { file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48" }, ] [package.dependencies] idna = ">=2.8" sniffio = ">=1.1" +typing_extensions = { version = ">=4.5", markers = "python_version < \"3.13\"" } [package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +trio = ["trio (>=0.26.1)"] [[package]] name = "apscheduler" -version = "3.10.4" +version = "3.11.0" description = "In-process task scheduler with Cron-like capabilities" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, - {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, + { file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da" }, + { file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133" }, ] [package.dependencies] -pytz = "*" -six = ">=1.4.0" -tzlocal = ">=2.0,<3.dev0 || >=4.dev0" +tzlocal = ">=3.0" [package.extras] -doc = ["sphinx", "sphinx-rtd-theme"] +doc = ["packaging", "sphinx", "sphinx-rtd-theme (>=1.3.0)"] +etcd = ["etcd3", "protobuf (<=3.21.0)"] gevent = ["gevent"] mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=1.4)"] -testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"] +test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6", "anyio (>=4.5.2)", "gevent", "pytest", "pytz", "twisted"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] [[package]] name = "asyncmy" -version = "0.2.9" +version = "0.2.10" description = "A fast asyncio MySQL driver" optional = true -python-versions = ">=3.7,<4.0" -files = [ - {file = "asyncmy-0.2.9-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:d077eaee9a126f36bbe95e0412baa89e93172dd46193ef7bf7650a686e458e50"}, - {file = "asyncmy-0.2.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83cf951a44294626df43c5a85cf328297c3bac63f25ede216f9706514dabb322"}, - {file = "asyncmy-0.2.9-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:8a1d63c1bb8e3a09c90767199954fd423c48084a1f6c0d956217bc2e48d37d6d"}, - {file = "asyncmy-0.2.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ecad6826086e47596c6aa65dcbe221305f3d9232f0d4de11b8562ee2c55464a"}, - {file = "asyncmy-0.2.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a664d58f9ebe4132f6cb3128206392be8ad71ad6fb09a5f4a990b04ec142024"}, - {file = "asyncmy-0.2.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f2bbd7b75e2d751216f48c3b1b5092b812d70c2cd0053f8d2f50ec3f76a525a8"}, - {file = "asyncmy-0.2.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:55e3bc41aa0d4ab410fc3a1d0c31b9cdb6688cd3b0cae6f2ee49c2e7f42968be"}, - {file = "asyncmy-0.2.9-cp310-cp310-win32.whl", hash = "sha256:ea44eefc965c62bcfebf34e9ef00f6e807edf51046046767c56914243e0737e4"}, - {file = "asyncmy-0.2.9-cp310-cp310-win_amd64.whl", hash = "sha256:2b4a2a7cf0bd5051931756e765fefef3c9f9561550e0dd8b1e79308d048b710a"}, - {file = "asyncmy-0.2.9-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:e2b77f03a17a8db338d74311e38ca6dbd4ff9aacb07d2af6b9e0cac9cf1c7b87"}, - {file = "asyncmy-0.2.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c19f27b7ff0e297f2981335a85599ffe1c9a8a35c97230203321d5d6e9e4cb30"}, - {file = "asyncmy-0.2.9-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:bf18aef65ac98f5130ca588c55a83a56e74ae416cf0fe2c0757a2b597c4269d0"}, - {file = "asyncmy-0.2.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef02186cc02cb767ee5d5cf9ab002d5c7910a1a9f4c16a666867a9325c9ec5e"}, - {file = "asyncmy-0.2.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:696da0f71db0fe11e62fa58cd5a27d7c9d9a90699d13d82640755d0061da0624"}, - {file = "asyncmy-0.2.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:84d20745bb187ced05bd4072ae8b0bff4b4622efa23b79935519edb717174584"}, - {file = "asyncmy-0.2.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ea242364523f6205c4426435272bd57cbf593c20d5e5551efb28d44cfbd595c2"}, - {file = "asyncmy-0.2.9-cp311-cp311-win32.whl", hash = "sha256:47609d34e6b49fc5ad5bd2a2a593ca120e143e2a4f4206f27a543c5c598a18ca"}, - {file = "asyncmy-0.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d56df7342f7b5467a9d09a854f0e5602c8da09afdad8181ba40b0434d66d8a4"}, - {file = "asyncmy-0.2.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c2a98f225560f9a52d5bd0d2e58517639e209e5d996e9ab7470e661b39394d"}, - {file = "asyncmy-0.2.9-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:20ae3acc326b4b104949cc5e3a728a927e671f671c6f26266ad4a44f57ea9a5b"}, - {file = "asyncmy-0.2.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8171a64888453423a17ae507cd97d256541ea880b314bba16376ab9deffef6e8"}, - {file = "asyncmy-0.2.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c966de493928f26218e0bfaa284cfa609540e52841c423d7babf9ca97c9ff820"}, - {file = "asyncmy-0.2.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4321c4cb4c691689aa26a56354e3fa723d89dc2cac82751e8671b2a4e6441778"}, - {file = "asyncmy-0.2.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:cd7cde6759dbbfcc467c2af4ef3d75de0b756dde39a3d176383d8c6d9f8a34f3"}, - {file = "asyncmy-0.2.9-cp37-cp37m-win32.whl", hash = "sha256:7678d3641d5a19f20e7e19220c83405fe8616a3b437efbc494f34ad186cedcf0"}, - {file = "asyncmy-0.2.9-cp37-cp37m-win_amd64.whl", hash = "sha256:e8f48d09adf3426e7a59066eaae3c7c84c318ec56cc2f20732d652056c7a3f62"}, - {file = "asyncmy-0.2.9-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:4c4f1dc0acbaac8c3f046215031bbf3ca3d2cd7716244365325496e4f6222b78"}, - {file = "asyncmy-0.2.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:901aac048e5342acc62e1f68f6dec5aa3ed272cb2b138dca38d1c74fc414285d"}, - {file = "asyncmy-0.2.9-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:c2d4ad8817f99d9734912c2ff91c42e419031441f512b4aecd7e40a167908c1c"}, - {file = "asyncmy-0.2.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:544d3736fd6682f0201a123e4f49335420b6abf6c245abe0487f5967021f1436"}, - {file = "asyncmy-0.2.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f0c606a55625146e189534cc39038540f7a8f2c680ea82845c1f4315a9ad2914"}, - {file = "asyncmy-0.2.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:625f96371d64769b94f7f7f699cfa5be56e669828aef3698cbf4f5bb0014ccb3"}, - {file = "asyncmy-0.2.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eeeb53fdd54eef54b9793a7a5c849c5f7a2fb2540a637f21585a996ef9dd8845"}, - {file = "asyncmy-0.2.9-cp38-cp38-win32.whl", hash = "sha256:2136b749ac489c25ab3aab4a81ae6e9dfb18fd0a5ebda96cd72788c5e4d46927"}, - {file = "asyncmy-0.2.9-cp38-cp38-win_amd64.whl", hash = "sha256:d08fb8722150a9c0645665cf777916335687bddb5f37a8e02af772e330be777b"}, - {file = "asyncmy-0.2.9-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:dbee276a9c8750b522aaad86315a6ed1ffbcb9145ce89070db77831c00dd2da1"}, - {file = "asyncmy-0.2.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8755248429f9bd3d7768c71494c9943fced18f9f526f768e96f5b9b3c727c84"}, - {file = "asyncmy-0.2.9-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:64bcd5110dca7a96cb411de85ab8f79fa867e864150939b8e76286a66eab28fc"}, - {file = "asyncmy-0.2.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a83e3895bed6d44aa334deb1c343d4ffc64b0def2215149f8df2e0e13499250"}, - {file = "asyncmy-0.2.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:beb3d0e434ce0bd9e609cf5341c3b82433ef544f89055d3792186e11fa2433d9"}, - {file = "asyncmy-0.2.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dc608ff331c5d1065e2d3566493d2d9e17f36e315bd5fad3c91c421eea306edb"}, - {file = "asyncmy-0.2.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:02caedc00035b2bd0be5555ef61d83ee9cb356ab488ac40072630ba224af02b0"}, - {file = "asyncmy-0.2.9-cp39-cp39-win32.whl", hash = "sha256:5b944d9cdf7ce25b396cd1e0c9319ba24c6583bde7a5dd31157614f3b9cc5b2f"}, - {file = "asyncmy-0.2.9-cp39-cp39-win_amd64.whl", hash = "sha256:3ceb59b9307b5eb893f4d473fcbc43ac0321ffb0436e0115b20cc2e0baa44eb5"}, - {file = "asyncmy-0.2.9-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9f1ca623517552a637900b90d65b5bafc9c67bebf96e3427eecb9359ffa24b1"}, - {file = "asyncmy-0.2.9-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:49622dc4ec69b5a4cbddb3695a1e9249b31092c6f19604abb664b43dcb509b6f"}, - {file = "asyncmy-0.2.9-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8412e825443ee876ef0d55ac4356b56173f5cb64ca8e4638974f8cf5c912a63"}, - {file = "asyncmy-0.2.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4025db2a27b1d84d3c68b5d5aacecac17258b69f25ec8a8c350c5f666003a778"}, - {file = "asyncmy-0.2.9-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7640f3357849b176364ed546908e28c8460701ddc0d23cc3fa7113ec52a076"}, - {file = "asyncmy-0.2.9-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:d2593717fa7a92a7d361444726292ce34edea76d5aa67d469b5efeee1c9b729e"}, - {file = "asyncmy-0.2.9-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9f22e13bd77277593b56de2e4b65c40c2e81b1a42c4845d062403c5c5bc52bc"}, - {file = "asyncmy-0.2.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:a4aa17cc6ac0f7bc6b72e08d112566e69a36e2e1ebebad43d699757b7b4ff028"}, - {file = "asyncmy-0.2.9-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e6f5205722e67c910510e294ad483bdafa7e29d5cf455d49ffa4b819e55fd8"}, - {file = "asyncmy-0.2.9-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:1021796f1910a0c2ab2d878f8f5d56f939ef0681f9c1fe925b78161cad2f8297"}, - {file = "asyncmy-0.2.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b1dd463bb054138bd1fd3fec9911eb618e92f54f61abb476658f863340394d1"}, - {file = "asyncmy-0.2.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ad06f3c02d455947e95087d29f7122411208f0eadaf8671772fe5bad97d9873a"}, - {file = "asyncmy-0.2.9.tar.gz", hash = "sha256:da188be013291d1f831d63cdd3614567f4c63bfdcde73631ddff8df00c56d614"}, +python-versions = "<4.0,>=3.8" +files = [ + { file = "asyncmy-0.2.10-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:c2237c8756b8f374099bd320c53b16f7ec0cee8258f00d72eed5a2cd3d251066" }, + { file = "asyncmy-0.2.10-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6e98d4fbf7ea0d99dfecb24968c9c350b019397ba1af9f181d51bb0f6f81919b" }, + { file = "asyncmy-0.2.10-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b1b1ee03556c7eda6422afc3aca132982a84706f8abf30f880d642f50670c7ed" }, + { file = "asyncmy-0.2.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e2b97672ea3f0b335c0ffd3da1a5727b530f82f5032cd87e86c3aa3ac6df7f3" }, + { file = "asyncmy-0.2.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c6471ce1f9ae1e6f0d55adfb57c49d0bcf5753a253cccbd33799ddb402fe7da2" }, + { file = "asyncmy-0.2.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:10e2a10fe44a2b216a1ae58fbdafa3fed661a625ec3c030c560c26f6ab618522" }, + { file = "asyncmy-0.2.10-cp310-cp310-win32.whl", hash = "sha256:a791ab117787eb075bc37ed02caa7f3e30cca10f1b09ec7eeb51d733df1d49fc" }, + { file = "asyncmy-0.2.10-cp310-cp310-win_amd64.whl", hash = "sha256:bd16fdc0964a4a1a19aec9797ca631c3ff2530013fdcd27225fc2e48af592804" }, + { file = "asyncmy-0.2.10-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:7af0f1f31f800a8789620c195e92f36cce4def68ee70d625534544d43044ed2a" }, + { file = "asyncmy-0.2.10-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:800116ab85dc53b24f484fb644fefffac56db7367a31e7d62f4097d495105a2c" }, + { file = "asyncmy-0.2.10-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:39525e9d7e557b83db268ed14b149a13530e0d09a536943dba561a8a1c94cc07" }, + { file = "asyncmy-0.2.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76e199d6b57918999efc702d2dbb182cb7ba8c604cdfc912517955219b16eaea" }, + { file = "asyncmy-0.2.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9ca8fdd7dbbf2d9b4c2d3a5fac42b058707d6a483b71fded29051b8ae198a250" }, + { file = "asyncmy-0.2.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0df23db54e38602c803dacf1bbc1dcc4237a87223e659681f00d1a319a4f3826" }, + { file = "asyncmy-0.2.10-cp311-cp311-win32.whl", hash = "sha256:a16633032be020b931acfd7cd1862c7dad42a96ea0b9b28786f2ec48e0a86757" }, + { file = "asyncmy-0.2.10-cp311-cp311-win_amd64.whl", hash = "sha256:cca06212575922216b89218abd86a75f8f7375fc9c28159ea469f860785cdbc7" }, + { file = "asyncmy-0.2.10-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:42295530c5f36784031f7fa42235ef8dd93a75d9b66904de087e68ff704b4f03" }, + { file = "asyncmy-0.2.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:641a853ffcec762905cbeceeb623839c9149b854d5c3716eb9a22c2b505802af" }, + { file = "asyncmy-0.2.10-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:c554874223dd36b1cfc15e2cd0090792ea3832798e8fe9e9d167557e9cf31b4d" }, + { file = "asyncmy-0.2.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd16e84391dde8edb40c57d7db634706cbbafb75e6a01dc8b68a63f8dd9e44ca" }, + { file = "asyncmy-0.2.10-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9f6b44c4bf4bb69a2a1d9d26dee302473099105ba95283b479458c448943ed3c" }, + { file = "asyncmy-0.2.10-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:16d398b1aad0550c6fe1655b6758455e3554125af8aaf1f5abdc1546078c7257" }, + { file = "asyncmy-0.2.10-cp312-cp312-win32.whl", hash = "sha256:59d2639dcc23939ae82b93b40a683c15a091460a3f77fa6aef1854c0a0af99cc" }, + { file = "asyncmy-0.2.10-cp312-cp312-win_amd64.whl", hash = "sha256:4c6674073be97ffb7ac7f909e803008b23e50281131fef4e30b7b2162141a574" }, + { file = "asyncmy-0.2.10-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:85bc4522d8b632cd3327001a00cb24416883fc3905857737b99aa00bc0703fe1" }, + { file = "asyncmy-0.2.10-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:c93768dde803c7c118e6ac1893f98252e48fecad7c20bb7e27d4bdf3d130a044" }, + { file = "asyncmy-0.2.10-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:93b6d7db19a093abdeceb454826ff752ce1917288635d5d63519068ef5b2f446" }, + { file = "asyncmy-0.2.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acecd4bbb513a67a94097fd499dac854546e07d2ff63c7fb5f4d2c077e4bdf91" }, + { file = "asyncmy-0.2.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1b4b346c02fca1d160005d4921753bb00ed03422f0c6ec90936c43aad96b7d52" }, + { file = "asyncmy-0.2.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8d393570e1c96ca200075797cc4f80849fc0ea960a45c6035855b1d392f33768" }, + { file = "asyncmy-0.2.10-cp38-cp38-win32.whl", hash = "sha256:c8ee5282af5f38b4dc3ae94a3485688bd6c0d3509ba37226dbaa187f1708e32c" }, + { file = "asyncmy-0.2.10-cp38-cp38-win_amd64.whl", hash = "sha256:10b3dfb119d7a9cb3aaae355c0981e60934f57297ea560bfdb280c5d85f77a9d" }, + { file = "asyncmy-0.2.10-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:244289bd1bea84384866bde50b09fe5b24856640e30a04073eacb71987b7b6ad" }, + { file = "asyncmy-0.2.10-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:6c9d024b160b9f869a21e62c4ef34a7b7a4b5a886ae03019d4182621ea804d2c" }, + { file = "asyncmy-0.2.10-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b57594eea942224626203503f24fa88a47eaab3f13c9f24435091ea910f4b966" }, + { file = "asyncmy-0.2.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:346192941470ac2d315f97afa14c0131ff846c911da14861baf8a1f8ed541664" }, + { file = "asyncmy-0.2.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:957c2b48c5228e5f91fdf389daf38261a7b8989ad0eb0d1ba4e5680ef2a4a078" }, + { file = "asyncmy-0.2.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:472989d7bfa405c108a7f3c408bbed52306504fb3aa28963d833cb7eeaafece0" }, + { file = "asyncmy-0.2.10-cp39-cp39-win32.whl", hash = "sha256:714b0fdadd72031e972de2bbbd14e35a19d5a7e001594f0c8a69f92f0d05acc9" }, + { file = "asyncmy-0.2.10-cp39-cp39-win_amd64.whl", hash = "sha256:9fb58645d3da0b91db384f8519b16edc7dc421c966ada8647756318915d63696" }, + { file = "asyncmy-0.2.10-pp310-pypy310_pp73-macosx_13_0_x86_64.whl", hash = "sha256:f10c977c60a95bd6ec6b8654e20c8f53bad566911562a7ad7117ca94618f05d3" }, + { file = "asyncmy-0.2.10-pp310-pypy310_pp73-macosx_14_0_arm64.whl", hash = "sha256:aab07fbdb9466beaffef136ffabe388f0d295d8d2adb8f62c272f1d4076515b9" }, + { file = "asyncmy-0.2.10-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:63144322ade68262201baae73ad0c8a06b98a3c6ae39d1f3f21c41cc5287066a" }, + { file = "asyncmy-0.2.10-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9659d95c6f2a611aec15bdd928950df937bf68bc4bbb68b809ee8924b6756067" }, + { file = "asyncmy-0.2.10-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8ced4bd938e95ede0fb9fa54755773df47bdb9f29f142512501e613dd95cf4a4" }, + { file = "asyncmy-0.2.10-pp38-pypy38_pp73-macosx_13_0_x86_64.whl", hash = "sha256:f76080d5d360635f0c67411fb3fb890d7a5a9e31135b4bb07c6a4e588287b671" }, + { file = "asyncmy-0.2.10-pp38-pypy38_pp73-macosx_14_0_arm64.whl", hash = "sha256:fde04da1a3e656ec7d7656b2d02ade87df9baf88cc1ebeff5d2288f856c086a4" }, + { file = "asyncmy-0.2.10-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:a83383cc6951bcde11c9cdda216a0849d29be2002a8fb6405ea6d9e5ced4ec69" }, + { file = "asyncmy-0.2.10-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58c3d8c12030c23df93929c8371da818211fa02c7b50cd178960c0a88e538adf" }, + { file = "asyncmy-0.2.10-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e0c8706ff7fc003775f3fc63804ea45be61e9ac9df9fd968977f781189d625ed" }, + { file = "asyncmy-0.2.10-pp39-pypy39_pp73-macosx_13_0_x86_64.whl", hash = "sha256:4651caaee6f4d7a8eb478a0dc460f8e91ab09a2d8d32444bc2b235544c791947" }, + { file = "asyncmy-0.2.10-pp39-pypy39_pp73-macosx_14_0_arm64.whl", hash = "sha256:ac091b327f01c38d91c697c810ba49e5f836890d48f6879ba0738040bb244290" }, + { file = "asyncmy-0.2.10-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:e1d2d9387cd3971297486c21098e035c620149c9033369491f58fe4fc08825b6" }, + { file = "asyncmy-0.2.10-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a760cb486ddb2c936711325236e6b9213564a9bb5deb2f6949dbd16c8e4d739e" }, + { file = "asyncmy-0.2.10-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1586f26633c05b16bcfc46d86e9875f4941280e12afa79a741cdf77ae4ccfb4d" }, + { file = "asyncmy-0.2.10.tar.gz", hash = "sha256:f4b67edadf7caa56bdaf1c2e6cf451150c0a86f5353744deabe4426fe27aff4e" }, ] [[package]] name = "asyncpg" -version = "0.29.0" +version = "0.30.0" description = "An asyncio PostgreSQL driver" optional = true python-versions = ">=3.8.0" files = [ - {file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"}, - {file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"}, - {file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"}, - {file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"}, - {file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"}, - {file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"}, - {file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"}, - {file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"}, - {file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"}, - {file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"}, - {file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"}, - {file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"}, - {file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"}, - {file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"}, - {file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"}, - {file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"}, - {file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"}, - {file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"}, - {file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"}, - {file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"}, - {file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"}, - {file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"}, - {file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"}, - {file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"}, - {file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"}, - {file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"}, - {file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"}, + { file = "asyncpg-0.30.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bfb4dd5ae0699bad2b233672c8fc5ccbd9ad24b89afded02341786887e37927e" }, + { file = "asyncpg-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc1f62c792752a49f88b7e6f774c26077091b44caceb1983509edc18a2222ec0" }, + { file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3152fef2e265c9c24eec4ee3d22b4f4d2703d30614b0b6753e9ed4115c8a146f" }, + { file = "asyncpg-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7255812ac85099a0e1ffb81b10dc477b9973345793776b128a23e60148dd1af" }, + { file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:578445f09f45d1ad7abddbff2a3c7f7c291738fdae0abffbeb737d3fc3ab8b75" }, + { file = "asyncpg-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c42f6bb65a277ce4d93f3fba46b91a265631c8df7250592dd4f11f8b0152150f" }, + { file = "asyncpg-0.30.0-cp310-cp310-win32.whl", hash = "sha256:aa403147d3e07a267ada2ae34dfc9324e67ccc4cdca35261c8c22792ba2b10cf" }, + { file = "asyncpg-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb622c94db4e13137c4c7f98834185049cc50ee01d8f657ef898b6407c7b9c50" }, + { file = "asyncpg-0.30.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5e0511ad3dec5f6b4f7a9e063591d407eee66b88c14e2ea636f187da1dcfff6a" }, + { file = "asyncpg-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:915aeb9f79316b43c3207363af12d0e6fd10776641a7de8a01212afd95bdf0ed" }, + { file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c198a00cce9506fcd0bf219a799f38ac7a237745e1d27f0e1f66d3707c84a5a" }, + { file = "asyncpg-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3326e6d7381799e9735ca2ec9fd7be4d5fef5dcbc3cb555d8a463d8460607956" }, + { file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:51da377487e249e35bd0859661f6ee2b81db11ad1f4fc036194bc9cb2ead5056" }, + { file = "asyncpg-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc6d84136f9c4d24d358f3b02be4b6ba358abd09f80737d1ac7c444f36108454" }, + { file = "asyncpg-0.30.0-cp311-cp311-win32.whl", hash = "sha256:574156480df14f64c2d76450a3f3aaaf26105869cad3865041156b38459e935d" }, + { file = "asyncpg-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:3356637f0bd830407b5597317b3cb3571387ae52ddc3bca6233682be88bbbc1f" }, + { file = "asyncpg-0.30.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c902a60b52e506d38d7e80e0dd5399f657220f24635fee368117b8b5fce1142e" }, + { file = "asyncpg-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aca1548e43bbb9f0f627a04666fedaca23db0a31a84136ad1f868cb15deb6e3a" }, + { file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c2a2ef565400234a633da0eafdce27e843836256d40705d83ab7ec42074efb3" }, + { file = "asyncpg-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1292b84ee06ac8a2ad8e51c7475aa309245874b61333d97411aab835c4a2f737" }, + { file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5712350388d0cd0615caec629ad53c81e506b1abaaf8d14c93f54b35e3595a" }, + { file = "asyncpg-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db9891e2d76e6f425746c5d2da01921e9a16b5a71a1c905b13f30e12a257c4af" }, + { file = "asyncpg-0.30.0-cp312-cp312-win32.whl", hash = "sha256:68d71a1be3d83d0570049cd1654a9bdfe506e794ecc98ad0873304a9f35e411e" }, + { file = "asyncpg-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:9a0292c6af5c500523949155ec17b7fe01a00ace33b68a476d6b5059f9630305" }, + { file = "asyncpg-0.30.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70" }, + { file = "asyncpg-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3" }, + { file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33" }, + { file = "asyncpg-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4" }, + { file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4" }, + { file = "asyncpg-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba" }, + { file = "asyncpg-0.30.0-cp313-cp313-win32.whl", hash = "sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590" }, + { file = "asyncpg-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e" }, + { file = "asyncpg-0.30.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:29ff1fc8b5bf724273782ff8b4f57b0f8220a1b2324184846b39d1ab4122031d" }, + { file = "asyncpg-0.30.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64e899bce0600871b55368b8483e5e3e7f1860c9482e7f12e0a771e747988168" }, + { file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b290f4726a887f75dcd1b3006f484252db37602313f806e9ffc4e5996cfe5cb" }, + { file = "asyncpg-0.30.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f86b0e2cd3f1249d6fe6fd6cfe0cd4538ba994e2d8249c0491925629b9104d0f" }, + { file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:393af4e3214c8fa4c7b86da6364384c0d1b3298d45803375572f415b6f673f38" }, + { file = "asyncpg-0.30.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fd4406d09208d5b4a14db9a9dbb311b6d7aeeab57bded7ed2f8ea41aeef39b34" }, + { file = "asyncpg-0.30.0-cp38-cp38-win32.whl", hash = "sha256:0b448f0150e1c3b96cb0438a0d0aa4871f1472e58de14a3ec320dbb2798fb0d4" }, + { file = "asyncpg-0.30.0-cp38-cp38-win_amd64.whl", hash = "sha256:f23b836dd90bea21104f69547923a02b167d999ce053f3d502081acea2fba15b" }, + { file = "asyncpg-0.30.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f4e83f067b35ab5e6371f8a4c93296e0439857b4569850b178a01385e82e9ad" }, + { file = "asyncpg-0.30.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5df69d55add4efcd25ea2a3b02025b669a285b767bfbf06e356d68dbce4234ff" }, + { file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3479a0d9a852c7c84e822c073622baca862d1217b10a02dd57ee4a7a081f708" }, + { file = "asyncpg-0.30.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26683d3b9a62836fad771a18ecf4659a30f348a561279d6227dab96182f46144" }, + { file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1b982daf2441a0ed314bd10817f1606f1c28b1136abd9e4f11335358c2c631cb" }, + { file = "asyncpg-0.30.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1c06a3a50d014b303e5f6fc1e5f95eb28d2cee89cf58384b700da621e5d5e547" }, + { file = "asyncpg-0.30.0-cp39-cp39-win32.whl", hash = "sha256:1b11a555a198b08f5c4baa8f8231c74a366d190755aa4f99aacec5970afe929a" }, + { file = "asyncpg-0.30.0-cp39-cp39-win_amd64.whl", hash = "sha256:8b684a3c858a83cd876f05958823b68e8d14ec01bb0c0d14a6704c5bf9711773" }, + { file = "asyncpg-0.30.0.tar.gz", hash = "sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851" }, ] [package.extras] -docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"] +docs = ["Sphinx (>=8.1.3,<8.2.0)", "sphinx-rtd-theme (>=1.2.2)"] +gssauth = ["gssapi", "sspilib"] +test = ["distro (>=1.9.0,<1.10.0)", "flake8 (>=6.1,<7.0)", "flake8-pyi (>=24.1.0,<24.2.0)", "gssapi", "k5test", "mypy (>=1.8.0,<1.9.0)", "sspilib", "uvloop (>=0.15.3)"] [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - { file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" }, - { file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346" }, + { file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308" }, + { file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff" }, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] @@ -567,89 +561,89 @@ typing-extensions = "*" [[package]] name = "certifi" -version = "2024.8.30" +version = "2024.12.14" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - { file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8" }, - { file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" }, + { file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56" }, + { file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db" }, ] [[package]] name = "cffi" -version = "1.17.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - { file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb" }, - { file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a" }, - { file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42" }, - { file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d" }, - { file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2" }, - { file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab" }, - { file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b" }, - { file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206" }, - { file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa" }, - { file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f" }, - { file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc" }, - { file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2" }, - { file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720" }, - { file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9" }, - { file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb" }, - { file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424" }, - { file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d" }, - { file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8" }, - { file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6" }, - { file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91" }, - { file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8" }, - { file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb" }, - { file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9" }, - { file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0" }, - { file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc" }, - { file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59" }, - { file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb" }, - { file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195" }, - { file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e" }, - { file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828" }, - { file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150" }, - { file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a" }, - { file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885" }, - { file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492" }, - { file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2" }, - { file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118" }, - { file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7" }, - { file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377" }, - { file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb" }, - { file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555" }, - { file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204" }, - { file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f" }, - { file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0" }, - { file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4" }, - { file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a" }, - { file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7" }, - { file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c" }, - { file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e" }, - { file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b" }, - { file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e" }, - { file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401" }, - { file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c" }, - { file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499" }, - { file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c" }, - { file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2" }, - { file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759" }, - { file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4" }, - { file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82" }, - { file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf" }, - { file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058" }, - { file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932" }, - { file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693" }, - { file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3" }, - { file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4" }, - { file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb" }, - { file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29" }, - { file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76" }, + { file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14" }, + { file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67" }, + { file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382" }, + { file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702" }, + { file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3" }, + { file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6" }, + { file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17" }, + { file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8" }, + { file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e" }, + { file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be" }, + { file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c" }, + { file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15" }, + { file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401" }, + { file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf" }, + { file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4" }, + { file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41" }, + { file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1" }, + { file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6" }, + { file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d" }, + { file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6" }, + { file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f" }, + { file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" }, + { file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655" }, + { file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0" }, + { file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4" }, + { file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c" }, + { file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36" }, + { file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5" }, + { file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff" }, + { file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99" }, + { file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93" }, + { file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3" }, + { file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8" }, + { file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65" }, + { file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903" }, + { file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e" }, + { file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2" }, + { file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3" }, + { file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683" }, + { file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5" }, + { file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4" }, + { file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd" }, + { file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed" }, + { file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9" }, + { file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d" }, + { file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a" }, + { file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b" }, + { file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964" }, + { file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9" }, + { file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc" }, + { file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c" }, + { file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1" }, + { file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8" }, + { file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1" }, + { file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16" }, + { file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36" }, + { file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8" }, + { file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576" }, + { file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87" }, + { file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0" }, + { file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3" }, + { file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595" }, + { file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a" }, + { file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e" }, + { file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7" }, + { file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662" }, + { file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824" }, ] [package.dependencies] @@ -657,13 +651,13 @@ pycparser = "*" [[package]] name = "click" -version = "8.1.7" +version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, + { file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2" }, + { file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" }, ] [package.dependencies] @@ -682,76 +676,65 @@ files = [ [[package]] name = "contourpy" -version = "1.3.0" +version = "1.3.1" description = "Python library for calculating contours of 2D quadrilateral grids" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - { file = "contourpy-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7" }, - { file = "contourpy-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42" }, - { file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7" }, - { file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab" }, - { file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589" }, - { file = "contourpy-1.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41" }, - { file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d" }, - { file = "contourpy-1.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223" }, - { file = "contourpy-1.3.0-cp310-cp310-win32.whl", hash = "sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f" }, - { file = "contourpy-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b" }, - { file = "contourpy-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad" }, - { file = "contourpy-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49" }, - { file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66" }, - { file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081" }, - { file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1" }, - { file = "contourpy-1.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d" }, - { file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c" }, - { file = "contourpy-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb" }, - { file = "contourpy-1.3.0-cp311-cp311-win32.whl", hash = "sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c" }, - { file = "contourpy-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67" }, - { file = "contourpy-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f" }, - { file = "contourpy-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6" }, - { file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639" }, - { file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c" }, - { file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06" }, - { file = "contourpy-1.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09" }, - { file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd" }, - { file = "contourpy-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35" }, - { file = "contourpy-1.3.0-cp312-cp312-win32.whl", hash = "sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb" }, - { file = "contourpy-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b" }, - { file = "contourpy-1.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3" }, - { file = "contourpy-1.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7" }, - { file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84" }, - { file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0" }, - { file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b" }, - { file = "contourpy-1.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da" }, - { file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14" }, - { file = "contourpy-1.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8" }, - { file = "contourpy-1.3.0-cp313-cp313-win32.whl", hash = "sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294" }, - { file = "contourpy-1.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087" }, - { file = "contourpy-1.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8" }, - { file = "contourpy-1.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b" }, - { file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973" }, - { file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18" }, - { file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8" }, - { file = "contourpy-1.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6" }, - { file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2" }, - { file = "contourpy-1.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927" }, - { file = "contourpy-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8" }, - { file = "contourpy-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c" }, - { file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca" }, - { file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f" }, - { file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc" }, - { file = "contourpy-1.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2" }, - { file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e" }, - { file = "contourpy-1.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800" }, - { file = "contourpy-1.3.0-cp39-cp39-win32.whl", hash = "sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5" }, - { file = "contourpy-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843" }, - { file = "contourpy-1.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c" }, - { file = "contourpy-1.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779" }, - { file = "contourpy-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4" }, - { file = "contourpy-1.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0" }, - { file = "contourpy-1.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102" }, - { file = "contourpy-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb" }, - { file = "contourpy-1.3.0.tar.gz", hash = "sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4" }, + { file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab" }, + { file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124" }, + { file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1" }, + { file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b" }, + { file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453" }, + { file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3" }, + { file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277" }, + { file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595" }, + { file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697" }, + { file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e" }, + { file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b" }, + { file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc" }, + { file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86" }, + { file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6" }, + { file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85" }, + { file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c" }, + { file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291" }, + { file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f" }, + { file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375" }, + { file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9" }, + { file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509" }, + { file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc" }, + { file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454" }, + { file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80" }, + { file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec" }, + { file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9" }, + { file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b" }, + { file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d" }, + { file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e" }, + { file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d" }, + { file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2" }, + { file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5" }, + { file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81" }, + { file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2" }, + { file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7" }, + { file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c" }, + { file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3" }, + { file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1" }, + { file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82" }, + { file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd" }, + { file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30" }, + { file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751" }, + { file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342" }, + { file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c" }, + { file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f" }, + { file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda" }, + { file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242" }, + { file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1" }, + { file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1" }, + { file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546" }, + { file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6" }, + { file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750" }, + { file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53" }, + { file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699" }, ] [package.dependencies] @@ -764,6 +747,55 @@ mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pil test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"] +[[package]] +name = "cryptography" +version = "44.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + { file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123" }, + { file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092" }, + { file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f" }, + { file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb" }, + { file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b" }, + { file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543" }, + { file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e" }, + { file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e" }, + { file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053" }, + { file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd" }, + { file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591" }, + { file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7" }, + { file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc" }, + { file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289" }, + { file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7" }, + { file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c" }, + { file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64" }, + { file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285" }, + { file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417" }, + { file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede" }, + { file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731" }, + { file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4" }, + { file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756" }, + { file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c" }, + { file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa" }, + { file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c" }, + { file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02" }, +] + +[package.dependencies] +cffi = { version = ">=1.12", markers = "platform_python_implementation != \"PyPy\"" } + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "cssselect" version = "1.2.0" @@ -792,46 +824,57 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"] [[package]] name = "emoji" -version = "2.12.1" +version = "2.14.0" description = "Emoji for Python" optional = false python-versions = ">=3.7" files = [ - {file = "emoji-2.12.1-py3-none-any.whl", hash = "sha256:a00d62173bdadc2510967a381810101624a2f0986145b8da0cffa42e29430235"}, - {file = "emoji-2.12.1.tar.gz", hash = "sha256:4aa0488817691aa58d83764b6c209f8a27c0b3ab3f89d1b8dceca1a62e4973eb"}, + { file = "emoji-2.14.0-py3-none-any.whl", hash = "sha256:fcc936bf374b1aec67dda5303ae99710ba88cc9cdce2d1a71c5f2204e6d78799" }, + { file = "emoji-2.14.0.tar.gz", hash = "sha256:f68ac28915a2221667cddb3e6c589303c3c6954c6c5af6fefaec7f9bdf72fdca" }, ] -[package.dependencies] -typing-extensions = ">=4.7.0" - [package.extras] dev = ["coverage", "pytest (>=7.4.4)"] [[package]] name = "et-xmlfile" -version = "1.1.0" +version = "2.0.0" description = "An implementation of lxml.xmlfile for the standard library" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, - {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, + { file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa" }, + { file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54" }, ] +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + { file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b" }, + { file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" }, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "fastapi" -version = "0.112.2" +version = "0.115.6" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - { file = "fastapi-0.112.2-py3-none-any.whl", hash = "sha256:db84b470bd0e2b1075942231e90e3577e12a903c4dc8696f0d206a7904a7af1c" }, - { file = "fastapi-0.112.2.tar.gz", hash = "sha256:3d4729c038414d5193840706907a41839d839523da6ed0c2811f1168cac1798c" }, + { file = "fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305" }, + { file = "fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654" }, ] [package.dependencies] pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.37.2,<0.39.0" +starlette = ">=0.40.0,<0.42.0" typing-extensions = ">=4.8.0" [package.extras] @@ -840,53 +883,61 @@ standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "htt [[package]] name = "fonttools" -version = "4.53.1" +version = "4.55.3" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - { file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397" }, - { file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3" }, - { file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d" }, - { file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0" }, - { file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41" }, - { file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f" }, - { file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4" }, - { file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671" }, - { file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1" }, - { file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923" }, - { file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719" }, - { file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3" }, - { file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb" }, - { file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2" }, - { file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88" }, - { file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02" }, - { file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58" }, - { file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8" }, - { file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60" }, - { file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f" }, - { file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2" }, - { file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f" }, - { file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670" }, - { file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab" }, - { file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749" }, - { file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2" }, - { file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb" }, - { file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f" }, - { file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d" }, - { file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169" }, - { file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d" }, - { file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8" }, - { file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a" }, - { file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31" }, - { file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c" }, - { file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407" }, - { file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb" }, - { file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122" }, - { file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb" }, - { file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb" }, - { file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d" }, - { file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4" }, + { file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0" }, + { file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f" }, + { file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841" }, + { file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674" }, + { file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276" }, + { file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5" }, + { file = "fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261" }, + { file = "fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5" }, + { file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e" }, + { file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b" }, + { file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90" }, + { file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0" }, + { file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b" }, + { file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765" }, + { file = "fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f" }, + { file = "fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72" }, + { file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35" }, + { file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c" }, + { file = "fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7" }, + { file = "fonttools-4.55.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314" }, + { file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427" }, + { file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a" }, + { file = "fonttools-4.55.3-cp312-cp312-win32.whl", hash = "sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07" }, + { file = "fonttools-4.55.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54" }, + { file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29" }, + { file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4" }, + { file = "fonttools-4.55.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca" }, + { file = "fonttools-4.55.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b" }, + { file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048" }, + { file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe" }, + { file = "fonttools-4.55.3-cp313-cp313-win32.whl", hash = "sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628" }, + { file = "fonttools-4.55.3-cp313-cp313-win_amd64.whl", hash = "sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b" }, + { file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:caf8230f3e10f8f5d7593eb6d252a37caf58c480b19a17e250a63dad63834cf3" }, + { file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b586ab5b15b6097f2fb71cafa3c98edfd0dba1ad8027229e7b1e204a58b0e09d" }, + { file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8c2794ded89399cc2169c4d0bf7941247b8d5932b2659e09834adfbb01589aa" }, + { file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf4fe7c124aa3f4e4c1940880156e13f2f4d98170d35c749e6b4f119a872551e" }, + { file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:86721fbc389ef5cc1e2f477019e5069e8e4421e8d9576e9c26f840dbb04678de" }, + { file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:89bdc5d88bdeec1b15af790810e267e8332d92561dce4f0748c2b95c9bdf3926" }, + { file = "fonttools-4.55.3-cp38-cp38-win32.whl", hash = "sha256:bc5dbb4685e51235ef487e4bd501ddfc49be5aede5e40f4cefcccabc6e60fb4b" }, + { file = "fonttools-4.55.3-cp38-cp38-win_amd64.whl", hash = "sha256:cd70de1a52a8ee2d1877b6293af8a2484ac82514f10b1c67c1c5762d38073e56" }, + { file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bdcc9f04b36c6c20978d3f060e5323a43f6222accc4e7fcbef3f428e216d96af" }, + { file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c3ca99e0d460eff46e033cd3992a969658c3169ffcd533e0a39c63a38beb6831" }, + { file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22f38464daa6cdb7b6aebd14ab06609328fe1e9705bb0fcc7d1e69de7109ee02" }, + { file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed63959d00b61959b035c7d47f9313c2c1ece090ff63afea702fe86de00dbed4" }, + { file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5e8d657cd7326eeaba27de2740e847c6b39dde2f8d7cd7cc56f6aad404ddf0bd" }, + { file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb594b5a99943042c702c550d5494bdd7577f6ef19b0bc73877c948a63184a32" }, + { file = "fonttools-4.55.3-cp39-cp39-win32.whl", hash = "sha256:dc5294a3d5c84226e3dbba1b6f61d7ad813a8c0238fceea4e09aa04848c3d851" }, + { file = "fonttools-4.55.3-cp39-cp39-win_amd64.whl", hash = "sha256:aedbeb1db64496d098e6be92b2e63b5fac4e53b1b92032dfc6988e1ea9134a4d" }, + { file = "fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977" }, + { file = "fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45" }, ] [package.extras] @@ -905,155 +956,185 @@ woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] [[package]] name = "frozenlist" -version = "1.4.1" +version = "1.5.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false python-versions = ">=3.8" files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, + { file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a" }, + { file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb" }, + { file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec" }, + { file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5" }, + { file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76" }, + { file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17" }, + { file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba" }, + { file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d" }, + { file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2" }, + { file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f" }, + { file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c" }, + { file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab" }, + { file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5" }, + { file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb" }, + { file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4" }, + { file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30" }, + { file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5" }, + { file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778" }, + { file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a" }, + { file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869" }, + { file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d" }, + { file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45" }, + { file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d" }, + { file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3" }, + { file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a" }, + { file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9" }, + { file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2" }, + { file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf" }, + { file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942" }, + { file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d" }, + { file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21" }, + { file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d" }, + { file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e" }, + { file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a" }, + { file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a" }, + { file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee" }, + { file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6" }, + { file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e" }, + { file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9" }, + { file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039" }, + { file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784" }, + { file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631" }, + { file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f" }, + { file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8" }, + { file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f" }, + { file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953" }, + { file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0" }, + { file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2" }, + { file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f" }, + { file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608" }, + { file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b" }, + { file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840" }, + { file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439" }, + { file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de" }, + { file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641" }, + { file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e" }, + { file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9" }, + { file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03" }, + { file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c" }, + { file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28" }, + { file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca" }, + { file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10" }, + { file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604" }, + { file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3" }, + { file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307" }, + { file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10" }, + { file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9" }, + { file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99" }, + { file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c" }, + { file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171" }, + { file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e" }, + { file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf" }, + { file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e" }, + { file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723" }, + { file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923" }, + { file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972" }, + { file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336" }, + { file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f" }, + { file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f" }, + { file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6" }, + { file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411" }, + { file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08" }, + { file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2" }, + { file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d" }, + { file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b" }, + { file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b" }, + { file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0" }, + { file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c" }, + { file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3" }, + { file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0" }, + { file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3" }, + { file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817" }, ] [[package]] name = "greenlet" -version = "3.0.3" +version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, + { file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563" }, + { file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83" }, + { file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0" }, + { file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120" }, + { file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc" }, + { file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617" }, + { file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7" }, + { file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6" }, + { file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80" }, + { file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70" }, + { file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159" }, + { file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e" }, + { file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1" }, + { file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383" }, + { file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a" }, + { file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511" }, + { file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395" }, + { file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39" }, + { file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d" }, + { file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79" }, + { file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa" }, + { file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441" }, + { file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36" }, + { file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9" }, + { file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0" }, + { file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942" }, + { file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01" }, + { file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1" }, + { file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff" }, + { file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a" }, + { file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e" }, + { file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4" }, + { file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e" }, + { file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1" }, + { file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c" }, + { file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761" }, + { file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011" }, + { file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13" }, + { file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475" }, + { file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b" }, + { file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822" }, + { file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01" }, + { file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6" }, + { file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291" }, + { file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981" }, + { file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803" }, + { file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc" }, + { file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de" }, + { file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa" }, + { file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af" }, + { file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798" }, + { file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef" }, + { file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9" }, + { file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111" }, + { file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81" }, + { file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba" }, + { file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8" }, + { file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1" }, + { file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd" }, + { file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7" }, + { file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef" }, + { file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d" }, + { file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3" }, + { file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42" }, + { file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f" }, + { file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437" }, + { file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145" }, + { file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c" }, + { file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e" }, + { file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e" }, + { file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c" }, + { file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22" }, + { file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467" }, ] [package.extras] @@ -1099,13 +1180,13 @@ files = [ [[package]] name = "httpcore" -version = "1.0.5" +version = "1.0.7" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"}, - {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"}, + { file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd" }, + { file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c" }, ] [package.dependencies] @@ -1116,65 +1197,72 @@ h11 = ">=0.13,<0.15" asyncio = ["anyio (>=4.0,<5.0)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.26.0)"] +trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httptools" -version = "0.6.1" +version = "0.6.4" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, + { file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0" }, + { file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da" }, + { file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1" }, + { file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50" }, + { file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959" }, + { file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4" }, + { file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c" }, + { file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069" }, + { file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a" }, + { file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975" }, + { file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636" }, + { file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721" }, + { file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988" }, + { file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17" }, + { file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2" }, + { file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44" }, + { file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1" }, + { file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2" }, + { file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81" }, + { file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f" }, + { file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970" }, + { file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660" }, + { file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083" }, + { file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3" }, + { file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071" }, + { file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5" }, + { file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0" }, + { file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8" }, + { file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba" }, + { file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc" }, + { file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff" }, + { file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490" }, + { file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43" }, + { file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440" }, + { file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f" }, + { file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003" }, + { file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab" }, + { file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547" }, + { file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9" }, + { file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076" }, + { file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd" }, + { file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6" }, + { file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c" }, ] [package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] +test = ["Cython (>=0.29.24)"] [[package]] name = "httpx" -version = "0.27.2" +version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - { file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0" }, - { file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2" }, + { file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" }, + { file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc" }, ] [package.dependencies] @@ -1183,7 +1271,6 @@ certifi = "*" h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} httpcore = "==1.*" idna = "*" -sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] @@ -1205,24 +1292,27 @@ files = [ [[package]] name = "idna" -version = "3.8" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ - { file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac" }, - { file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" }, + { file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" }, + { file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9" }, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "imageio" -version = "2.35.1" +version = "2.36.1" description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - { file = "imageio-2.35.1-py3-none-any.whl", hash = "sha256:6eb2e5244e7a16b85c10b5c2fe0f7bf961b40fcb9f1a9fd1bd1d2c2f8fb3cd65" }, - { file = "imageio-2.35.1.tar.gz", hash = "sha256:4952dfeef3c3947957f6d5dedb1f4ca31c6e509a476891062396834048aeed2a" }, + { file = "imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf" }, + { file = "imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62" }, ] [package.dependencies] @@ -1230,8 +1320,8 @@ numpy = "*" pillow = ">=8.3.2" [package.extras] -all-plugins = ["astropy", "av", "imageio-ffmpeg", "psutil", "tifffile"] -all-plugins-pypy = ["av", "imageio-ffmpeg", "psutil", "tifffile"] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "numpy (>2)", "pillow-heif", "psutil", "rawpy", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] build = ["wheel"] dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] @@ -1249,223 +1339,213 @@ tifffile = ["tifffile"] [[package]] name = "inflate64" -version = "1.0.0" +version = "1.0.1" description = "deflate64 compression/decompression library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a90c0bdf4a7ecddd8a64cc977181810036e35807f56b0bcacee9abb0fcfd18dc"}, - {file = "inflate64-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:57fe7c14aebf1c5a74fc3b70d355be1280a011521a76aa3895486e62454f4242"}, - {file = "inflate64-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d90730165f471d61a1a694a5e354f3ffa938227e8dcecb62d5d728e8069cee94"}, - {file = "inflate64-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543f400201f5c101141af3c79c82059e1aa6ef4f1584a7f1fa035fb2e465097f"}, - {file = "inflate64-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ceca14f7ec19fb44b047f56c50efb7521b389d222bba2b0a10286a0caeb03fa"}, - {file = "inflate64-1.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b559937a42f0c175b4d2dfc7eb53b97bdc87efa9add15ed5549c6abc1e89d02f"}, - {file = "inflate64-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5ff8bd2a562343fcbc4eea26fdc368904a3b5f6bb8262344274d3d74a1de15bb"}, - {file = "inflate64-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:0fe481f31695d35a433c3044ac8fd5d9f5069aaad03a0c04b570eb258ce655aa"}, - {file = "inflate64-1.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a45f6979ad5874d4d4898c2fc770b136e61b96b850118fdaec5a5af1b9123a"}, - {file = "inflate64-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:022ca1cc928e7365a05f7371ff06af143c6c667144965e2cf9a9236a2ae1c291"}, - {file = "inflate64-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46792ecf3565d64fd2c519b0a780c03a57e195613c9954ef94e739a057b3fd06"}, - {file = "inflate64-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a70ea2e456c15f7aa7c74b8ab8f20b4f8940ec657604c9f0a9de3342f280fff"}, - {file = "inflate64-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e243ea9bd36a035059f2365bd6d156ff59717fbafb0255cb0c75bf151bf6904"}, - {file = "inflate64-1.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4dc392dec1cd11cacda3d2637214ca45e38202e8a4f31d4a4e566d6e90625fc4"}, - {file = "inflate64-1.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8b402a50eda7ee75f342fc346d33a41bca58edc222a4b17f9be0db1daed459fa"}, - {file = "inflate64-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:f5924499dc8800928c0ee4580fa8eb4ffa880b2cce4431537d0390e503a9c9ee"}, - {file = "inflate64-1.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0c644bf7208e20825ca3bbb5fb1f7f495cfcb49eb01a5f67338796d44a42f2bf"}, - {file = "inflate64-1.0.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9964a4eaf26a9d36f82a1d9b12c28e35800dd3d99eb340453ed12ac90c2976a8"}, - {file = "inflate64-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2cccded63865640d03253897be7232b2bbac295fe43914c61f86a57aa23bb61d"}, - {file = "inflate64-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d491f104fb3701926ebd82b8c9250dfba0ddcab584504e26f1e4adb26730378d"}, - {file = "inflate64-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ebad4a6cd2a2c1d81be0b09d4006479f3b258803c49a9224ef8ca0b649072fa"}, - {file = "inflate64-1.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6823b2c0cff3a8159140f3b17ec64fb8ec0e663b45a6593618ecdde8aeecb5b2"}, - {file = "inflate64-1.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:228d504239d27958e71fc77e3119a6ac4528127df38468a0c95a5bd3927204b8"}, - {file = "inflate64-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae2572e06bcfe15e3bbf77d4e4a6d6c55e2a70d6abceaaf60c5c3653ddb96dfd"}, - {file = "inflate64-1.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c10ca61212a753bbce6d341e7cfa779c161b839281f1f9fdc15cf1f324ce7c5b"}, - {file = "inflate64-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a982dc93920f9450da4d4f25c5e5c1288ef053b1d618cedc91adb67e035e35f5"}, - {file = "inflate64-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ca0310b2c55bc40394c5371db2a22f705fd594226cc09432e1eb04d3aed83930"}, - {file = "inflate64-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e95044ae55a161144445527a2efad550851fecc699066423d24b2634a6a83710"}, - {file = "inflate64-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34de6902c39d9225459583d5034182d371fc694bc3cfd6c0fc89aa62e9809faf"}, - {file = "inflate64-1.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ebafbd813213dc470719cd0a2bcb53aab89d9059f4e75386048b4c4dcdb2fd99"}, - {file = "inflate64-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:75448c7b414dadaeeb11dab9f75e022aa1e0ee19b00f570e9f58e933603d71ac"}, - {file = "inflate64-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:2be4e01c1b04761874cb44b35b6103ca5846bc36c18fc3ff5e8cbcd8bfc15e9f"}, - {file = "inflate64-1.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bf2981b95c1f26242bb084d9a07f3feb0cfe3d6d0a8d90f42389803bc1252c4a"}, - {file = "inflate64-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9373ccf0661cc72ac84a0ad622634144da5ce7d57c9572ed0723d67a149feed2"}, - {file = "inflate64-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e4650c6f65011ec57cf5cd96b92d5b7c6f59e502930c86eb8227c93cf02dc270"}, - {file = "inflate64-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a475e8822f1a74c873e60b8f270773757ade024097ca39e43402d47c049c67d4"}, - {file = "inflate64-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4367480733ac8daf368f6fc704b7c9db85521ee745eb5bd443f4b97d2051acc"}, - {file = "inflate64-1.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c5775c91f94f5eced9160fb0af12a09f3e030194f91a6a46e706a79350bd056"}, - {file = "inflate64-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d76d205b844d78ce04768060084ef20e64dcc63a3e9166674f857acaf4d140ed"}, - {file = "inflate64-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:92f0dc6af0e8e97324981178dc442956cbff1247a56d1e201af8d865244653f8"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f79542478e49e471e8b23556700e6f688a40dc93e9a746f77a546c13251b59b1"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a270be6b10cde01258c0097a663a307c62d12c78eb8f62f8e29f205335942c9"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1616a87ff04f583e9558cc247ec0b72a30d540ee0c17cc77823be175c0ec92f0"}, - {file = "inflate64-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:137ca6b315f0157a786c3a755a09395ca69aed8bcf42ad3437cb349f5ebc86d2"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8140942d1614bdeb5a9ddd7559348c5c77f884a42424aef7ccf149ccfb93aa08"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fe3f9051338bb7d07b5e7d88420d666b5109f33ae39aa55ecd1a053c0f22b1b"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36342338e957c790fc630d4afcdcc3926beb2ecaea0b302336079e8fa37e57a0"}, - {file = "inflate64-1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9b65cc701ef33ab20dbfd1d64088ffd89a8c265b356d2c21ba0ec565661645ef"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dd6d3e7d47df43210a995fd1f5989602b64de3f2a17cf4cbff553518b3577fd4"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f033b2879696b855200cde5ca4e293132c7499df790acb2c0dacb336d5e83b1"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f816d1c8a0593375c289e285c96deaee9c2d8742cb0edbd26ee05588a9ae657"}, - {file = "inflate64-1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1facd35319b6a391ee4c3d709c7c650bcada8cd7141d86cd8c2257287f45e6e6"}, - {file = "inflate64-1.0.0.tar.gz", hash = "sha256:3278827b803cf006a1df251f3e13374c7d26db779e5a33329cc11789b804bc2d"}, + { file = "inflate64-1.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5122a188995e47a735ab969edc9129d42bbd97b993df5a3f0819b87205ce81b4" }, + { file = "inflate64-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:975ed694c680e46a5c0bb872380a9c9da271a91f9c0646561c58e8f3714347d4" }, + { file = "inflate64-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bcaf445d9cda5f7358e0c2b78144641560f8ce9e3e4351099754c49d26a34e8" }, + { file = "inflate64-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daede09baba24117279109b30fdf935195e91957e31b995b86f8dd01711376ee" }, + { file = "inflate64-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df40eaaba4fb8379d5c4fa5f56cc24741c4f1a91d4aef66438207473351ceaa" }, + { file = "inflate64-1.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ef90855ff63d53c8fd3bfbf85b5280b22f82b9ab2e21a7eee45b8a19d9866c42" }, + { file = "inflate64-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5daa4566c0b009c9ab8a6bf18ce407d14f5dbbb0d3068f3a43af939a17e117a7" }, + { file = "inflate64-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:d58a360b59685561a8feacee743479a9d7cc17c8d210aa1f2ae221f2513973cb" }, + { file = "inflate64-1.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31198c5f156806cee05b69b149074042b7b7d39274ff4c259b898e617294ac17" }, + { file = "inflate64-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ab693bb1cd92573a997f8fe7b90a2ec1e17a507884598f5640656257b95ef49" }, + { file = "inflate64-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:95b6a60e305e6e759e37d6c36691fcb87678922c56b3ddc2df06cd56e04f41f6" }, + { file = "inflate64-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:711ef889bdb3b3b296881d1e49830a3a896938fba7033c4287f1aed9b9a20111" }, + { file = "inflate64-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3178495970ecb5c6a32167a8b57fdeef3bf4e2843eaf8f2d8f816f523741e36" }, + { file = "inflate64-1.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e8373b7feedf10236eb56d21598a19a3eb51077c3702d0ce3456b827374025e1" }, + { file = "inflate64-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cf026d5c885f2d2bbf233e9a0c8c6d046ec727e2467024ffe0ac76b5be308258" }, + { file = "inflate64-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:3aa7489241e6c6f6d34b9561efdf06031c35305b864267a5b8f406abcd3e85c5" }, + { file = "inflate64-1.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b81b3d373190ecd82901f42afd90b7127e9bdef341032a94db381c750ed3ddb2" }, + { file = "inflate64-1.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:dbfddc5dac975227c20997f0ac515917a15421767c6bff0c209ac6ff9d7b17cc" }, + { file = "inflate64-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2adeabe79cc2f90bca832673520c8cbad7370f86353e151293add7ca529bed34" }, + { file = "inflate64-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b235c97a05dbe2f92f0f057426e4d05a449e1fccf8e9aa88075ea9c6a06a182" }, + { file = "inflate64-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19b74e30734dca5f1c83ca07074e1f25bf7b63f4a5ee7e074d9a4cb05af65cd5" }, + { file = "inflate64-1.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b298feb85204b5ef148ccf807744c836fffed7c1ed3ec8bc9b4e323a03163291" }, + { file = "inflate64-1.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a4c75241bc442267f79b8242135f2ded29405662c44b9353d34fbd4fa6e56b3" }, + { file = "inflate64-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:7b210392f0830ab27371e36478592f47757f5ea6c09ddb96e2125847b309eb5e" }, + { file = "inflate64-1.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8dd58aa1adc4f98bf9b52baffa8f2ddf589e071a90db2f2bec9024328d4608cf" }, + { file = "inflate64-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c108be2b87e88c966570f84f839eb37f489b45dc3fa3046dc228327af6e921bb" }, + { file = "inflate64-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63971c6b096c0d533c0e38b4257f5a7748501a8bc04d00cf239bd06467888703" }, + { file = "inflate64-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d0077edb6b1cabfa2223b71a4a725e5755148f551a7a396c7d5698e45fb8828" }, + { file = "inflate64-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05b5f2a6f1bf2f70e9c20d997261711cbc1ae477379662b05b36911da60a67" }, + { file = "inflate64-1.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f3c7402165f7e15789caa0787e5a349465d9a454105d0c3a0ccf2e9cdfb8117" }, + { file = "inflate64-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39bced168822e4bf2f545d1b6dbeded6db01c32629d9e4549ef2cd1604a12e1b" }, + { file = "inflate64-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:70bb6a22d300d8ca25c26bc60afb5662c5a96d97a801962874d0461568512789" }, + { file = "inflate64-1.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f3d5ea758358a1cc50f9e8e41de2134e9b5c5ca8bbcd88d1cd135d0e953d0fa8" }, + { file = "inflate64-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fa102c834314c3d7edbf249d1be0bce5d12a9e122228a7ac3f861ee82c3dc5c" }, + { file = "inflate64-1.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2ae56a34e6cc2a712418ac82332e5d550ef8599e0ffb64c19b86d63a7df0c5" }, + { file = "inflate64-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9808ae50b5db661770992566e51e648cac286c32bd80892b151e7b1eca81afe8" }, + { file = "inflate64-1.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:04b2788c6a26e1e525f53cc3d8c58782d41f18bef8d2a34a3d58beaaf0bfdd3b" }, + { file = "inflate64-1.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67fd5b1f9e433b0abab8cb91f4da94d16223a5241008268a57f4729fdbfc4dbc" }, + { file = "inflate64-1.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6f3b00c17ae365e82fc3d48ff9a7a566820a6c8c55b4e16c6cfbcbd46505a72" }, + { file = "inflate64-1.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:91c0c1d41c1655fb0189630baaa894a3b778d77062bb90ca11db878422948395" }, + { file = "inflate64-1.0.1.tar.gz", hash = "sha256:3b1c83c22651b5942b35829df526e89602e494192bf021e0d7d0b600e76c429d" }, ] [package.extras] -check = ["check-manifest", "flake8", "flake8-black", "flake8-deprecated", "isort (>=5.0.3)", "mypy (>=0.940)", "mypy-extensions (>=0.4.1)", "pygments", "readme-renderer", "twine"] +check = ["check-manifest", "flake8", "flake8-black", "flake8-deprecated", "flake8-isort", "mypy (>=1.10.0)", "mypy_extensions (>=0.4.1)", "pygments", "readme-renderer", "twine"] docs = ["docutils", "sphinx (>=5.0)"] -test = ["pyannotate", "pytest"] +test = ["pytest"] + +[[package]] +name = "jieba" +version = "0.42.1" +description = "Chinese Words Segmentation Utilities" +optional = false +python-versions = "*" +files = [ + { file = "jieba-0.42.1.tar.gz", hash = "sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2" }, +] [[package]] name = "kiwisolver" -version = "1.4.5" +version = "1.4.8" description = "A fast implementation of the Cassowary constraint solver" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, + { file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db" }, + { file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b" }, + { file = "kiwisolver-1.4.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce2cf1e5688edcb727fdf7cd1bbd0b6416758996826a8be1d958f91880d0809d" }, + { file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c8bf637892dc6e6aad2bc6d4d69d08764166e5e3f69d469e55427b6ac001b19d" }, + { file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c" }, + { file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47b28d1dfe0793d5e96bce90835e17edf9a499b53969b03c6c47ea5985844c3" }, + { file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb158fe28ca0c29f2260cca8c43005329ad58452c36f0edf298204de32a9a3ed" }, + { file = "kiwisolver-1.4.8-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5536185fce131780ebd809f8e623bf4030ce1b161353166c49a3c74c287897f" }, + { file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:369b75d40abedc1da2c1f4de13f3482cb99e3237b38726710f4a793432b1c5ff" }, + { file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:641f2ddf9358c80faa22e22eb4c9f54bd3f0e442e038728f500e3b978d00aa7d" }, + { file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d561d2d8883e0819445cfe58d7ddd673e4015c3c57261d7bdcd3710d0d14005c" }, + { file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1732e065704b47c9afca7ffa272f845300a4eb959276bf6970dc07265e73b605" }, + { file = "kiwisolver-1.4.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bcb1ebc3547619c3b58a39e2448af089ea2ef44b37988caf432447374941574e" }, + { file = "kiwisolver-1.4.8-cp310-cp310-win_amd64.whl", hash = "sha256:89c107041f7b27844179ea9c85d6da275aa55ecf28413e87624d033cf1f6b751" }, + { file = "kiwisolver-1.4.8-cp310-cp310-win_arm64.whl", hash = "sha256:b5773efa2be9eb9fcf5415ea3ab70fc785d598729fd6057bea38d539ead28271" }, + { file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a4d3601908c560bdf880f07d94f31d734afd1bb71e96585cace0e38ef44c6d84" }, + { file = "kiwisolver-1.4.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:856b269c4d28a5c0d5e6c1955ec36ebfd1651ac00e1ce0afa3e28da95293b561" }, + { file = "kiwisolver-1.4.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c2b9a96e0f326205af81a15718a9073328df1173a2619a68553decb7097fd5d7" }, + { file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5020c83e8553f770cb3b5fc13faac40f17e0b205bd237aebd21d53d733adb03" }, + { file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dace81d28c787956bfbfbbfd72fdcef014f37d9b48830829e488fdb32b49d954" }, + { file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11e1022b524bd48ae56c9b4f9296bce77e15a2e42a502cceba602f804b32bb79" }, + { file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b9b4d2892fefc886f30301cdd80debd8bb01ecdf165a449eb6e78f79f0fabd6" }, + { file = "kiwisolver-1.4.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a96c0e790ee875d65e340ab383700e2b4891677b7fcd30a699146f9384a2bb0" }, + { file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23454ff084b07ac54ca8be535f4174170c1094a4cff78fbae4f73a4bcc0d4dab" }, + { file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:87b287251ad6488e95b4f0b4a79a6d04d3ea35fde6340eb38fbd1ca9cd35bbbc" }, + { file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b21dbe165081142b1232a240fc6383fd32cdd877ca6cc89eab93e5f5883e1c25" }, + { file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:768cade2c2df13db52475bd28d3a3fac8c9eff04b0e9e2fda0f3760f20b3f7fc" }, + { file = "kiwisolver-1.4.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d47cfb2650f0e103d4bf68b0b5804c68da97272c84bb12850d877a95c056bd67" }, + { file = "kiwisolver-1.4.8-cp311-cp311-win_amd64.whl", hash = "sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34" }, + { file = "kiwisolver-1.4.8-cp311-cp311-win_arm64.whl", hash = "sha256:16523b40aab60426ffdebe33ac374457cf62863e330a90a0383639ce14bf44b2" }, + { file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502" }, + { file = "kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31" }, + { file = "kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb" }, + { file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f" }, + { file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc" }, + { file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a" }, + { file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a" }, + { file = "kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a" }, + { file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3" }, + { file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b" }, + { file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4" }, + { file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d" }, + { file = "kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8" }, + { file = "kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50" }, + { file = "kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476" }, + { file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09" }, + { file = "kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1" }, + { file = "kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c" }, + { file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b" }, + { file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47" }, + { file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16" }, + { file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc" }, + { file = "kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246" }, + { file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794" }, + { file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b" }, + { file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3" }, + { file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957" }, + { file = "kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb" }, + { file = "kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2" }, + { file = "kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90" }, + { file = "kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85" }, + { file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e7a019419b7b510f0f7c9dceff8c5eae2392037eae483a7f9162625233802b0a" }, + { file = "kiwisolver-1.4.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:286b18e86682fd2217a48fc6be6b0f20c1d0ed10958d8dc53453ad58d7be0bf8" }, + { file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4191ee8dfd0be1c3666ccbac178c5a05d5f8d689bbe3fc92f3c4abec817f8fe0" }, + { file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd2785b9391f2873ad46088ed7599a6a71e762e1ea33e87514b1a441ed1da1c" }, + { file = "kiwisolver-1.4.8-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07b29089b7ba090b6f1a669f1411f27221c3662b3a1b7010e67b59bb5a6f10b" }, + { file = "kiwisolver-1.4.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:65ea09a5a3faadd59c2ce96dc7bf0f364986a315949dc6374f04396b0d60e09b" }, + { file = "kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e" }, ] [[package]] name = "libcst" -version = "1.4.0" -description = "A concrete syntax tree with AST-like properties for Python 3.0 through 3.12 programs." +version = "1.5.1" +description = "A concrete syntax tree with AST-like properties for Python 3.0 through 3.13 programs." optional = false python-versions = ">=3.9" files = [ - {file = "libcst-1.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:279b54568ea1f25add50ea4ba3d76d4f5835500c82f24d54daae4c5095b986aa"}, - {file = "libcst-1.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3401dae41fe24565387a65baee3887e31a44e3e58066b0250bc3f3ccf85b1b5a"}, - {file = "libcst-1.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1989fa12d3cd79118ebd29ebe2a6976d23d509b1a4226bc3d66fcb7cb50bd5d"}, - {file = "libcst-1.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:addc6d585141a7677591868886f6bda0577529401a59d210aa8112114340e129"}, - {file = "libcst-1.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17d71001cb25e94cfe8c3d997095741a8c4aa7a6d234c0f972bc42818c88dfaf"}, - {file = "libcst-1.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:2d47de16d105e7dd5f4e01a428d9f4dc1e71efd74f79766daf54528ce37f23c3"}, - {file = "libcst-1.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e6227562fc5c9c1efd15dfe90b0971ae254461b8b6b23c1b617139b6003de1c1"}, - {file = "libcst-1.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3399e6c95df89921511b44d8c5bf6a75bcbc2d51f1f6429763609ba005c10f6b"}, - {file = "libcst-1.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48601e3e590e2d6a7ab8c019cf3937c70511a78d778ab3333764531253acdb33"}, - {file = "libcst-1.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f42797309bb725f0f000510d5463175ccd7155395f09b5e7723971b0007a976d"}, - {file = "libcst-1.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb4e42ea107a37bff7f9fdbee9532d39f9ea77b89caa5c5112b37057b12e0838"}, - {file = "libcst-1.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:9d0cc3c5a2a51fa7e1d579a828c0a2e46b2170024fd8b1a0691c8a52f3abb2d9"}, - {file = "libcst-1.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7ece51d935bc9bf60b528473d2e5cc67cbb88e2f8146297e40ee2c7d80be6f13"}, - {file = "libcst-1.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:81653dea1cdfa4c6520a7c5ffb95fa4d220cbd242e446c7a06d42d8636bfcbba"}, - {file = "libcst-1.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6abce0e66bba2babfadc20530fd3688f672d565674336595b4623cd800b91ef"}, - {file = "libcst-1.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5da9d7dc83801aba3b8d911f82dc1a375db0d508318bad79d9fb245374afe068"}, - {file = "libcst-1.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c54aa66c86d8ece9c93156a2cf5ca512b0dce40142fe9e072c86af2bf892411"}, - {file = "libcst-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:62e2682ee1567b6a89c91853865372bf34f178bfd237853d84df2b87b446e654"}, - {file = "libcst-1.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8ecdba8934632b4dadacb666cd3816627a6ead831b806336972ccc4ba7ca0e9"}, - {file = "libcst-1.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8e54c777b8d27339b70f304d16fc8bc8674ef1bd34ed05ea874bf4921eb5a313"}, - {file = "libcst-1.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061d6855ef30efe38b8a292b7e5d57c8e820e71fc9ec9846678b60a934b53bbb"}, - {file = "libcst-1.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0abf627ee14903d05d0ad9b2c6865f1b21eb4081e2c7bea1033f85db2b8bae"}, - {file = "libcst-1.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d024f44059a853b4b852cfc04fec33e346659d851371e46fc8e7c19de24d3da9"}, - {file = "libcst-1.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3c6a8faab9da48c5b371557d0999b4ca51f4f2cbd37ee8c2c4df0ac01c781465"}, - {file = "libcst-1.4.0.tar.gz", hash = "sha256:449e0b16604f054fa7f27c3ffe86ea7ef6c409836fe68fe4e752a1894175db00"}, + { file = "libcst-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab83633e61ee91df575a3838b1e73c371f19d4916bf1816554933235553d41ea" }, + { file = "libcst-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b58a49895d95ec1fd34fad041a142d98edf9b51fcaf632337c13befeb4d51c7c" }, + { file = "libcst-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9ec764aa781ef35ab96b693569ac3dced16df9feb40ee6c274d13e86a1472e" }, + { file = "libcst-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99bbffd8596d192bc0e844a4cf3c4fc696979d4e20ab1c0774a01768a59b47ed" }, + { file = "libcst-1.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec6ee607cfe4cc4cc93e56e0188fdb9e50399d61a1262d58229752946f288f5e" }, + { file = "libcst-1.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72132756f985a19ef64d702a821099d4afc3544974662772b44cbc55b7279727" }, + { file = "libcst-1.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:40b75bf2d70fc0bc26b1fa73e61bdc46fef59f5c71aedf16128e7c33db8d5e40" }, + { file = "libcst-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:56c944acaa781b8e586df3019374f5cf117054d7fc98f85be1ba84fe810005dc" }, + { file = "libcst-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db7711a762b0327b581be5a963908fecd74412bdda34db34553faa521563c22d" }, + { file = "libcst-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa524bd012aaae1f485fd44490ef5abf708b14d2addc0f06b28de3e4585c4b9e" }, + { file = "libcst-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffb8135c09e41e8cf710b152c33e9b7f1d0d0b9f242bae0c502eb082fdb1fb" }, + { file = "libcst-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76a8ac7a84f9b6f678a668bff85b360e0a93fa8d7f25a74a206a28110734bb2a" }, + { file = "libcst-1.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89c808bdb5fa9ca02df41dd234cbb0e9de0d2e0c029c7063d5435a9f6781cc10" }, + { file = "libcst-1.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40fbbaa8b839bfbfa5b300623ca2b6b0768b58bbc31b341afbc99110c9bee232" }, + { file = "libcst-1.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c7021e3904d8d088c369afc3fe17c279883e583415ef07edacadba76cfbecd27" }, + { file = "libcst-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f053a5deb6a214972dbe9fa26ecd8255edb903de084a3d7715bf9e9da8821c50" }, + { file = "libcst-1.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:666813950b8637af0c0e96b1ca46f5d5f183d2fe50bbac2186f5b283a99f3529" }, + { file = "libcst-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b58b36022ae77a5a00002854043ae95c03e92f6062ad08473eff326f32efa0" }, + { file = "libcst-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eeb13d7c598fe9a798a1d22eae56ab3d3d599b38b83436039bd6ae229fc854d7" }, + { file = "libcst-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5987daff8389b0df60b5c20499ff4fb73fc03cb3ae1f6a746eefd204ed08df85" }, + { file = "libcst-1.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f3d2f32ee081bad3394546b0b9ac5e31686d3b5cfe4892d716d2ba65f9ec08" }, + { file = "libcst-1.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ff21005c33b634957a98db438e882522febf1cacc62fa716f29e163a3f5871a" }, + { file = "libcst-1.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:15697ea9f1edbb9a263364d966c72abda07195d1c1a6838eb79af057f1040770" }, + { file = "libcst-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:cedd4c8336e01c51913113fbf5566b8f61a86d90f3d5cc5b1cb5049575622c5f" }, + { file = "libcst-1.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:06a9b4c9b76da4a7399e6f1f3a325196fb5febd3ea59fac1f68e2116f3517cd8" }, + { file = "libcst-1.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:940ec4c8db4c2d620a7268d6c83e64ff646e4afd74ae5183d0f0ef3b80e05be0" }, + { file = "libcst-1.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fbccb016b1ac6d892344300dcccc8a16887b71bb7f875ba56c0ed6c1a7ade8be" }, + { file = "libcst-1.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c615af2117320e9a218083c83ec61227d3547e38a0de80329376971765f27a9e" }, + { file = "libcst-1.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02b38fa4d9f13e79fe69e9b5407b9e173557bcfb5960f7866cf4145af9c7ae09" }, + { file = "libcst-1.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3334afe9e7270e175de01198f816b0dc78dda94d9d72152b61851c323e4e741e" }, + { file = "libcst-1.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26c804fa8091747128579013df0b5f8e6b0c7904d9c4ee83841f136f53e18684" }, + { file = "libcst-1.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:b5a0d3c632aa2b21c5fa145e4e8dbf86f45c9b37a64c0b7221a5a45caf58915a" }, + { file = "libcst-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1cc7393aaac733e963f0ee00466d059db74a38e15fc7e6a46dddd128c5be8d08" }, + { file = "libcst-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bbaf5755be50fa9b35a3d553d1e62293fbb2ee5ce2c16c7e7ffeb2746af1ab88" }, + { file = "libcst-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e397f5b6c0fc271acea44579f154b0f3ab36011050f6db75ab00cef47441946" }, + { file = "libcst-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1947790a4fd7d96bcc200a6ecaa528045fcb26a34a24030d5859c7983662289e" }, + { file = "libcst-1.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:697eabe9f5ffc40f76d6d02e693274e0a382826d0cf8183bd44e7407dfb0ab90" }, + { file = "libcst-1.5.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dc06b7c60d086ef1832aebfd31b64c3c8a645adf0c5638d6243e5838f6a9356e" }, + { file = "libcst-1.5.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:19e39cfef4316599ca20d1c821490aeb783b52e8a8543a824972a525322a85d0" }, + { file = "libcst-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:01e01c04f0641188160d3b99c6526436e93a3fbf9783dba970f9885a77ec9b38" }, + { file = "libcst-1.5.1.tar.gz", hash = "sha256:71cb294db84df9e410208009c732628e920111683c2f2b2e0c5b71b98464f365" }, ] [package.dependencies] pyyaml = ">=5.2" [package.extras] -dev = ["Sphinx (>=5.1.1)", "black (==23.12.1)", "build (>=0.10.0)", "coverage (>=4.5.4)", "fixit (==2.1.0)", "flake8 (==7.0.0)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.4)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<1.6)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.18)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.6.0)", "usort (==1.0.8.post1)"] +dev = ["Sphinx (>=5.1.1)", "black (==24.8.0)", "build (>=0.10.0)", "coverage[toml] (>=4.5.4)", "fixit (==2.1.0)", "flake8 (==7.1.1)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jinja2 (==3.1.4)", "jupyter (>=1.0.0)", "maturin (>=1.7.0,<1.8)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "pyre-check (==0.9.18)", "setuptools-rust (>=1.5.2)", "setuptools-scm (>=6.0.1)", "slotscheck (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==2.7.3)", "usort (==1.0.8.post1)"] [[package]] name = "linkify-it-py" @@ -1489,13 +1569,13 @@ test = ["coverage", "pytest", "pytest-cov"] [[package]] name = "loguru" -version = "0.7.2" +version = "0.7.3" description = "Python logging made (stupidly) simple" optional = false -python-versions = ">=3.5" +python-versions = "<4.0,>=3.5" files = [ - {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, - {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, + { file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c" }, + { file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6" }, ] [package.dependencies] @@ -1503,7 +1583,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] +dev = ["Sphinx (==8.1.3)", "build (==1.2.2)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.5.0)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.13.0)", "mypy (==v1.4.1)", "myst-parser (==4.0.0)", "pre-commit (==4.0.1)", "pytest (==6.1.2)", "pytest (==8.3.2)", "pytest-cov (==2.12.1)", "pytest-cov (==5.0.0)", "pytest-cov (==6.0.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.1.0)", "sphinx-rtd-theme (==3.0.2)", "tox (==3.27.1)", "tox (==4.23.2)", "twine (==6.0.1)"] [[package]] name = "lxml" @@ -1675,13 +1755,13 @@ test = ["coverage[toml] (>=7.2.5)", "mypy (>=1.2.0)", "pytest (>=7.3.0)", "pytes [[package]] name = "mako" -version = "1.3.5" +version = "1.3.8" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." optional = false python-versions = ">=3.8" files = [ - {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, - {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, + { file = "Mako-1.3.8-py3-none-any.whl", hash = "sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627" }, + { file = "mako-1.3.8.tar.gz", hash = "sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8" }, ] [package.dependencies] @@ -1720,120 +1800,115 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + { file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" }, + { file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d" }, + { file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30" }, + { file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1" }, + { file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6" }, + { file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f" }, + { file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a" }, + { file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0" }, ] [[package]] name = "matplotlib" -version = "3.9.2" +version = "3.10.0" description = "Python plotting package" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - { 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" }, + { file = "matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6" }, + { file = "matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e" }, + { file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5" }, + { file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6" }, + { file = "matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1" }, + { file = "matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3" }, + { file = "matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363" }, + { file = "matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997" }, + { file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef" }, + { file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683" }, + { file = "matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765" }, + { file = "matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a" }, + { file = "matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59" }, + { file = "matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a" }, + { file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95" }, + { file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8" }, + { file = "matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12" }, + { file = "matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc" }, + { file = "matplotlib-3.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25" }, + { file = "matplotlib-3.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908" }, + { file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2" }, + { file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf" }, + { file = "matplotlib-3.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae" }, + { file = "matplotlib-3.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442" }, + { file = "matplotlib-3.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06" }, + { file = "matplotlib-3.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff" }, + { file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593" }, + { file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e" }, + { file = "matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede" }, + { file = "matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c" }, + { file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03" }, + { file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea" }, + { file = "matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef" }, + { file = "matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278" }, ] [package.dependencies] @@ -1848,17 +1923,17 @@ pyparsing = ">=2.3.1" python-dateutil = ">=2.7" [package.extras] -dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] +dev = ["meson-python (>=0.13.1,<0.17.0)", "pybind11 (>=2.13.2,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"] [[package]] name = "mdit-py-plugins" -version = "0.4.1" +version = "0.4.2" description = "Collection of plugins for markdown-it-py" optional = false python-versions = ">=3.8" files = [ - {file = "mdit_py_plugins-0.4.1-py3-none-any.whl", hash = "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a"}, - {file = "mdit_py_plugins-0.4.1.tar.gz", hash = "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c"}, + { file = "mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636" }, + { file = "mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5" }, ] [package.dependencies] @@ -1880,168 +1955,192 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "memory-profiler" +version = "0.61.0" +description = "A module for monitoring memory usage of a python program" +optional = false +python-versions = ">=3.5" +files = [ + { file = "memory_profiler-0.61.0-py3-none-any.whl", hash = "sha256:400348e61031e3942ad4d4109d18753b2fb08c2f6fb8290671c5513a34182d84" }, + { file = "memory_profiler-0.61.0.tar.gz", hash = "sha256:4e5b73d7864a1d1292fb76a03e82a3e78ef934d06828a698d9dada76da2067b0" }, +] + +[package.dependencies] +psutil = "*" + [[package]] name = "msgpack" -version = "1.0.8" +version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, + { file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd" }, + { file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d" }, + { file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5" }, + { file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5" }, + { file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e" }, + { file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b" }, + { file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f" }, + { file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68" }, + { file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b" }, + { file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044" }, + { file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f" }, + { file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7" }, + { file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa" }, + { file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701" }, + { file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6" }, + { file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59" }, + { file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0" }, + { file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e" }, + { file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6" }, + { file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5" }, + { file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88" }, + { file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788" }, + { file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d" }, + { file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2" }, + { file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420" }, + { file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2" }, + { file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39" }, + { file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f" }, + { file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247" }, + { file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c" }, + { file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b" }, + { file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b" }, + { file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f" }, + { file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf" }, + { file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330" }, + { file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734" }, + { file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e" }, + { file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca" }, + { file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915" }, + { file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d" }, + { file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434" }, + { file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c" }, + { file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc" }, + { file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f" }, + { file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec" }, + { file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96" }, + { file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870" }, + { file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7" }, + { file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb" }, + { file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f" }, + { file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b" }, + { file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb" }, + { file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1" }, + { file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48" }, + { file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c" }, + { file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468" }, + { file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74" }, + { file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846" }, + { file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346" }, + { file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b" }, + { file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8" }, + { file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd" }, + { file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325" }, + { file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e" }, ] [[package]] name = "multidict" -version = "6.0.5" +version = "6.1.0" description = "multidict implementation" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, - {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, - {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, - {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, - {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, - {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, - {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, - {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, - {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, - {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, - {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, - {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, - {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, - {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, - {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, - {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, - {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, - {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, - {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, - {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, - {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, - {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, - {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, - {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, - {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, - {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, - {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, - {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, - {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, - {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, - {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, - {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, - {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, + { file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60" }, + { file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1" }, + { file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53" }, + { file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5" }, + { file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581" }, + { file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56" }, + { file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429" }, + { file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748" }, + { file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" }, + { file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056" }, + { file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76" }, + { file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160" }, + { file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7" }, + { file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0" }, + { file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d" }, + { file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6" }, + { file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156" }, + { file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb" }, + { file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b" }, + { file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72" }, + { file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304" }, + { file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351" }, + { file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb" }, + { file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3" }, + { file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399" }, + { file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423" }, + { file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3" }, + { file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753" }, + { file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80" }, + { file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926" }, + { file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa" }, + { file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436" }, + { file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761" }, + { file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e" }, + { file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef" }, + { file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95" }, + { file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925" }, + { file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966" }, + { file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305" }, + { file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2" }, + { file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2" }, + { file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6" }, + { file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3" }, + { file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133" }, + { file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1" }, + { file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008" }, + { file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f" }, + { file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28" }, + { file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b" }, + { file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c" }, + { file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3" }, + { file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44" }, + { file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2" }, + { file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3" }, + { file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa" }, + { file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa" }, + { file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4" }, + { file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6" }, + { file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81" }, + { file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774" }, + { file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392" }, + { file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a" }, + { file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2" }, + { file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc" }, + { file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478" }, + { file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4" }, + { file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d" }, + { file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6" }, + { file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2" }, + { file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd" }, + { file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6" }, + { file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492" }, + { file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd" }, + { file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167" }, + { file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef" }, + { file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c" }, + { file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1" }, + { file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c" }, + { file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c" }, + { file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f" }, + { file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875" }, + { file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255" }, + { file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30" }, + { file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057" }, + { file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657" }, + { file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28" }, + { file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972" }, + { file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43" }, + { file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada" }, + { file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a" }, + { file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506" }, + { file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a" }, ] [[package]] @@ -2062,46 +2161,58 @@ type = ["mypy", "mypy-extensions"] [[package]] name = "mypy" -version = "1.11.2" +version = "1.14.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - { file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a" }, - { file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef" }, - { file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383" }, - { file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8" }, - { file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7" }, - { file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385" }, - { file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca" }, - { file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104" }, - { file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4" }, - { file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6" }, - { file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318" }, - { file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36" }, - { file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987" }, - { file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca" }, - { file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70" }, - { file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b" }, - { file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86" }, - { file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce" }, - { file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1" }, - { file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b" }, - { file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6" }, - { file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70" }, - { file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d" }, - { file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d" }, - { file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24" }, - { file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12" }, - { file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79" }, + { file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb" }, + { file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0" }, + { file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d" }, + { file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b" }, + { file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427" }, + { file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f" }, + { file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c" }, + { file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1" }, + { file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8" }, + { file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f" }, + { file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1" }, + { file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae" }, + { file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14" }, + { file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9" }, + { file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11" }, + { file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e" }, + { file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89" }, + { file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b" }, + { file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255" }, + { file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34" }, + { file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a" }, + { file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9" }, + { file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd" }, + { file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107" }, + { file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31" }, + { file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6" }, + { file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319" }, + { file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac" }, + { file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b" }, + { file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837" }, + { file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35" }, + { file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc" }, + { file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9" }, + { file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb" }, + { file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60" }, + { file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c" }, + { file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1" }, + { file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6" }, ] [package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.6.0" +mypy_extensions = ">=1.0.0" +typing_extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] @@ -2136,13 +2247,13 @@ typing-extensions = ">=4.7.1" [[package]] name = "nonebot-adapter-onebot" -version = "2.4.4" +version = "2.4.6" description = "OneBot(CQHTTP) adapter for nonebot2" optional = false python-versions = "<4.0,>=3.9" files = [ - { file = "nonebot_adapter_onebot-2.4.4-py3-none-any.whl", hash = "sha256:4dceeec7332bb560652c764405e9dd350268303f69b7c0e92b7cfebe876e8d39" }, - { file = "nonebot_adapter_onebot-2.4.4.tar.gz", hash = "sha256:c8a3645f74a3e43c85f092fb670508c662c36831f019a15e4d74eaac686089f0" }, + { file = "nonebot_adapter_onebot-2.4.6-py3-none-any.whl", hash = "sha256:b1ec7023fd83d731f63b513217327a57d12893a261944934b9195f79173791ad" }, + { file = "nonebot_adapter_onebot-2.4.6.tar.gz", hash = "sha256:e33c93649ad11b320d8e9ff213635f29b23b4d0413c9158bd031c513c2f8f701" }, ] [package.dependencies] @@ -2153,16 +2264,17 @@ typing-extensions = ">=4.0.0,<5.0.0" [[package]] name = "nonebot-adapter-qq" -version = "1.5.1" +version = "1.6.0" description = "QQ adapter for nonebot2" optional = false python-versions = "<4.0,>=3.9" files = [ - { file = "nonebot_adapter_qq-1.5.1-py3-none-any.whl", hash = "sha256:d98a264087e2e92024673cbbefc963804b4a85b680599d9bebc5d3c606c8cd22" }, - { file = "nonebot_adapter_qq-1.5.1.tar.gz", hash = "sha256:02cd9c6204fa8a711569fd59fd518826fb484a3ad5bcb45868a754091005a6ea" }, + { file = "nonebot_adapter_qq-1.6.0-py3-none-any.whl", hash = "sha256:a727ce1905d705ee6cb9bad37a2d9384b8fc80c12b2b341679fa4a139fa999a6" }, + { file = "nonebot_adapter_qq-1.6.0.tar.gz", hash = "sha256:6471fefb77f3d131f95a11b380bbcd87ce7ac8932148614f20327d3ed36d9962" }, ] [package.dependencies] +cryptography = ">=43.0.3,<45.0.0" nonebot2 = ">=2.2.1,<3.0.0" pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" typing-extensions = ">=4.4.0,<5.0.0" @@ -2170,13 +2282,13 @@ yarl = ">=1.9.0,<2.0.0" [[package]] name = "nonebot-adapter-telegram" -version = "0.1.0b17" +version = "0.1.0b20" description = "Telegram Adapter for NoneBot2" optional = false -python-versions = ">=3.8,<4.0" +python-versions = "<4.0,>=3.9" files = [ - {file = "nonebot-adapter-telegram-0.1.0b17.tar.gz", hash = "sha256:0914b07e24d6a747a53426b4f8ae799c753a70b703242943f0421f8dee3e5f03"}, - {file = "nonebot_adapter_telegram-0.1.0b17-py3-none-any.whl", hash = "sha256:4ff41a9a8ce828e229ffa7e192a82ea228ac60ae20abeeacbfd8244c6b71ecf8"}, + { file = "nonebot-adapter-telegram-0.1.0b20.tar.gz", hash = "sha256:fc17df61cbdb3162f29dbada1e5712b26f37c185da87602b613694bca32c9ada" }, + { file = "nonebot_adapter_telegram-0.1.0b20-py3-none-any.whl", hash = "sha256:a90acf29b8a9c8a16d4aaa93fb930debde4648d514f2050af945e973cd37a8d4" }, ] [package.dependencies] @@ -2202,21 +2314,23 @@ pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" [[package]] name = "nonebot2" -version = "2.3.3" +version = "2.4.1" description = "An asynchronous python bot framework." optional = false python-versions = "<4.0,>=3.9" files = [ - { file = "nonebot2-2.3.3-py3-none-any.whl", hash = "sha256:5bc8d073091347f29c4a1a2f927c24a8941e5d286c77139376259318b9bbfc68" }, - { file = "nonebot2-2.3.3.tar.gz", hash = "sha256:4fa7707de5d708c27cc49493bc78a07fee2ba01f5516835a2ea5fbebb49b9dfa" }, + { file = "nonebot2-2.4.1-py3-none-any.whl", hash = "sha256:fec95f075efc89dbe9ce148618b413b02f46ba284200367749b035e794695111" }, + { file = "nonebot2-2.4.1.tar.gz", hash = "sha256:8fea364318501ed79721403a8ecd76587bc884d58c356260f691a8bbda9b05e6" }, ] [package.dependencies] -aiohttp = {version = ">=3.9.0b0,<4.0.0", extras = ["speedups"], optional = true, markers = "extra == \"aiohttp\" or extra == \"all\""} +aiohttp = { version = ">=3.11.0,<4.0.0", extras = ["speedups"], optional = true, markers = "extra == \"aiohttp\" or extra == \"all\"" } +anyio = ">=4.4.0,<5.0.0" +exceptiongroup = ">=1.2.2,<2.0.0" fastapi = {version = ">=0.93.0,<1.0.0", optional = true, markers = "extra == \"fastapi\" or extra == \"all\""} -httpx = {version = ">=0.20.0,<1.0.0", extras = ["http2"], optional = true, markers = "extra == \"httpx\" or extra == \"all\""} +httpx = { version = ">=0.26.0,<1.0.0", extras = ["http2"], optional = true, markers = "extra == \"httpx\" or extra == \"all\"" } loguru = ">=0.6.0,<1.0.0" -pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<3.0.0" +pydantic = ">=1.10.0,<2.5.0 || >2.5.0,<2.5.1 || >2.5.1,<2.10.0 || >2.10.0,<2.10.1 || >2.10.1,<3.0.0" pygtrie = ">=2.4.1,<3.0.0" python-dotenv = ">=0.21.0,<2.0.0" typing-extensions = ">=4.4.0,<5.0.0" @@ -2225,10 +2339,10 @@ websockets = {version = ">=10.0", optional = true, markers = "extra == \"websock yarl = ">=1.7.2,<2.0.0" [package.extras] -aiohttp = ["aiohttp[speedups] (>=3.9.0b0,<4.0.0)"] -all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.9.0b0,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"] +aiohttp = ["aiohttp[speedups] (>=3.11.0,<4.0.0)"] +all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.11.0,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.26.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"] fastapi = ["fastapi (>=0.93.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"] -httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"] +httpx = ["httpx[http2] (>=0.26.0,<1.0.0)"] quart = ["Quart (>=0.18.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"] websockets = ["websockets (>=10.0)"] @@ -2248,47 +2362,66 @@ textual = ">=0.76.0" [[package]] name = "numpy" -version = "1.26.4" +version = "2.2.1" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + { file = "numpy-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440" }, + { file = "numpy-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab" }, + { file = "numpy-2.2.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675" }, + { file = "numpy-2.2.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308" }, + { file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957" }, + { file = "numpy-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf" }, + { file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2" }, + { file = "numpy-2.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528" }, + { file = "numpy-2.2.1-cp310-cp310-win32.whl", hash = "sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95" }, + { file = "numpy-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf" }, + { file = "numpy-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484" }, + { file = "numpy-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7" }, + { file = "numpy-2.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb" }, + { file = "numpy-2.2.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5" }, + { file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73" }, + { file = "numpy-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591" }, + { file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8" }, + { file = "numpy-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0" }, + { file = "numpy-2.2.1-cp311-cp311-win32.whl", hash = "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd" }, + { file = "numpy-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16" }, + { file = "numpy-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab" }, + { file = "numpy-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa" }, + { file = "numpy-2.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315" }, + { file = "numpy-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355" }, + { file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7" }, + { file = "numpy-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d" }, + { file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51" }, + { file = "numpy-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046" }, + { file = "numpy-2.2.1-cp312-cp312-win32.whl", hash = "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2" }, + { file = "numpy-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8" }, + { file = "numpy-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780" }, + { file = "numpy-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821" }, + { file = "numpy-2.2.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e" }, + { file = "numpy-2.2.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348" }, + { file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59" }, + { file = "numpy-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af" }, + { file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51" }, + { file = "numpy-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716" }, + { file = "numpy-2.2.1-cp313-cp313-win32.whl", hash = "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e" }, + { file = "numpy-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60" }, + { file = "numpy-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e" }, + { file = "numpy-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712" }, + { file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008" }, + { file = "numpy-2.2.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84" }, + { file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631" }, + { file = "numpy-2.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d" }, + { file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5" }, + { file = "numpy-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71" }, + { file = "numpy-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2" }, + { file = "numpy-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268" }, + { file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3" }, + { file = "numpy-2.2.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964" }, + { file = "numpy-2.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800" }, + { file = "numpy-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e" }, + { file = "numpy-2.2.1.tar.gz", hash = "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918" }, ] [[package]] @@ -2318,139 +2451,243 @@ et-xmlfile = "*" [[package]] name = "packaging" -version = "24.1" +version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, + { file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759" }, + { file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" }, ] [[package]] name = "pillow" -version = "10.4.0" +version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - { file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e" }, - { file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d" }, - { file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856" }, - { file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f" }, - { file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b" }, - { file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc" }, - { file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e" }, - { file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46" }, - { file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984" }, - { file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141" }, - { file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1" }, - { file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c" }, - { file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be" }, - { file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3" }, - { file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6" }, - { file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe" }, - { file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319" }, - { file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d" }, - { file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696" }, - { file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496" }, - { file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91" }, - { file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22" }, - { file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94" }, - { file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597" }, - { file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80" }, - { file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca" }, - { file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef" }, - { file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a" }, - { file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b" }, - { file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9" }, - { file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42" }, - { file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a" }, - { file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9" }, - { file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3" }, - { file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb" }, - { file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70" }, - { file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be" }, - { file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0" }, - { file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc" }, - { file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a" }, - { file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309" }, - { file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060" }, - { file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea" }, - { file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d" }, - { file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736" }, - { file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b" }, - { file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2" }, - { file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680" }, - { file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b" }, - { file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd" }, - { file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84" }, - { file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0" }, - { file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e" }, - { file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab" }, - { file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d" }, - { file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b" }, - { file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd" }, - { file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126" }, - { file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b" }, - { file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c" }, - { file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1" }, - { file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df" }, - { file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef" }, - { file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5" }, - { file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e" }, - { file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4" }, - { file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da" }, - { file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026" }, - { file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e" }, - { file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5" }, - { file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885" }, - { file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5" }, - { file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b" }, - { file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908" }, - { file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b" }, - { file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8" }, - { file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a" }, - { file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27" }, - { file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3" }, - { file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06" }, + { file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947" }, + { file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba" }, + { file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086" }, + { file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9" }, + { file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488" }, + { file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f" }, + { file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb" }, + { file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97" }, + { file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50" }, + { file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c" }, + { file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1" }, + { file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc" }, + { file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a" }, + { file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3" }, + { file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5" }, + { file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b" }, + { file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa" }, + { file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306" }, + { file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9" }, + { file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5" }, + { file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291" }, + { file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9" }, + { file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923" }, + { file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903" }, + { file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4" }, + { file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f" }, + { file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9" }, + { file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7" }, + { file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6" }, + { file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc" }, + { file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6" }, + { file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47" }, + { file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25" }, + { file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699" }, + { file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38" }, + { file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2" }, + { file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2" }, + { file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527" }, + { file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa" }, + { file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f" }, + { file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb" }, + { file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798" }, + { file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de" }, + { file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84" }, + { file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b" }, + { file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003" }, + { file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2" }, + { file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a" }, + { file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8" }, + { file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8" }, + { file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904" }, + { file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3" }, + { file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba" }, + { file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a" }, + { file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916" }, + { file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d" }, + { file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7" }, + { file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e" }, + { file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f" }, + { file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae" }, + { file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4" }, + { file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd" }, + { file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd" }, + { file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2" }, + { file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2" }, + { file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b" }, + { file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2" }, + { file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830" }, + { file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734" }, + { file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316" }, + { file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06" }, + { file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273" }, + { file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790" }, + { file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944" }, + { file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739" }, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] typing = ["typing-extensions"] xmp = ["defusedxml"] +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + { file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" }, + { file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907" }, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "propcache" +version = "0.2.1" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +files = [ + { file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6" }, + { file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2" }, + { file = "propcache-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6445804cf4ec763dc70de65a3b0d9954e868609e83850a47ca4f0cb64bd79fea" }, + { file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9479aa06a793c5aeba49ce5c5692ffb51fcd9a7016e017d555d5e2b0045d212" }, + { file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9631c5e8b5b3a0fda99cb0d29c18133bca1e18aea9effe55adb3da1adef80d3" }, + { file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3156628250f46a0895f1f36e1d4fbe062a1af8718ec3ebeb746f1d23f0c5dc4d" }, + { file = "propcache-0.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6fb63ae352e13748289f04f37868099e69dba4c2b3e271c46061e82c745634" }, + { file = "propcache-0.2.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:887d9b0a65404929641a9fabb6452b07fe4572b269d901d622d8a34a4e9043b2" }, + { file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a96dc1fa45bd8c407a0af03b2d5218392729e1822b0c32e62c5bf7eeb5fb3958" }, + { file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a7e65eb5c003a303b94aa2c3852ef130230ec79e349632d030e9571b87c4698c" }, + { file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:999779addc413181912e984b942fbcc951be1f5b3663cd80b2687758f434c583" }, + { file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:19a0f89a7bb9d8048d9c4370c9c543c396e894c76be5525f5e1ad287f1750ddf" }, + { file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1ac2f5fe02fa75f56e1ad473f1175e11f475606ec9bd0be2e78e4734ad575034" }, + { file = "propcache-0.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:574faa3b79e8ebac7cb1d7930f51184ba1ccf69adfdec53a12f319a06030a68b" }, + { file = "propcache-0.2.1-cp310-cp310-win32.whl", hash = "sha256:03ff9d3f665769b2a85e6157ac8b439644f2d7fd17615a82fa55739bc97863f4" }, + { file = "propcache-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:2d3af2e79991102678f53e0dbf4c35de99b6b8b58f29a27ca0325816364caaba" }, + { file = "propcache-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ffc3cca89bb438fb9c95c13fc874012f7b9466b89328c3c8b1aa93cdcfadd16" }, + { file = "propcache-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f174bbd484294ed9fdf09437f889f95807e5f229d5d93588d34e92106fbf6717" }, + { file = "propcache-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:70693319e0b8fd35dd863e3e29513875eb15c51945bf32519ef52927ca883bc3" }, + { file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b480c6a4e1138e1aa137c0079b9b6305ec6dcc1098a8ca5196283e8a49df95a9" }, + { file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d27b84d5880f6d8aa9ae3edb253c59d9f6642ffbb2c889b78b60361eed449787" }, + { file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:857112b22acd417c40fa4595db2fe28ab900c8c5fe4670c7989b1c0230955465" }, + { file = "propcache-0.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf6c4150f8c0e32d241436526f3c3f9cbd34429492abddbada2ffcff506c51af" }, + { file = "propcache-0.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66d4cfda1d8ed687daa4bc0274fcfd5267873db9a5bc0418c2da19273040eeb7" }, + { file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2f992c07c0fca81655066705beae35fc95a2fa7366467366db627d9f2ee097f" }, + { file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:4a571d97dbe66ef38e472703067021b1467025ec85707d57e78711c085984e54" }, + { file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bb6178c241278d5fe853b3de743087be7f5f4c6f7d6d22a3b524d323eecec505" }, + { file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad1af54a62ffe39cf34db1aa6ed1a1873bd548f6401db39d8e7cd060b9211f82" }, + { file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e7048abd75fe40712005bcfc06bb44b9dfcd8e101dda2ecf2f5aa46115ad07ca" }, + { file = "propcache-0.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:160291c60081f23ee43d44b08a7e5fb76681221a8e10b3139618c5a9a291b84e" }, + { file = "propcache-0.2.1-cp311-cp311-win32.whl", hash = "sha256:819ce3b883b7576ca28da3861c7e1a88afd08cc8c96908e08a3f4dd64a228034" }, + { file = "propcache-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:edc9fc7051e3350643ad929df55c451899bb9ae6d24998a949d2e4c87fb596d3" }, + { file = "propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a" }, + { file = "propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0" }, + { file = "propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d" }, + { file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4" }, + { file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d" }, + { file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5" }, + { file = "propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24" }, + { file = "propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff" }, + { file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f" }, + { file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec" }, + { file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348" }, + { file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6" }, + { file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6" }, + { file = "propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518" }, + { file = "propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246" }, + { file = "propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1" }, + { file = "propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc" }, + { file = "propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9" }, + { file = "propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439" }, + { file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536" }, + { file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629" }, + { file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b" }, + { file = "propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052" }, + { file = "propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce" }, + { file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d" }, + { file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce" }, + { file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95" }, + { file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf" }, + { file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f" }, + { file = "propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30" }, + { file = "propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6" }, + { file = "propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1" }, + { file = "propcache-0.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6a9a8c34fb7bb609419a211e59da8887eeca40d300b5ea8e56af98f6fbbb1541" }, + { file = "propcache-0.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ae1aa1cd222c6d205853b3013c69cd04515f9d6ab6de4b0603e2e1c33221303e" }, + { file = "propcache-0.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:accb6150ce61c9c4b7738d45550806aa2b71c7668c6942f17b0ac182b6142fd4" }, + { file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5eee736daafa7af6d0a2dc15cc75e05c64f37fc37bafef2e00d77c14171c2097" }, + { file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7a31fc1e1bd362874863fdeed71aed92d348f5336fd84f2197ba40c59f061bd" }, + { file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba4cfa1052819d16699e1d55d18c92b6e094d4517c41dd231a8b9f87b6fa681" }, + { file = "propcache-0.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f089118d584e859c62b3da0892b88a83d611c2033ac410e929cb6754eec0ed16" }, + { file = "propcache-0.2.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:781e65134efaf88feb447e8c97a51772aa75e48b794352f94cb7ea717dedda0d" }, + { file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31f5af773530fd3c658b32b6bdc2d0838543de70eb9a2156c03e410f7b0d3aae" }, + { file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a7a078f5d37bee6690959c813977da5291b24286e7b962e62a94cec31aa5188b" }, + { file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cea7daf9fc7ae6687cf1e2c049752f19f146fdc37c2cc376e7d0032cf4f25347" }, + { file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:8b3489ff1ed1e8315674d0775dc7d2195fb13ca17b3808721b54dbe9fd020faf" }, + { file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9403db39be1393618dd80c746cb22ccda168efce239c73af13c3763ef56ffc04" }, + { file = "propcache-0.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5d97151bc92d2b2578ff7ce779cdb9174337390a535953cbb9452fb65164c587" }, + { file = "propcache-0.2.1-cp39-cp39-win32.whl", hash = "sha256:9caac6b54914bdf41bcc91e7eb9147d331d29235a7c967c150ef5df6464fd1bb" }, + { file = "propcache-0.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:92fc4500fcb33899b05ba73276dfb684a20d31caa567b7cb5252d48f896a91b1" }, + { file = "propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54" }, + { file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64" }, +] + [[package]] name = "psutil" -version = "5.9.8" +version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, - {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, - {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, - {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, - {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, - {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, - {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, - {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, - {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, - {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + { file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8" }, + { file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777" }, + { file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4" }, + { file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468" }, + { file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca" }, + { file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac" }, + { file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030" }, + { file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8" }, + { file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377" }, + { file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003" }, + { file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160" }, + { file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3" }, + { file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603" }, + { file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303" }, + { file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53" }, + { file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649" }, + { file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5" }, ] [package.extras] -test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] +dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] [[package]] name = "py7zr" @@ -2484,116 +2721,136 @@ test-compat = ["libarchive-c"] [[package]] name = "pybcj" -version = "1.0.2" +version = "1.0.3" description = "bcj filter library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7bff28d97e47047d69a4ac6bf59adda738cf1d00adde8819117fdb65d966bdbc"}, - {file = "pybcj-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:198e0b4768b4025eb3309273d7e81dc53834b9a50092be6e0d9b3983cfd35c35"}, - {file = "pybcj-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa26415b4a118ea790de9d38f244312f2510a9bb5c65e560184d241a6f391a2d"}, - {file = "pybcj-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fabb2be57e4ca28ea36c13146cdf97d73abd27c51741923fc6ba1e8cd33e255c"}, - {file = "pybcj-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d6d613bae6f27678d5e44e89d61018779726aa6aa950c516d33a04b8af8c59"}, - {file = "pybcj-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ffae79ef8a1ea81ea2748ad7b7ad9b882aa88ddf65ce90f9e944df639eccc61"}, - {file = "pybcj-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bdb4d8ff5cba3e0bd1adee7d20dbb2b4d80cb31ac04d6ea1cd06cfc02d2ecd0d"}, - {file = "pybcj-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a29be917fbc99eca204b08407e0971e0205bfdad4b74ec915930675f352b669d"}, - {file = "pybcj-1.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2562ebe5a0abec4da0229f8abb5e90ee97b178f19762eb925c1159be36828b3"}, - {file = "pybcj-1.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af19bc61ded933001cd68f004ae2042bf1a78eb498a3c685ebd655fa1be90dbe"}, - {file = "pybcj-1.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3f4a447800850aba7724a2274ea0a4800724520c1caf38f7d0dabf2f89a5e15"}, - {file = "pybcj-1.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce1c8af7a4761d2b1b531864d84113948daa0c4245775c63bd9874cb955f4662"}, - {file = "pybcj-1.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8007371f6f2b462f5aa05d5c2135d0a1bcf5b7bdd9bd15d86c730f588d10b7d3"}, - {file = "pybcj-1.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1079ca63ff8da5c936b76863690e0bd2489e8d4e0a3a340e032095dae805dd91"}, - {file = "pybcj-1.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e9a785eb26884429d9b9f6326e68c3638828c83bf6d42d2463c97ad5385caff2"}, - {file = "pybcj-1.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:9ea46e2d45469d13b7f25b08efcdb140220bab1ac5a850db0954591715b8caaa"}, - {file = "pybcj-1.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:21b5f2460629167340403d359289a173e0729ce8e84e3ce99462009d5d5e01a4"}, - {file = "pybcj-1.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2940fb85730b9869254559c491cd83cf777e56c76a8a60df60e4be4f2a4248d7"}, - {file = "pybcj-1.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f40f3243139d675f43793a4e35c410c370f7b91ccae74e70c8b2f4877869f90e"}, - {file = "pybcj-1.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c2b3e60b65c7ac73e44335934e1e122da8d56db87840984601b3c5dc0ae4c19"}, - {file = "pybcj-1.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746550dc7b5af4d04bb5fa4d065f18d39c925bcb5dee30db75747cd9a58bb6e8"}, - {file = "pybcj-1.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8ce9b62b6aaa5b08773be8a919ecc4e865396c969f982b685eeca6e80c82abb7"}, - {file = "pybcj-1.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:493eab2b1f6f546730a6de0c5ceb75ce16f3767154e8ae30e2b70d41b928b7d2"}, - {file = "pybcj-1.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:ef55b96b7f2ed823e0b924de902065ec42ade856366c287dbb073fabd6b90ec1"}, - {file = "pybcj-1.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ed5b3dd9c209fe7b90990dee4ef21870dca39db1cd326553c314ee1b321c1cc"}, - {file = "pybcj-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22a94885723f8362d4cb468e68910eef92d3e2b1293de82b8eacb4198ef6655f"}, - {file = "pybcj-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b8f9368036c9e658d8e3b3534086d298a5349c864542b34657cbe57c260daa49"}, - {file = "pybcj-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87108181c7a6ac4d3fc1e4551cab5db5eea7f9fdca611175243234cd94bcc59b"}, - {file = "pybcj-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db57f26b8c0162cfddb52b869efb1741b8c5e67fc536994f743074985f714c55"}, - {file = "pybcj-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bdf5bcac4f1da36ad43567ea6f6ef404347658dbbe417c87cdb1699f327d6337"}, - {file = "pybcj-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c3171bb95c9b45cbcad25589e1ae4f4ca4ea99dc1724c4e0671eb6b9055514e"}, - {file = "pybcj-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:f9a2585e0da9cf343ea27421995b881736a1eb604a7c1d4ca74126af94c3d4a8"}, - {file = "pybcj-1.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fdb7cd8271471a5979d84915c1ee57eea7e0a69c893225fc418db66883b0e2a7"}, - {file = "pybcj-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e96ae14062bdcddc3197300e6ee4efa6fbc6749be917db934eac66d0daaecb68"}, - {file = "pybcj-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a54ebdc8423ba99d75372708a882fcfc3b14d9d52cf195295ad53e5a47dab37f"}, - {file = "pybcj-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3602be737c6e9553c45ae89e6b0e556f64f34dabf27d5260317d1824d31b79d3"}, - {file = "pybcj-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63dd2ca52a48841f561bfec0fa3f208d375b0a8dcd3d7b236459e683ae29221d"}, - {file = "pybcj-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8204a714029784b1a08a3d790430d80b423b68615c5b1e67aabca5bd5419b77d"}, - {file = "pybcj-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:fde2376b180ae2620c102fbc3ef06638d306feae83964aaa5051ecbdda54845a"}, - {file = "pybcj-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:3b8d7810fb587adbffba025330cf212d9bbed8f29559656d05cb6609673f306a"}, - {file = "pybcj-1.0.2.tar.gz", hash = "sha256:c7f5bef7f47723c53420e377bc64d2553843bee8bcac5f0ad076ab1524780018"}, + { file = "pybcj-1.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0bd8afeacf9173af091a08783aa9111500f5619ce0ae486bffb5ee4d08a331b4" }, + { file = "pybcj-1.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc81d3c941485e7d3c2812834ca005849fe91a624977ed5227658cf952d19696" }, + { file = "pybcj-1.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f01b75621452578ccd48a79819bc95ddac41535e16aa163ea1d86b14258afa00" }, + { file = "pybcj-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e08431845702173d50d66cbbd169969d7b7cf67992f5fb7bc27a8c67e19d3d1f" }, + { file = "pybcj-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:476f3c815b85e563d13238c4310b9cb47aefd0c51ac1b33312e41fcd079ea94f" }, + { file = "pybcj-1.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97bfd712bfce0d58099a02acc05b15b1d1aa3e6edf4dd8e018f43349182ffa3f" }, + { file = "pybcj-1.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4d1374806cde777bc6e371f79c7f3acfb2b0906a418e04cf5331866a321633c3" }, + { file = "pybcj-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9245039e0fc87921f702133c019722e333934e61f1c90408f16618d585ff88ec" }, + { file = "pybcj-1.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae30aa62deff1ba40e4f13ef6964cf083ece541dbfb3ec3731c1fc58cc218b7d" }, + { file = "pybcj-1.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6639f5443bc696a981a502c37e1393398a7182d61820eb39ee6d122076b6ad8c" }, + { file = "pybcj-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4502c5afa2a41e569b94527bbb46185ee1a378a4fb3e9d7806ad10e892ecdf58" }, + { file = "pybcj-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ff48aaadd8fd91ac02557eec225ce7c1a3b627a6832d6cb723469891b3b242" }, + { file = "pybcj-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62668bd0a1aedaa3b779615cf129d9469fd709ab8d944aa07aad68dc189de349" }, + { file = "pybcj-1.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8af60d5eeed32fd1a9f6a2a11eef47cb7ebd80fe9853e709a2c1d9e29108cdf2" }, + { file = "pybcj-1.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68e1bd1b0836e216cce3d9a33795501dfc956c61ff52768737e26286e65a3771" }, + { file = "pybcj-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:05738d44a987422e21f4ee15023a8c4f38a5509fdf6e6f6dfaaf43ca05cef7db" }, + { file = "pybcj-1.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c68a3fe847f22a8393fe71b1b16450b6b9e8ef36faa36d0c03759f58740f6eff" }, + { file = "pybcj-1.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17f610ede3a766c0ff1869a4dd7750db78d39e4bfc9997f6bef050fe794c051b" }, + { file = "pybcj-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:15f776925a4d6f69b344cde9035fc8f1fd02f1f2a4ccb76f4047406c0ea4241d" }, + { file = "pybcj-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdda28e0a20214c7f0e7de9e260122b9197106231249bf07a5ca5b84a5d38a1" }, + { file = "pybcj-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764cba20166fcd9ff580f4d877f17807be057da7d1234caaf54fd5fd5c591387" }, + { file = "pybcj-1.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:97cf7f788560c3283a8afe3de585abb849bb1338d007e53fb6441d6ccd202e0a" }, + { file = "pybcj-1.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26d201f773d17d5e8a88785f00fa73a6647e080d933e75ddeb33da7f0baff657" }, + { file = "pybcj-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:990047ac176317d42e7059b3cd357ff7c7201f3e3f08b35d083b2004d066cd39" }, + { file = "pybcj-1.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3bbbf22687c9f6c57cc9b605a3a60937230843ff1b5560e2a42133fd4dd5dc73" }, + { file = "pybcj-1.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e0a75d5ec3fa40af865f93f29e613d93fb67dc016fc60e64a4b3a4621076fecd" }, + { file = "pybcj-1.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:631bcdea0d47ae562f118f8404fb6ef5813eb2dcfbcc53c7b9ac6bc5d4c2ef32" }, + { file = "pybcj-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75c9430a10e69fbea336668944c0f4a9979e0bb3ab5de820315025c157baa2ae" }, + { file = "pybcj-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5221652a9c656f6b27fda389cc4888354a287d3e0f6ea6d5b70718b6d9ec110d" }, + { file = "pybcj-1.0.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f6a6c3a776aa9b579c51768d2c727d3912cd8e1c2add61898dc6794b269e7ab3" }, + { file = "pybcj-1.0.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:cb50276bd804f58690571c13e2e6eb26eca6c4a39a611591e2202136dca1b7a5" }, + { file = "pybcj-1.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:623a4eef080f5cb0405ce19f90fa9824e2477f4a85d8b888e613cf7f146b84d1" }, + { file = "pybcj-1.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:47d2a0f33dfd55dfa961502922d2b0f090857585b321f838f1c2510de4e66a9a" }, + { file = "pybcj-1.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cf8ac15785412aa6924818fb86e250ae15e8238b7db7d410e28d3ae0743cdbd3" }, + { file = "pybcj-1.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de02d2933fef5b26d845d2e002996c5e22c710af5b5dfc930285dff09db885cf" }, + { file = "pybcj-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40a0f542dba6d079d702c1c129cc8cdc0f20bf2c5cb45defba8d5ac8e2d691a1" }, + { file = "pybcj-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace508285fd4788845a208dd00f1c7af8e68dd222cf7797ae525562a2eb22bab" }, + { file = "pybcj-1.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6da2b0c120a415fa5620b76110bab487de20f8a108756499fd4df9c92fc10098" }, + { file = "pybcj-1.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9c6347f1e2c78cf2584fddebe6fb9dc036b75020887facec1bab149fd6056c6" }, + { file = "pybcj-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:be309c0fbf06b1e8cd1c40b24dd621271b5fb5d9fe7a0becb40ed64ac92ff50b" }, + { file = "pybcj-1.0.3.tar.gz", hash = "sha256:b8873637f0be00ceaa372d0fb81693604b4bbc8decdb2b1ae5f9b84d196788d9" }, ] [package.extras] -check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pygments", "readme-renderer"] +check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-colors", "flake8-isort", "flake8-pyi", "flake8-typing-imports", "mypy (>=1.10.0)", "pygments", "readme-renderer"] test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "pycares" -version = "4.4.0" +version = "4.5.0" description = "Python interface for c-ares" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pycares-4.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:24da119850841d16996713d9c3374ca28a21deee056d609fbbed29065d17e1f6"}, - {file = "pycares-4.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8f64cb58729689d4d0e78f0bfb4c25ce2f851d0274c0273ac751795c04b8798a"}, - {file = "pycares-4.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33e2a1120887e89075f7f814ec144f66a6ce06a54f5722ccefc62fbeda83cff"}, - {file = "pycares-4.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c680fef1b502ee680f8f0b95a41af4ec2c234e50e16c0af5bbda31999d3584bd"}, - {file = "pycares-4.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fff16b09042ba077f7b8aa5868d1d22456f0002574d0ba43462b10a009331677"}, - {file = "pycares-4.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:229a1675eb33bc9afb1fc463e73ee334950ccc485bc83a43f6ae5839fb4d5fa3"}, - {file = "pycares-4.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3aebc73e5ad70464f998f77f2da2063aa617cbd8d3e8174dd7c5b4518f967153"}, - {file = "pycares-4.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef64649eba56448f65e26546d85c860709844d2fc22ef14d324fe0b27f761a9"}, - {file = "pycares-4.4.0-cp310-cp310-win32.whl", hash = "sha256:4afc2644423f4eef97857a9fd61be9758ce5e336b4b0bd3d591238bb4b8b03e0"}, - {file = "pycares-4.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5ed4e04af4012f875b78219d34434a6d08a67175150ac1b79eb70ab585d4ba8c"}, - {file = "pycares-4.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bce8db2fc6f3174bd39b81405210b9b88d7b607d33e56a970c34a0c190da0490"}, - {file = "pycares-4.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a0303428d013ccf5c51de59c83f9127aba6200adb7fd4be57eddb432a1edd2a"}, - {file = "pycares-4.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afb91792f1556f97be7f7acb57dc7756d89c5a87bd8b90363a77dbf9ea653817"}, - {file = "pycares-4.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b61579cecf1f4d616e5ea31a6e423a16680ab0d3a24a2ffe7bb1d4ee162477ff"}, - {file = "pycares-4.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7af06968cbf6851566e806bf3e72825b0e6671832a2cbe840be1d2d65350710"}, - {file = "pycares-4.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ceb12974367b0a68a05d52f4162b29f575d241bd53de155efe632bf2c943c7f6"}, - {file = "pycares-4.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2eeec144bcf6a7b6f2d74d6e70cbba7886a84dd373c886f06cb137a07de4954c"}, - {file = "pycares-4.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e3a6f7cfdfd11eb5493d6d632e582408c8f3b429f295f8799c584c108b28db6f"}, - {file = "pycares-4.4.0-cp311-cp311-win32.whl", hash = "sha256:34736a2ffaa9c08ca9c707011a2d7b69074bbf82d645d8138bba771479b2362f"}, - {file = "pycares-4.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:eb66c30eb11e877976b7ead13632082a8621df648c408b8e15cdb91a452dd502"}, - {file = "pycares-4.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:fd644505a8cfd7f6584d33a9066d4e3d47700f050ef1490230c962de5dfb28c6"}, - {file = "pycares-4.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52084961262232ec04bd75f5043aed7e5d8d9695e542ff691dfef0110209f2d4"}, - {file = "pycares-4.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0c5368206057884cde18602580083aeaad9b860e2eac14fd253543158ce1e93"}, - {file = "pycares-4.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:112a4979c695b1c86f6782163d7dec58d57a3b9510536dcf4826550f9053dd9a"}, - {file = "pycares-4.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d186dafccdaa3409194c0f94db93c1a5d191145a275f19da6591f9499b8e7b8"}, - {file = "pycares-4.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:64965dc19c578a683ea73487a215a8897276224e004d50eeb21f0bc7a0b63c88"}, - {file = "pycares-4.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ed2a38e34bec6f2586435f6ff0bc5fe11d14bebd7ed492cf739a424e81681540"}, - {file = "pycares-4.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:94d6962db81541eb0396d2f0dfcbb18cdb8c8b251d165efc2d974ae652c547d4"}, - {file = "pycares-4.4.0-cp312-cp312-win32.whl", hash = "sha256:1168a48a834813aa80f412be2df4abaf630528a58d15c704857448b20b1675c0"}, - {file = "pycares-4.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:db24c4e7fea4a052c6e869cbf387dd85d53b9736cfe1ef5d8d568d1ca925e977"}, - {file = "pycares-4.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:21a5a0468861ec7df7befa69050f952da13db5427ae41ffe4713bc96291d1d95"}, - {file = "pycares-4.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:22c00bf659a9fa44d7b405cf1cd69b68b9d37537899898d8cbe5dffa4016b273"}, - {file = "pycares-4.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23aa3993a352491a47fcf17867f61472f32f874df4adcbb486294bd9fbe8abee"}, - {file = "pycares-4.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:813d661cbe2e37d87da2d16b7110a6860e93ddb11735c6919c8a3545c7b9c8d8"}, - {file = "pycares-4.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:77cf5a2fd5583c670de41a7f4a7b46e5cbabe7180d8029f728571f4d2e864084"}, - {file = "pycares-4.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3eaa6681c0a3e3f3868c77aca14b7760fed35fdfda2fe587e15c701950e7bc69"}, - {file = "pycares-4.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ad58e284a658a8a6a84af2e0b62f2f961f303cedfe551854d7bd40c3cbb61912"}, - {file = "pycares-4.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bfb89ca9e3d0a9b5332deeb666b2ede9d3469107742158f4aeda5ce032d003f4"}, - {file = "pycares-4.4.0-cp38-cp38-win32.whl", hash = "sha256:f36bdc1562142e3695555d2f4ac0cb69af165eddcefa98efc1c79495b533481f"}, - {file = "pycares-4.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:902461a92b6a80fd5041a2ec5235680c7cc35e43615639ec2a40e63fca2dfb51"}, - {file = "pycares-4.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7bddc6adba8f699728f7fc1c9ce8cef359817ad78e2ed52b9502cb5f8dc7f741"}, - {file = "pycares-4.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cb49d5805cd347c404f928c5ae7c35e86ba0c58ffa701dbe905365e77ce7d641"}, - {file = "pycares-4.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56cf3349fa3a2e67ed387a7974c11d233734636fe19facfcda261b411af14d80"}, - {file = "pycares-4.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bf2eaa83a5987e48fa63302f0fe7ce3275cfda87b34d40fef9ce703fb3ac002"}, - {file = "pycares-4.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82bba2ab77eb5addbf9758d514d9bdef3c1bfe7d1649a47bd9a0d55a23ef478b"}, - {file = "pycares-4.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c6a8bde63106f162fca736e842a916853cad3c8d9d137e11c9ffa37efa818b02"}, - {file = "pycares-4.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5f646eec041db6ffdbcaf3e0756fb92018f7af3266138c756bb09d2b5baadec"}, - {file = "pycares-4.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9dc04c54c6ea615210c1b9e803d0e2d2255f87a3d5d119b6482c8f0dfa15b26b"}, - {file = "pycares-4.4.0-cp39-cp39-win32.whl", hash = "sha256:97892cced5794d721fb4ff8765764aa4ea48fe8b2c3820677505b96b83d4ef47"}, - {file = "pycares-4.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:917f08f0b5d9324e9a34211e68d27447c552b50ab967044776bbab7e42a553a2"}, - {file = "pycares-4.4.0.tar.gz", hash = "sha256:f47579d508f2f56eddd16ce72045782ad3b1b3b678098699e2b6a1b30733e1c2"}, + { file = "pycares-4.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13a82fad8239d6fbcf916099bee17d8b5666d0ddb77dace431e0f7961c9427ab" }, + { file = "pycares-4.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fefc7bebbe39b2e3b4b9615471233a8f7356b96129a7db9030313a3ae4ecc42d" }, + { file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e322e8ce810026f6e0c7c2a254b9ed02191ab8d42fa2ce6808ede1bdccab8e65" }, + { file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723ba0803b016294430e40e544503fed9164949b694342c2552ab189e2b688ef" }, + { file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e48b20b59cdc929cc712a8b22e89c273256e482b49bb8999af98d2c6fc4563c2" }, + { file = "pycares-4.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6e55bd9af595b112ac6080ac0a0d52b5853d0d8e6d01ac65ff09e51e62490a" }, + { file = "pycares-4.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6f4b9063e3dd70460400367917698f209c10aabb68bf70b09e364895444487d" }, + { file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:95522d4840d702fd766439a7c7cd747935aa54cf0b8675e9fadd8414dd9dd0df" }, + { file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4709ce4fd9dbee24b1397f71a2adb3267323bb5ad5e7fde3f87873d172dd156" }, + { file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8addbf3408af1010f50fd67ef634a6cb239ccb9c534c32a40713f3b8d306a98e" }, + { file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d0428ef42fcf575e197047e6a47892404faa34231902a453b3dfed66af4178b3" }, + { file = "pycares-4.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:aed5c2732f3a6bdbbfab202267d37044ca1162f690b9d34b7ece97ba43f27453" }, + { file = "pycares-4.5.0-cp310-cp310-win32.whl", hash = "sha256:b1859ea770a7abec40a6d02b5ab03c2396c4900c01f4e50ddb6c0dca4c2a6a7c" }, + { file = "pycares-4.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9f87d8da20a3a80ab05fe80c14a62bf078bd726ca6af609edbeb376fb97d50ab" }, + { file = "pycares-4.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ca7a1dba7b88290710db45012e0903c21c839fa0a2b9ddc100bba8e66bfb251" }, + { file = "pycares-4.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:160e92588cdf1a0fa3a7015f47990b508d50efd9109ea4d719dee31c058f0648" }, + { file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f38e45d23660ed1dafdb956fd263ae4735530ef1578aa2bf2caabb94cee4523" }, + { file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f742acc6d29a99ffc14e3f154b3848ea05c5533b71065e0f0a0fd99c527491b2" }, + { file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceaf71bcd7b6447705e689b8fee8836c20c6148511a90122981f524a84bfcca9" }, + { file = "pycares-4.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdc3c0be7b5b83e78e28818fecd0405bd401110dd6e2e66f7f10713c1188362c" }, + { file = "pycares-4.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd458ee69800195247aa19b5675c5914cbc091c5a220e4f0e96777a31bb555c1" }, + { file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6649d713df73266708642fc3d04f110c0a66bee510fbce4cc5fed79df42083" }, + { file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ac57d7bda925c10b997434e7ce30a2c3689c2e96bab9fd0a1165d5577378eecd" }, + { file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba17d8e5eeec4b2e0eb1a6a840bae9e62cd1c1c9cbc8dc9db9d1b9fdf33d0b54" }, + { file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9e9b7d1a8de703283e4735c0e532ba4bc600e88de872dcd1a9a4950cf74d9f4f" }, + { file = "pycares-4.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c6922ecbe458c13a4a2c1177bbce38abc44b5f086bc82115a92eab34418915f" }, + { file = "pycares-4.5.0-cp311-cp311-win32.whl", hash = "sha256:1004b8a17614e33410b4b1bb68360977667f1cc9ab2dbcfb27240d6703e4cb6a" }, + { file = "pycares-4.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:2c9c1055c622258a0f315560b2880a372363484b87cbef48af092624804caa72" }, + { file = "pycares-4.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:506efbe5017807747ccd1bdcb3c2f6e64635bc01fee01a50c0b97d649018c162" }, + { file = "pycares-4.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c469ec9fbe0526f45a98f67c1ea55be03abf30809c4f9c9be4bc93fb6806304d" }, + { file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597c0950ede240c3a779f023fcf2442207fc11e570d3ca4ccdbb0db5bbaf2588" }, + { file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9aa0da03c4df6ed0f87dd52a293bd0508734515041cc5be0f85d9edc1814914f" }, + { file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1ebf52767c777d10a1b3d03844b9b05cc892714b3ee177d5d9fbff74fb9fa" }, + { file = "pycares-4.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb20d84269ddffb177b6048e3bc03d0b9ffe17592093d900d5544805958d86b3" }, + { file = "pycares-4.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3125df81b657971ee5c0333f8f560ba0151db1eb7cf04aea7d783bb433b306c1" }, + { file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:525c77ea44546c12f379641aee163585d403cf50e29b04a06059d6aac894e956" }, + { file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:1fd87cb26b317a9988abfcfa4e4dbc55d5f20177e5979ad4d854468a9246c187" }, + { file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a90aecd41188884e57ae32507a2c6b010c60b791a253083761bbb37a488ecaed" }, + { file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0d3de65cab653979dcc491e03f596566c9d40346c9deb088e0f9fe70600d8737" }, + { file = "pycares-4.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27a77b43604b3ba24e4fc49fd3ea59f50f7d89c7255f1f1ea46928b26cccacfa" }, + { file = "pycares-4.5.0-cp312-cp312-win32.whl", hash = "sha256:6028cb8766f0fea1d2caa69fac23621fbe2cff9ce6968374e165737258703a33" }, + { file = "pycares-4.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:2ce10672c4cfd1c5fb6718e8b25f0336ca11c89aab88aa6df53dafc4e41df740" }, + { file = "pycares-4.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:011cd670da7caf55664c944abb71ec39af82b837f8d48da7cf0eec80f5682c4c" }, + { file = "pycares-4.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b5c67930497fb2b1dbcaa85f8c4188fc2cb62e41d787deeed2d33cfe9dd6bf52" }, + { file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d435a3b8468c656a7e7180dd7c4794510f6c612c33ad61a0fff6e440621f8b5" }, + { file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8371f5ee1efb33d6276e275d152c9c5605e5f2e58a9e168519ec1f9e13dd95ae" }, + { file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c76a9096fd5dc49c61c5235ea7032e8b43f4382800d64ca1e0e0cda700c082aa" }, + { file = "pycares-4.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b604af76b57469ff68b44e9e4c857eaee43bc5035f4f183f07f4f7149191fe1b" }, + { file = "pycares-4.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c589bd4f9160bfdb2f8080cf564bb120a4312cf091db07fe417f8e58a896a63c" }, + { file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:361262805bb09742c364ec0117842043c950339e38561009bcabbb6ac89458ef" }, + { file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d2afb3c0776467055bf33db843ef483d25639be0f32e3a13ef5d4dc64098bf5" }, + { file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bc7a1d8ed7c7a4de17706a3c89b305b02eb64c778897e6727c043e5b9dd0d853" }, + { file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5703ec878b5c1efacdbf24ceaedfa606112fc67af5564f4db99c2c210f3ffadc" }, + { file = "pycares-4.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d87758e09dbf52c27ed7cf7bc7eaf8b3226217d10c52b03d61a14d59f40fcae1" }, + { file = "pycares-4.5.0-cp313-cp313-win32.whl", hash = "sha256:3316d490b4ce1a69f034881ac1ea7608f5f24ea5293db24ab574ac70b7d7e407" }, + { file = "pycares-4.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:018e700fb0d1a2db5ec96e404ffa85ed97cc96e96d6af0bb9548111e37cf36a3" }, + { file = "pycares-4.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78c9890d93108c70708babee8a783e6021233f1f0a763d3634add6fd429aae58" }, + { file = "pycares-4.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba69f8123995aa3df99f6ebc726fc6a4b08e467a957b215c0a82749b901d5eed" }, + { file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d33c4ffae31d1b544adebe0b9aee2be1fb18aedd3f4f91e41c495ccbafd6d8" }, + { file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17a060cfc469828abf7f5945964d505bd8c0a756942fee159538f7885169752e" }, + { file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1d0d5e69fa29e41b590a9dd5842454e8f34e2b928c92540aaf87e0161de8120" }, + { file = "pycares-4.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f096699c46f5dde2c7a8d91501a36d2d58500f4d63682e2ec14a0fed7cca6402" }, + { file = "pycares-4.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:429fe2065581a64a5f024f507b5f679bf37ea0ed39c3ba6289dba907e1c8a8f4" }, + { file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9ea2f6d48e64b413b97b41b47392087b452af9bf9f9d4d6d05305a159f45909f" }, + { file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:96d3aecd747a3fcd1e12c1ea1481b0813b4e0e80d40f314db7a86dda5bb1bd94" }, + { file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:32919f6eda7f5ea4df3e64149fc5792b0d455277d23d6d0fc365142062f35d80" }, + { file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:37add862461f9a3fc7ee4dd8b68465812b39456e21cebd5a33c414131ac05060" }, + { file = "pycares-4.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ed1d050d2c6d74a77c1b6c51fd99426cc000b4202a50d28d6ca75f7433099a6b" }, + { file = "pycares-4.5.0-cp39-cp39-win32.whl", hash = "sha256:887ac451ffe6e39ee46d3d0989c7bb829933d77e1dad5776511d825fc7e6a25b" }, + { file = "pycares-4.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c8b87c05740595bc8051dc98e51f022f003750e7da90f62f7a9fd50e330b196" }, + { file = "pycares-4.5.0.tar.gz", hash = "sha256:025b6c2ffea4e9fb8f9a097381c2fecb24aff23fbd6906e70da22ec9ba60e19d" }, ] [package.dependencies] @@ -2615,204 +2872,213 @@ files = [ [[package]] name = "pycryptodome" -version = "3.20.0" +version = "3.21.0" description = "Cryptographic library for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, - {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + { file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd" }, + { file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4" }, + { file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e" }, + { file = "pycryptodome-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e" }, + { file = "pycryptodome-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a" }, + { file = "pycryptodome-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3" }, + { file = "pycryptodome-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d" }, + { file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb" }, + { file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568" }, + { file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819" }, + { file = "pycryptodome-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3" }, + { file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4" }, + { file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b" }, + { file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e" }, + { file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8" }, + { file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1" }, + { file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a" }, + { file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2" }, + { file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93" }, + { file = "pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764" }, + { file = "pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53" }, + { file = "pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca" }, + { file = "pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd" }, + { file = "pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8" }, + { file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6" }, + { file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0" }, + { file = "pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6" }, + { file = "pycryptodome-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f" }, + { file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b" }, + { file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58" }, + { file = "pycryptodome-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c" }, + { file = "pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297" }, ] [[package]] name = "pycryptodomex" -version = "3.20.0" +version = "3.21.0" description = "Cryptographic library for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -files = [ - {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, - {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + { file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822" }, + { file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd" }, + { file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1233443f19d278c72c4daae749872a4af3787a813e05c3561c73ab0c153c7b0f" }, + { file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbb07f88e277162b8bfca7134b34f18b400d84eac7375ce73117f865e3c80d4c" }, + { file = "pycryptodomex-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e859e53d983b7fe18cb8f1b0e29d991a5c93be2c8dd25db7db1fe3bd3617f6f9" }, + { file = "pycryptodomex-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:ef046b2e6c425647971b51424f0f88d8a2e0a2a63d3531817968c42078895c00" }, + { file = "pycryptodomex-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:da76ebf6650323eae7236b54b1b1f0e57c16483be6e3c1ebf901d4ada47563b6" }, + { file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c07e64867a54f7e93186a55bec08a18b7302e7bee1b02fd84c6089ec215e723a" }, + { file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:56435c7124dd0ce0c8bdd99c52e5d183a0ca7fdcd06c5d5509423843f487dd0b" }, + { file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65d275e3f866cf6fe891411be9c1454fb58809ccc5de6d3770654c47197acd65" }, + { file = "pycryptodomex-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5241bdb53bcf32a9568770a6584774b1b8109342bd033398e4ff2da052123832" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:34325b84c8b380675fd2320d0649cdcbc9cf1e0d1526edbe8fce43ed858cdc7e" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:103c133d6cd832ae7266feb0a65b69e3a5e4dbbd6f3a3ae3211a557fd653f516" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77ac2ea80bcb4b4e1c6a596734c775a1615d23e31794967416afc14852a639d3" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46eb1f0c8d309da63a2064c28de54e5e614ad17b7e2f88df0faef58ce192fc7b" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:cc7e111e66c274b0df5f4efa679eb31e23c7545d702333dfd2df10ab02c2a2ce" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:770d630a5c46605ec83393feaa73a9635a60e55b112e1fb0c3cea84c2897aa0a" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e" }, + { file = "pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0" }, + { file = "pycryptodomex-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:feaecdce4e5c0045e7a287de0c4351284391fe170729aa9182f6bd967631b3a8" }, + { file = "pycryptodomex-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:365aa5a66d52fd1f9e0530ea97f392c48c409c2f01ff8b9a39c73ed6f527d36c" }, + { file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31" }, + { file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3" }, + { file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37" }, + { file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:27e84eeff24250ffec32722334749ac2a57a5fd60332cd6a0680090e7c42877e" }, + { file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ef436cdeea794015263853311f84c1ff0341b98fc7908e8a70595a68cefd971" }, + { file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1058e6dfe827f4209c5cae466e67610bcd0d66f2f037465daa2a29d92d952b" }, + { file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba09a5b407cbb3bcb325221e346a140605714b5e880741dc9a1e9ecf1688d42" }, + { file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8a9d8342cf22b74a746e3c6c9453cb0cfbb55943410e3a2619bd9164b48dc9d9" }, + { file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c" }, ] [[package]] name = "pydantic" -version = "2.8.2" +version = "2.10.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - { file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8" }, - { file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a" }, + { file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d" }, + { file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06" }, ] [package.dependencies] -annotated-types = ">=0.4.0" -pydantic-core = "2.20.1" -typing-extensions = [ - { version = ">=4.12.2", markers = "python_version >= \"3.13\"" }, - { version = ">=4.6.1", markers = "python_version < \"3.13\"" }, -] +annotated-types = ">=0.6.0" +pydantic-core = "2.27.2" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.20.1" +version = "2.27.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - { file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3" }, - { file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6" }, - { file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a" }, - { file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3" }, - { file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1" }, - { file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953" }, - { file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98" }, - { file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a" }, - { file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a" }, - { file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840" }, - { file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250" }, - { file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c" }, - { file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312" }, - { file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88" }, - { file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc" }, - { file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43" }, - { file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6" }, - { file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121" }, - { file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1" }, - { file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b" }, - { file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27" }, - { file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b" }, - { file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a" }, - { file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2" }, - { file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231" }, - { file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9" }, - { file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f" }, - { file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52" }, - { file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237" }, - { file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe" }, - { file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e" }, - { file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24" }, - { file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1" }, - { file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd" }, - { file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688" }, - { file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d" }, - { file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686" }, - { file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a" }, - { file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b" }, - { file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19" }, - { file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac" }, - { file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703" }, - { file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c" }, - { file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83" }, - { file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203" }, - { file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0" }, - { file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e" }, - { file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20" }, - { file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91" }, - { file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b" }, - { file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a" }, - { file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f" }, - { file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad" }, - { file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c" }, - { file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598" }, - { file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd" }, - { file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa" }, - { file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987" }, - { file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a" }, - { file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434" }, - { file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c" }, - { file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6" }, - { file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2" }, - { file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a" }, - { file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611" }, - { file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b" }, - { file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006" }, - { file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1" }, - { file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09" }, - { file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab" }, - { file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2" }, - { file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99" }, - { file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a" }, - { file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7" }, - { file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4" }, + { file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa" }, + { file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c" }, + { file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a" }, + { file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5" }, + { file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c" }, + { file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7" }, + { file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a" }, + { file = "pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236" }, + { file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962" }, + { file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9" }, + { file = "pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af" }, + { file = "pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4" }, + { file = "pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31" }, + { file = "pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc" }, + { file = "pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7" }, + { file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15" }, + { file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306" }, + { file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99" }, + { file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459" }, + { file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048" }, + { file = "pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d" }, + { file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b" }, + { file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474" }, + { file = "pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6" }, + { file = "pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c" }, + { file = "pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc" }, + { file = "pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4" }, + { file = "pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0" }, + { file = "pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef" }, + { file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7" }, + { file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934" }, + { file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6" }, + { file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c" }, + { file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2" }, + { file = "pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4" }, + { file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3" }, + { file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4" }, + { file = "pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57" }, + { file = "pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc" }, + { file = "pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9" }, + { file = "pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b" }, + { file = "pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b" }, + { file = "pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154" }, + { file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9" }, + { file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9" }, + { file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1" }, + { file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a" }, + { file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e" }, + { file = "pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4" }, + { file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27" }, + { file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee" }, + { file = "pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1" }, + { file = "pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130" }, + { file = "pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee" }, + { file = "pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b" }, + { file = "pydantic_core-2.27.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d3e8d504bdd3f10835468f29008d72fc8359d95c9c415ce6e767203db6127506" }, + { file = "pydantic_core-2.27.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:521eb9b7f036c9b6187f0b47318ab0d7ca14bd87f776240b90b21c1f4f149320" }, + { file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85210c4d99a0114f5a9481b44560d7d1e35e32cc5634c656bc48e590b669b145" }, + { file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d716e2e30c6f140d7560ef1538953a5cd1a87264c737643d481f2779fc247fe1" }, + { file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f66d89ba397d92f840f8654756196d93804278457b5fbede59598a1f9f90b228" }, + { file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:669e193c1c576a58f132e3158f9dfa9662969edb1a250c54d8fa52590045f046" }, + { file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdbe7629b996647b99c01b37f11170a57ae675375b14b8c13b8518b8320ced5" }, + { file = "pydantic_core-2.27.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d262606bf386a5ba0b0af3b97f37c83d7011439e3dc1a9298f21efb292e42f1a" }, + { file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cabb9bcb7e0d97f74df8646f34fc76fbf793b7f6dc2438517d7a9e50eee4f14d" }, + { file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:d2d63f1215638d28221f664596b1ccb3944f6e25dd18cd3b86b0a4c408d5ebb9" }, + { file = "pydantic_core-2.27.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bca101c00bff0adb45a833f8451b9105d9df18accb8743b08107d7ada14bd7da" }, + { file = "pydantic_core-2.27.2-cp38-cp38-win32.whl", hash = "sha256:f6f8e111843bbb0dee4cb6594cdc73e79b3329b526037ec242a3e49012495b3b" }, + { file = "pydantic_core-2.27.2-cp38-cp38-win_amd64.whl", hash = "sha256:fd1aea04935a508f62e0d0ef1f5ae968774a32afc306fb8545e06f5ff5cdf3ad" }, + { file = "pydantic_core-2.27.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c10eb4f1659290b523af58fa7cffb452a61ad6ae5613404519aee4bfbf1df993" }, + { file = "pydantic_core-2.27.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef592d4bad47296fb11f96cd7dc898b92e795032b4894dfb4076cfccd43a9308" }, + { file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61709a844acc6bf0b7dce7daae75195a10aac96a596ea1b776996414791ede4" }, + { file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c5f762659e47fdb7b16956c71598292f60a03aa92f8b6351504359dbdba6cf" }, + { file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c9775e339e42e79ec99c441d9730fccf07414af63eac2f0e48e08fd38a64d76" }, + { file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57762139821c31847cfb2df63c12f725788bd9f04bc2fb392790959b8f70f118" }, + { file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d1e85068e818c73e048fe28cfc769040bb1f475524f4745a5dc621f75ac7630" }, + { file = "pydantic_core-2.27.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:097830ed52fd9e427942ff3b9bc17fab52913b2f50f2880dc4a5611446606a54" }, + { file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:044a50963a614ecfae59bb1eaf7ea7efc4bc62f49ed594e18fa1e5d953c40e9f" }, + { file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:4e0b4220ba5b40d727c7f879eac379b822eee5d8fff418e9d3381ee45b3b0362" }, + { file = "pydantic_core-2.27.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4f4bb20d75e9325cc9696c6802657b58bc1dbbe3022f32cc2b2b632c3fbb96" }, + { file = "pydantic_core-2.27.2-cp39-cp39-win32.whl", hash = "sha256:cca63613e90d001b9f2f9a9ceb276c308bfa2a43fafb75c8031c4f66039e8c6e" }, + { file = "pydantic_core-2.27.2-cp39-cp39-win_amd64.whl", hash = "sha256:77d1bca19b0f7021b3a982e6f903dcd5b2b06076def36a652e3907f596e29f67" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9" }, + { file = "pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c33939a82924da9ed65dab5a65d427205a73181d8098e79b6b426bdf8ad4e656" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c817e2b40aba42bac6f457498dacabc568c3b7a986fc9ba7c8d9d260b71485fb" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:251136cdad0cb722e93732cb45ca5299fb56e1344a833640bf93b2803f8d1bfd" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2088237af596f0a524d3afc39ab3b036e8adb054ee57cbb1dcf8e09da5b29cc" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d4041c0b966a84b4ae7a09832eb691a35aec90910cd2dbe7a208de59be77965b" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8083d4e875ebe0b864ffef72a4304827015cff328a1be6e22cc850753bfb122b" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f141ee28a0ad2123b6611b6ceff018039df17f32ada8b534e6aa039545a3efb2" }, + { file = "pydantic_core-2.27.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7d0c8399fcc1848491f00e0314bd59fb34a9c008761bcb422a057670c3f65e35" }, + { file = "pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39" }, ] [package.dependencies] @@ -2860,112 +3126,85 @@ rsa = ["cryptography"] [[package]] name = "pyparsing" -version = "3.1.4" +version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" files = [ - { file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c" }, - { file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032" }, + { file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84" }, + { file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c" }, ] [package.extras] diagrams = ["jinja2", "railroad-diagrams"] -[[package]] -name = "pypng" -version = "0.20220715.0" -description = "Pure Python library for saving and loading PNG images" -optional = false -python-versions = "*" -files = [ - {file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"}, - {file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"}, -] - [[package]] name = "pyppmd" -version = "1.1.0" +version = "1.1.1" description = "PPMd compression/decompression library" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5cd428715413fe55abf79dc9fc54924ba7e518053e1fc0cbdf80d0d99cf1442"}, - {file = "pyppmd-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e96cc43f44b7658be2ea764e7fa99c94cb89164dbb7cdf209178effc2168319"}, - {file = "pyppmd-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd20142869094bceef5ab0b160f4fff790ad1f612313a1e3393a51fc3ba5d57e"}, - {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4f9b51e45c11e805e74ea6f6355e98a6423b5bbd92f45aceee24761bdc3d3b8"}, - {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459f85e928fb968d0e34fb6191fd8c4e710012d7d884fa2b317b2e11faac7c59"}, - {file = "pyppmd-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f73cf2aaf60477eef17f5497d14b6099d8be9748390ad2b83d1c88214d050c05"}, - {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ea3ae0e92c0b5345cd3a4e145e01bbd79c2d95355481ea5d833b5c0cb202a2d"}, - {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:775172c740133c0162a01c1a5443d0e312246881cdd6834421b644d89a634b91"}, - {file = "pyppmd-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14421030f1d46f69829698bdd960698a3b3df0925e3c470e82cfcdd4446b7bc1"}, - {file = "pyppmd-1.1.0-cp310-cp310-win32.whl", hash = "sha256:b691264f9962532aca3bba5be848b6370e596d0a2ca722c86df388be08d0568a"}, - {file = "pyppmd-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:216b0d969a3f06e35fbfef979706d987d105fcb1e37b0b1324f01ee143719c4a"}, - {file = "pyppmd-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1f8c51044ee4df1b004b10bf6b3c92f95ea86cfe1111210d303dca44a56e4282"}, - {file = "pyppmd-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac25b3a13d1ac9b8f0bde46952e10848adc79d932f2b548a6491ef8825ae0045"}, - {file = "pyppmd-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c8d3003eebe6aabe22ba744a38a146ed58a25633420d5da882b049342b7c8036"}, - {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c520656bc12100aa6388df27dd7ac738577f38bf43f4a4bea78e1861e579ea5"}, - {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c2a3e807028159a705951f5cb5d005f94caed11d0984e59cc50506de543e22d"}, - {file = "pyppmd-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec8a2447e69444703e2b273247bfcd4b540ec601780eff07da16344c62d2993d"}, - {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b9e0c8053e69cad6a92a0889b3324f567afc75475b4f54727de553ac4fc85780"}, - {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5938d256e8d2a2853dc3af8bb58ae6b4a775c46fc891dbe1826a0b3ceb624031"}, - {file = "pyppmd-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1ce5822d8bea920856232ccfb3c26b56b28b6846ea1b0eb3d5cb9592a026649e"}, - {file = "pyppmd-1.1.0-cp311-cp311-win32.whl", hash = "sha256:2a9e894750f2a52b03e3bc0d7cf004d96c3475a59b1af7e797d808d7d29c9ffe"}, - {file = "pyppmd-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:969555c72e72fe2b4dd944127521a8f2211caddb5df452bbc2506b5adfac539e"}, - {file = "pyppmd-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d6ef8fd818884e914bc209f7961c9400a4da50d178bba25efcef89f09ec9169"}, - {file = "pyppmd-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95f28e2ecf3a9656bd7e766aaa1162b6872b575627f18715f8b046e8617c124a"}, - {file = "pyppmd-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f3557ea65ee417abcdf5f49d35df00bb9f6f252639cae57aeefcd0dd596133"}, - {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e84b25d088d7727d50218f57f92127cdb839acd6ec3de670b6680a4cf0b2d2a"}, - {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99ed42891986dac8c2ecf52bddfb777900233d867aa18849dbba6f3335600466"}, - {file = "pyppmd-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6fe69b82634488ada75ba07efb90cd5866fa3d64a2c12932b6e8ae207a14e5f"}, - {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:60981ffde1fe6ade750b690b35318c41a1160a8505597fda2c39a74409671217"}, - {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46e8240315476f57aac23d71e6de003e122b65feba7c68f4cc46a089a82a7cd4"}, - {file = "pyppmd-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0308e2e76ecb4c878a18c2d7a7c61dbca89b4ef138f65d5f5ead139154dcdea"}, - {file = "pyppmd-1.1.0-cp312-cp312-win32.whl", hash = "sha256:b4fa4c27dc1314d019d921f2aa19e17f99250557e7569eeb70e180558f46af74"}, - {file = "pyppmd-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c269d21e15f4175df27cf00296476097af76941f948734c642d7fb6e85b9b3b9"}, - {file = "pyppmd-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a04ef5fd59818b035855723af85ce008c8191d31216706ffcbeedc505efca269"}, - {file = "pyppmd-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1e3ebcf5f95142268afa5cc46457d9dab2d29a3ccfd020a1129dd9d6bd021be1"}, - {file = "pyppmd-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4ad046a9525d1f52e93bc642a4cec0bf344a3ba1a15923e424e7a50f8ca003d8"}, - {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:169e5023c86ed1f7587961900f58aa78ad8a3d59de1e488a2228b5ba3de52402"}, - {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:baf798e76edd9da975cc536f943756a1b1755eb8ed87371f86f76d7c16e8d034"}, - {file = "pyppmd-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63be8c068879194c1e7548d0c57f54a4d305ba204cd0c7499b678f0aee893ef"}, - {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5fc178a3c21af78858acbac9782fca6a927267694c452e0882c55fec6e78319"}, - {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:28a1ab1ef0a31adce9b4c837b7b9acb01ce8f1f702ff3ff884f03d21c2f6b9bb"}, - {file = "pyppmd-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5fef43bfe98ada0a608adf03b2d205e071259027ab50523954c42eef7adcef67"}, - {file = "pyppmd-1.1.0-cp38-cp38-win32.whl", hash = "sha256:6b980902797eab821299a1c9f42fa78eff2826a6b0b0f6bde8a621f9765ffd55"}, - {file = "pyppmd-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:80cde69013f357483abe0c3ff30c55dc5e6b4f72b068f91792ce282c51dc0bff"}, - {file = "pyppmd-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aeea1bf585c6b8771fa43a6abd704da92f8a46a6d0020953af15d7f3c82e48c"}, - {file = "pyppmd-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7759bdb137694d4ab0cfa5ff2c75c212d90714c7da93544694f68001a0c38e12"}, - {file = "pyppmd-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db64a4fe956a2e700a737a1d019f526e6ccece217c163b28b354a43464cc495b"}, - {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f788ae8f5a9e79cd777b7969d3401b2a2b87f47abe306c2a03baca30595e9bd"}, - {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:324a178935c140210fca2043c688b77e79281da8172d2379a06e094f41735851"}, - {file = "pyppmd-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:363030bbcb7902fb9eeb59ffc262581ca5dd7790ba950328242fd2491c54d99b"}, - {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:31b882584f86440b0ff7906385c9f9d9853e5799197abaafdae2245f87d03f01"}, - {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b991b4501492ec3380b605fe30bee0b61480d305e98519d81c2a658b2de01593"}, - {file = "pyppmd-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6108044d943b826f97a9e79201242f61392d6c1fadba463b2069c4e6bc961e1"}, - {file = "pyppmd-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c45ce2968b7762d2cacf622b0a8f260295c6444e0883fd21a21017e3eaef16ed"}, - {file = "pyppmd-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5289f32ab4ec5f96a95da51309abd1769f928b0bff62047b3bc25c878c16ccb"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ad5da9f7592158e6b6b51d7cd15e536d8b23afbb4d22cba4e5744c7e0a3548b1"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc6543e7d12ef0a1466d291d655e3d6bca59c7336dbb53b62ccdd407822fb52b"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5e4008a45910e3c8c227f6f240de67eb14454c015dc3d8060fc41e230f395d3"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9301fa39d1fb0ed09a10b4c5d7f0074113e96a1ead16ba7310bedf95f7ef660c"}, - {file = "pyppmd-1.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:59521a3c6028da0cb5780ba16880047b00163432a6b975da2f6123adfc1b0be8"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d7ec02f1778dd68547e497625d66d7858ce10ea199146eb1d80ee23ba42954be"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f062ca743f9b99fe88d417b4d351af9b4ff1a7cbd3d765c058bb97de976d57f1"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:088e326b180a0469ac936849f5e1e5320118c22c9d9e673e9c8551153b839c84"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:897fa9ab5ff588a1000b8682835c5acf219329aa2bbfec478100e57d1204eeab"}, - {file = "pyppmd-1.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3af4338cc48cd59ee213af61d936419774a0f8600b9aa2013cd1917b108424f0"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cce8cd2d4ceebe2dbf41db6dfebe4c2e621314b3af8a2df2cba5eb5fa277f122"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62e57927dbcb91fb6290a41cd83743b91b9d85858efb16a0dd34fac208ee1c6b"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:435317949a6f35e54cdf08e0af6916ace427351e7664ac1593980114668f0aaa"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f66b0d0e32b8fb8707f1d2552f13edfc2917e8ed0bdf4d62e2ce190d2c70834"}, - {file = "pyppmd-1.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:650a663a591e06fb8096c213f4070b158981c8c3bf9c166ce7e4c360873f2750"}, - {file = "pyppmd-1.1.0.tar.gz", hash = "sha256:1d38ce2e4b7eb84b53bc8a52380b94f66ba6c39328b8800b30c2b5bf31693973"}, + { file = "pyppmd-1.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:406b184132c69e3f60ea9621b69eaa0c5494e83f82c307b3acce7b86a4f8f888" }, + { file = "pyppmd-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2cf003bb184adf306e1ac1828107307927737dde63474715ba16462e266cbef" }, + { file = "pyppmd-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71c8fd0ecc8d4760e852dd6df19d1a827427cb9e6c9e568cbf5edba7d860c514" }, + { file = "pyppmd-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6b5edee08b66ad6c39fd4d34a7ef4cfeb4b69fd6d68957e59cd2db674611a9e" }, + { file = "pyppmd-1.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e95bd23eb1543ab3149f24fe02f6dd2695023326027a4b989fb2c6dba256e75e" }, + { file = "pyppmd-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e633ee4cc19d0c71b3898092c3c4cc20a10bd5e6197229fffac29d68ad5d83b8" }, + { file = "pyppmd-1.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ecaafe2807ef557f0c49b8476a4fa04091b43866072fbcf31b3ceb01a96c9168" }, + { file = "pyppmd-1.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c182fccff60ae8f24f28f5145c36a60708b5b041a25d36b67f23c44923552fa4" }, + { file = "pyppmd-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:70c93d19efe67cdac3e7fa2d4e171650a2c4f90127a9781b25e496a43f12fbbc" }, + { file = "pyppmd-1.1.1-cp310-cp310-win32.whl", hash = "sha256:57c75856920a210ed72b553885af7bc06eddfd30ff26b62a3a63cb8f86f3d217" }, + { file = "pyppmd-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d5293f10dc8c1d571b780e0d54426d3d858c19bbd8cb0fe972dcea3906acd05c" }, + { file = "pyppmd-1.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:753c5297c91c059443caef33bccbffb10764221739d218046981638aeb9bc5f2" }, + { file = "pyppmd-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b5a73da09de480a94793c9064876af14a01be117de872737935ac447b7cde3c" }, + { file = "pyppmd-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89c6febb7114dea02a061143d78d04751a945dfcadff77560e9a3d3c7583c24b" }, + { file = "pyppmd-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0001e467c35e35e6076a8c32ed9074aa45833615ee16115de9282d5c0985a1d8" }, + { file = "pyppmd-1.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c76820db25596afc859336ba06c01c9be0ff326480beec9c699fd378a546a77f" }, + { file = "pyppmd-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b67f0a228f8c58750a21ba667c170ae957283e08fd580857f13cb686334e5b3e" }, + { file = "pyppmd-1.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b18f24c14f0b0f1757a42c458ae7b6fd7aa0bce8147ac1016a9c134068c1ccc2" }, + { file = "pyppmd-1.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c9e43729161cc3b6ad5b04b16bae7665d3c0cc803de047d8a979aa9232a4f94a" }, + { file = "pyppmd-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fe057d254528b4eeebe2800baefde47d6af679bae184d3793c13a06f794df442" }, + { file = "pyppmd-1.1.1-cp311-cp311-win32.whl", hash = "sha256:faa51240493a5c53c9b544c99722f70303eea702742bf90f3c3064144342da4a" }, + { file = "pyppmd-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:62486f544d6957e1381147e3961eee647b7f4421795be4fb4f1e29d52aee6cb5" }, + { file = "pyppmd-1.1.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9877ef273e2c0efdec740855e28004a708ada9012e0db6673df4bb6eba3b05e0" }, + { file = "pyppmd-1.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f816a5cbccceced80e15335389eeeaf1b56a605fb7eebe135b1c85bd161e288c" }, + { file = "pyppmd-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6bddabf8f2c6b991d15d6785e603d9d414ae4a791f131b1a729bb8a5d31133d1" }, + { file = "pyppmd-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:855bc2b0d19c3fead5815d72dbe350b4f765334336cbf8bcb504d46edc9e9dd2" }, + { file = "pyppmd-1.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a95b11b3717c083b912f0879678ba72f301bbdb9b69efed46dbc5df682aa3ce7" }, + { file = "pyppmd-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38b645347b6ea217b0c58e8edac27473802868f152db520344ac8c7490981849" }, + { file = "pyppmd-1.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f8f94b6222262def5b532f2b9716554ef249ad8411fd4da303596cc8c2e8eda1" }, + { file = "pyppmd-1.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1c0306f69ceddf385ef689ebd0218325b7e523c48333d87157b37393466cfa1e" }, + { file = "pyppmd-1.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4ba510457a56535522a660098399e3fa8722e4de55808d089c9d13435d87069" }, + { file = "pyppmd-1.1.1-cp312-cp312-win32.whl", hash = "sha256:032f040a89fd8348109e8638f94311bd4c3c693fb4cad213ad06a37c203690b1" }, + { file = "pyppmd-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:2be8cbd13dd59fad1a0ad38062809e28596f3673b77a799dfe82b287986265ed" }, + { file = "pyppmd-1.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9458f972f090f3846fc5bea0a6f7363da773d3c4b2d4654f1d4ca3c11f6ecbfa" }, + { file = "pyppmd-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:44811a9d958873d857ca81cebf7ba646a0952f8a7bbf8a60cf6ec5d002faa040" }, + { file = "pyppmd-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a1b12460958885ca44e433986644009d0599b87a444f668ce3724a46ce588924" }, + { file = "pyppmd-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:200c74f05b97b00f047cf60607914a0b50f80991f1fb3677f624a85aa79d9458" }, + { file = "pyppmd-1.1.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ebe0d98a341b32f164e860059243e125398865cc0363b32ffc31f953460fe87" }, + { file = "pyppmd-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf93e1e047a82f1e7e194fcf49da166d2b9d8dc98d7c0b5cd844dc4360d9c1f5" }, + { file = "pyppmd-1.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f5b0b8c746bde378ae3b4df42a11fd8599ba3e5808dfea36e16d722b74bd0506" }, + { file = "pyppmd-1.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bcdd5207b6c79887f25639632ca2623a399d8c54f567973e9ba474b5ebae2b1c" }, + { file = "pyppmd-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7bfcca94e5452b6d54ac24a11c2402f6a193c331e5dc221c1f1df71773624374" }, + { file = "pyppmd-1.1.1-cp39-cp39-win32.whl", hash = "sha256:18e99c074664f996f511bc6e87aab46bc4c75f5bd0157d3210292919be35e22c" }, + { file = "pyppmd-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b29788d5a0f8f39ea46a1255cd886daddf9c64ba9d4cb64677bc93bd3859ac0e" }, + { file = "pyppmd-1.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28648ef56793bf1ed0ff24728642f56fa39cb96ea161dec6ee2d26f97c0cdd28" }, + { file = "pyppmd-1.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:427d6f9b9c011e032db9529b2a15773f2e2944ca490b67d5757f4af33bbda406" }, + { file = "pyppmd-1.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34c7a07197a03656c1920fd88e05049c155a955c4de4b8b8a8e5fec19a97b45b" }, + { file = "pyppmd-1.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1fea2eee28beca61165c4714dcd032de76af318553791107d308b4b08575ecc" }, + { file = "pyppmd-1.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:04391e4f82c8c2c316ba60e480300ad1af37ec12bdb5c20f06b502030ff35975" }, + { file = "pyppmd-1.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cf08a354864c352a94e6e53733009baeab1e7c570010c4f5be226923ecfa09d1" }, + { file = "pyppmd-1.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:334e5fe5d75764b87c591a16d2b2df6f9939e2ad114dacf98bb4b0e7c90911e9" }, + { file = "pyppmd-1.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15d5928b25f04f5431585d17c835cd509a34e1c9f1416653db8d2815e97d4e20" }, + { file = "pyppmd-1.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af06329796a4965788910ac40f1b012d2e173ede08456ceea0ec7fc4d2e69d62" }, + { file = "pyppmd-1.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4ccdd3751e432e71e02de96f16fc8824e4f4bfc47a8b470f0c7aae88dae4c666" }, + { file = "pyppmd-1.1.1.tar.gz", hash = "sha256:f1a812f1e7628f4c26d05de340b91b72165d7b62778c27d322b82ce2e8ff00cb" }, ] [package.extras] -check = ["check-manifest", "flake8 (<5)", "flake8-black", "flake8-isort", "isort (>=5.0.3)", "mypy (>=0.812)", "mypy-extensions (>=0.4.3)", "pygments", "readme-renderer"] -docs = ["sphinx (>=2.3)", "sphinx-rtd-theme"] +check = ["check-manifest", "flake8", "flake8-black", "flake8-isort", "mypy (>=1.10.0)", "pygments", "readme-renderer"] +docs = ["sphinx", "sphinx_rtd_theme"] fuzzer = ["atheris", "hypothesis"] test = ["coverage[toml] (>=5.2)", "hypothesis", "pytest (>=6.0)", "pytest-benchmark", "pytest-cov", "pytest-timeout"] @@ -2999,13 +3238,13 @@ cli = ["click (>=5.0)"] [[package]] name = "pytz" -version = "2024.1" +version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + { file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" }, + { file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a" }, ] [[package]] @@ -3072,255 +3311,225 @@ files = [ [[package]] name = "pyzstd" -version = "0.16.1" +version = "0.16.2" description = "Python bindings to Zstandard (zstd) compression library." optional = false python-versions = ">=3.5" files = [ - { file = "pyzstd-0.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0cff110d121598f9eb638ce15393fece65bb5fac9a9d38c60fc5cb1ac8631465" }, - { file = "pyzstd-0.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:acbf3d01f79be0bd284ab316e33d6a3fceab478a932ce93de7275d7d9547b9be" }, - { file = "pyzstd-0.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1d26304c41cc07a87b1b85f4bf61a0f853368e0c00bb700dc7245971dedd53" }, - { file = "pyzstd-0.16.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c7507175f8d3f48358e28001a19242d3d4df819b6cd4cbc4f0fbe6f9dee9427" }, - { file = "pyzstd-0.16.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd62933e3a11f7dd6c892fa38c67e7ba45de17cae08f1355bf07b31e631a36f3" }, - { file = "pyzstd-0.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4725fb00bf06bd674f73f37cb168dd73ca67e68287207fece340e7425f0754d" }, - { file = "pyzstd-0.16.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9865ffbff114ad4411c9794deb1cbe57a03902f82a2671c23929a2628fd70bbc" }, - { file = "pyzstd-0.16.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:65fc3e12ad4d3ddc1f408e31ad2b70e110bbb7f835e4737f0f7b99ed1ff110cd" }, - { file = "pyzstd-0.16.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:397ca9ea892fece84fbbc5847ce46d16ee03501de3bbc6fb1f9b69bb14fe47a3" }, - { file = "pyzstd-0.16.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:83e770056823f8add3be33599219aa962c36f60eff24fa815579bc65bb053499" }, - { file = "pyzstd-0.16.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f949a5375ca8a546059193400b2e7c70f1a10de58bd87d35bdc31c6230e47ab0" }, - { file = "pyzstd-0.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:55e6dd2667a58ca92924f7ced5ac2c53ed26e07c453cfbde50693bf58c4c7b5b" }, - { file = "pyzstd-0.16.1-cp310-cp310-win32.whl", hash = "sha256:c088b57288a8e1818c032ed7e3e3e573b3fe8fad698d02740a1583f55458a73f" }, - { file = "pyzstd-0.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:089f3d04430b1044fccedbd4e88bd5429cd1220cf523b8841ead0127d8eedd9f" }, - { file = "pyzstd-0.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7bb570705a39e2a78619e6134a68be00ccd04398d782827180c0d1df79fc88c1" }, - { file = "pyzstd-0.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5633a0e9ac780a5577fc5dee3d6d05b8edf2f3d646ffe2c71e065d62a1b538c" }, - { file = "pyzstd-0.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61450162fb86504d16c00558976a4864ae12537e362f7346a0a79594ec2eb491" }, - { file = "pyzstd-0.16.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd3d79a74f863ec277ee3297b43f30178aa1a014eba54c286ea48f21248e525e" }, - { file = "pyzstd-0.16.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ddb50c2767ebf411f2b28e698d61d1671c87e943dac81b2a6e89529052c8ad" }, - { file = "pyzstd-0.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf0dec2978f9bc622c4daa48dd286f3f7e6ab196b1e17c46437abb6d4a968201" }, - { file = "pyzstd-0.16.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64ae91c0c19160cc0b95d33a5802e708ab15f11213f8043906d484b6062a80b3" }, - { file = "pyzstd-0.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9175bf699ec234189dd5549b4ededc676b66010e2eef5b3170501a17d765cf5" }, - { file = "pyzstd-0.16.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cdedcddd851139605b0dbc9b9ed5767052f67c02fa98c66b0a0bd4c1bce0ba49" }, - { file = "pyzstd-0.16.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:efeac4bf8a12cc0a1284164e77cca85727f8a5ec20328cef2e5c72f8eabf7630" }, - { file = "pyzstd-0.16.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b867f620b7402e0befa4b5e7eaa79693be099a52304f31bfc1006cdc915d21c7" }, - { file = "pyzstd-0.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d9f8aa524f99f593ebf38639e6d066984b0f9ed084d45ee8877761d1ee6aa48" }, - { file = "pyzstd-0.16.1-cp311-cp311-win32.whl", hash = "sha256:a4f2f1bd58361e4994e0fed4223038554bdb61644b2449f50f8c2960a8aeffc4" }, - { file = "pyzstd-0.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:81567ffe7f5ba6d6612399a82191448ba4f7780c96f2643bea36403a49462e0b" }, - { file = "pyzstd-0.16.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bb26734a5cda4b5e58b33c5fe20aee697fb9ad8dd72999bc71d7df09783f44db" }, - { file = "pyzstd-0.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b73e9d8ae8eca8dd600d54408584b625503761ad6b0e481e47e270a19e968141" }, - { file = "pyzstd-0.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b8af1f24361728cb0abeb447204015b2af016bfaf61d55b7c7bc44edc50348b" }, - { file = "pyzstd-0.16.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5faf5894b58f38491ecb458e6f4032ae0bbebea64dfeff86abc6c6176829ac3" }, - { file = "pyzstd-0.16.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:748ea21376016b77f93eb6e5d3fdf158620a27d36d2a05cb319f3e7b8b1943a5" }, - { file = "pyzstd-0.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb49c7854c6c56d9d41abdcd970b5fec2681a6a74f390b6f8f8fe9d1ca1f8530" }, - { file = "pyzstd-0.16.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68ea4cbeb5fa722222e8607ed22eab7723dfe8f502cbdaaab0989fc47f2fe4e6" }, - { file = "pyzstd-0.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c78ca31b0d83f77ab6ff041808304f51672f925683ffc3a1a866469f1678fc10" }, - { file = "pyzstd-0.16.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:deea37b1618f31fd2618be0aad42bb5bafcdddc24df9fc18c71071314239e3a2" }, - { file = "pyzstd-0.16.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aadbab6d6b79bd37697c3de28d4c2cbac3545ae9622be2f86ae5e426c6e1b192" }, - { file = "pyzstd-0.16.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3b23295a6aedc71e5318b7e490f2ed1ea3fda6b31f2b5957c8da49a5aac7aa81" }, - { file = "pyzstd-0.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f0a685bea6ba4e965d0de77cda3e380efeb144bb4fa0eff362626b4cdec71814" }, - { file = "pyzstd-0.16.1-cp312-cp312-win32.whl", hash = "sha256:ad8686ae57a59432860907e4c62d4b08b98d2330a129928145d797eda118da7b" }, - { file = "pyzstd-0.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:53ae4ac03c286896b2a6741c9069afd80e432526d267f900420d8083f8ab1f78" }, - { file = "pyzstd-0.16.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:96c04f3ef21f8c84672468358001b1f78b18f62a1b6af202e9fe0c71d0cd85f8" }, - { file = "pyzstd-0.16.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f3b74f42ac91dfcd5b3e8dfa691714e23c4bb3931070fdc134dbbaa2c92c51e" }, - { file = "pyzstd-0.16.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cba92b21b12bff45c0393e022ca4e6029aa5d4d3f11d1d9f05ca9a13245d325" }, - { file = "pyzstd-0.16.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:036d76e26300bc03cf05108a019fb0dd0a40ee6ed40128ead1c953fc603fba68" }, - { file = "pyzstd-0.16.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb00ce5e9a88e27f27db3ff4f4c6080c4158ad848d620b68d48bbc413d99f0ef" }, - { file = "pyzstd-0.16.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f7b5d93b5e7d3b3bd4a0f665b2bfab61a9cc78cb19b4f9d2faa454ae19133e" }, - { file = "pyzstd-0.16.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a426a253413ede9dad34fffde2d533950aa6aab82d0e9c7c7660168e323c43dc" }, - { file = "pyzstd-0.16.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3fcf498488cf2a866142a35d0c14c021a58c7d96b25bafd13c72676458912011" }, - { file = "pyzstd-0.16.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:2325ff41ff4bea19065894244c4dade5ae6b40df6e9def9dd4bc6e4c81edabf1" }, - { file = "pyzstd-0.16.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:593a4ec2f639a80523c6d8cb6a3f97899a4b3db4eadb768039dbd61fed4fe675" }, - { file = "pyzstd-0.16.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:13ab3b66c660438cf9031543a1cb9a4c7adde6b58b65e05783d32044178e871c" }, - { file = "pyzstd-0.16.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15a242d03c1516e1325d41a43b05c95abce0306d6f9782408b44f6225fadea9b" }, - { file = "pyzstd-0.16.1-cp38-cp38-win32.whl", hash = "sha256:763e084e0a7273d81d4bd68c4c89d642e3a447e30d1108d3dc0d0ec07a3ad01c" }, - { file = "pyzstd-0.16.1-cp38-cp38-win_amd64.whl", hash = "sha256:8b54ea942847b6e2f842f8b524f0c4dcc199f99b39420e06262cbcf25cb24363" }, - { file = "pyzstd-0.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2595819277b46d195565861f2966f58908444c7787da1ec45ea56390650013a6" }, - { file = "pyzstd-0.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f42bb898d5335e91d4575758cb11f68308756061d1eff042c7c4daa09cc560ba" }, - { file = "pyzstd-0.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa579210ae03a0aeeff86d492ff26acd358ec1daea8553beaac5f1ba774991d" }, - { file = "pyzstd-0.16.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:234423432d2e66328bdb06121aad3477bb97e200141a863aba0d1a14ff30b0cb" }, - { file = "pyzstd-0.16.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84059dfa917a2704e04776f26d5105bebc5019fc4f13379b44e71e57b575fc28" }, - { file = "pyzstd-0.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c870947722ae4c7de8e2d259690041f8b3332b1d75b4c3ca2caf17b170d10be3" }, - { file = "pyzstd-0.16.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3630a47b7d418e65521a45afbea5d77a825e4fb675fdf884eff42e6ce3230f91" }, - { file = "pyzstd-0.16.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:070434349fdd2fd56207a82a146c89a50811c5e0f767ac00d09f513919335f6f" }, - { file = "pyzstd-0.16.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59d016a105efd11db4305d43712ca2aab5e4f7dc73f42cc6324bc8f1b0ce2402" }, - { file = "pyzstd-0.16.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb2e62ed3d04fed425e009e9948c5e1478665475c5a6ca52d9f02295db7cffb1" }, - { file = "pyzstd-0.16.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1f00c7d40304329fbebbe9891cd2b144b09844876fe65a8bcfef71d80d417214" }, - { file = "pyzstd-0.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:28b33701e0a5bdb7aa96229ef7f680442894a4be3dfb39daf2fbae805778ade7" }, - { file = "pyzstd-0.16.1-cp39-cp39-win32.whl", hash = "sha256:7cdc3c293ab30ea141789a4454a4fd7b7858e005f6d2f61113d239a20d9bafd4" }, - { file = "pyzstd-0.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f6a7996f56abc23ad96bb73aea363720a1fca91a99822f8267bb5d3c4b7af7dc" }, - { file = "pyzstd-0.16.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cf08a0fa9af8d690a41b9b7db6b8ae174ba2ac42b5463993c2cd3d144a094644" }, - { file = "pyzstd-0.16.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:65683cb63d631b159e02738376987c26106b37a1345105c52067441e6259cf87" }, - { file = "pyzstd-0.16.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc09abfd5e281dba33a1cfdc653ece69fc239ad2c6cebd99506facbcb2669c91" }, - { file = "pyzstd-0.16.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46feda6257df4cde7dda55811851c2096dea7b38dcd601099acb95d7acdc795f" }, - { file = "pyzstd-0.16.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca952ce3388b5f7ee78931733ec41c8939482b466882e41d79a9a8c1387dd398" }, - { file = "pyzstd-0.16.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dc0e4d4c832adcd3c25a5d5b5bf0aa05bc25a279b8e8356eb2b95975b2a67fa0" }, - { file = "pyzstd-0.16.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ef5943a471b0d51cdb4eb05187b4be81cd6c95349e73818c4b959f60a05dfccd" }, - { file = "pyzstd-0.16.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:2df7e255b4aef73d7f8b11301bb6e39cf43e46cf80aa885ff7c1570565cf2398" }, - { file = "pyzstd-0.16.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a456ca431e4968a31c350004eca7957490f51245be8f3b44e49a9f143251312" }, - { file = "pyzstd-0.16.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1751fabc263654b3b4fbfb2729f63d6b3a51bf498bfbb1851ed332cd1b9a02e8" }, - { file = "pyzstd-0.16.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b1ce3eae59fd7093a05b8f073c7dce4795cccbf5987371fda5931b38fa9a567" }, - { file = "pyzstd-0.16.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:bc6326d017c618e7897c2f529dc71100403c0dfdbc523cd6c62f6ba1ed9f23f1" }, - { file = "pyzstd-0.16.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:487efbe3da2b879c5835e0d762bc8ea69e6bd765d31d6de32b20146bc7f5b2cc" }, - { file = "pyzstd-0.16.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4ae871967fc080a24118135dd8465339cf69c990fdea8755aef8806c5ebfb0e3" }, - { file = "pyzstd-0.16.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6467ba4ccbc1e09793e763c602079bb5b95813dcb2b0d2afffb40130b5927e69" }, - { file = "pyzstd-0.16.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1274d766f8a2655f99bd8f2ebc8f109ccf640734e941ca484ef03e275441e220" }, - { file = "pyzstd-0.16.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd568900f5ce7e2ced7928342b7cbc234c2b5648cff6a84bbf5e713377fce4f5" }, - { file = "pyzstd-0.16.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:123aba9d2bfdc1840b1fadd386c0095130948c10cd5a4f0acc48368d61448c9e" }, - { file = "pyzstd-0.16.1.tar.gz", hash = "sha256:ed50c08233878c155c73ab2622e115cd9e46c0f1c2e2ddd76f2e7ca24933f195" }, + { file = "pyzstd-0.16.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:637376c8f8cbd0afe1cab613f8c75fd502bd1016bf79d10760a2d5a00905fe62" }, + { file = "pyzstd-0.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e7a7118cbcfa90ca2ddbf9890c7cb582052a9a8cf2b7e2c1bbaf544bee0f16a" }, + { file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a74cb1ba05876179525144511eed3bd5a509b0ab2b10632c1215a85db0834dfd" }, + { file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c084dde218ffbf112e507e72cbf626b8f58ce9eb23eec129809e31037984662" }, + { file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4646459ebd3d7a59ddbe9312f020bcf7cdd1f059a2ea07051258f7af87a0b31" }, + { file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14bfc2833cc16d7657fc93259edeeaa793286e5031b86ca5dc861ba49b435fce" }, + { file = "pyzstd-0.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f27d488f19e5bf27d1e8aa1ae72c6c0a910f1e1ffbdf3c763d02ab781295dd27" }, + { file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:91e134ca968ff7dcfa8b7d433318f01d309b74ee87e0d2bcadc117c08e1c80db" }, + { file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6b5f64cd3963c58b8f886eb6139bb8d164b42a74f8a1bb95d49b4804f4592d61" }, + { file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b4a8266871b9e0407f9fd8e8d077c3558cf124d174e6357b523d14f76971009" }, + { file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1bb19f7acac30727354c25125922aa59f44d82e0e6a751df17d0d93ff6a73853" }, + { file = "pyzstd-0.16.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3008325b7368e794d66d4d98f2ee1d867ef5afd09fd388646ae02b25343c420d" }, + { file = "pyzstd-0.16.2-cp310-cp310-win32.whl", hash = "sha256:66f2d5c0bbf5bf32c577aa006197b3525b80b59804450e2c32fbcc2d16e850fd" }, + { file = "pyzstd-0.16.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fe5f5459ebe1161095baa7a86d04ab625b35148f6c425df0347ed6c90a2fd58" }, + { file = "pyzstd-0.16.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1bdbe7f01c7f37d5cd07be70e32a84010d7dfd6677920c0de04cf7d245b60d" }, + { file = "pyzstd-0.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1882a3ceaaf9adc12212d587d150ec5e58cfa9a765463d803d739abbd3ac0f7a" }, + { file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea46a8b9d60f6a6eba29facba54c0f0d70328586f7ef0da6f57edf7e43db0303" }, + { file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7865bc06589cdcecdede0deefe3da07809d5b7ad9044c224d7b2a0867256957" }, + { file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:52f938a65b409c02eb825e8c77fc5ea54508b8fc44b5ce226db03011691ae8cc" }, + { file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e97620d3f53a0282947304189deef7ca7f7d0d6dfe15033469dc1c33e779d5e5" }, + { file = "pyzstd-0.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c40e9983d017108670dc8df68ceef14c7c1cf2d19239213274783041d0e64c" }, + { file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7cd4b3b2c6161066e4bde6af1cf78ed3acf5d731884dd13fdf31f1db10830080" }, + { file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:454f31fd84175bb203c8c424f2255a343fa9bd103461a38d1bf50487c3b89508" }, + { file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5ef754a93743f08fb0386ce3596780bfba829311b49c8f4107af1a4bcc16935d" }, + { file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:be81081db9166e10846934f0e3576a263cbe18d81eca06e6a5c23533f8ce0dc6" }, + { file = "pyzstd-0.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:738bcb2fa1e5f1868986f5030955e64de53157fa1141d01f3a4daf07a1aaf644" }, + { file = "pyzstd-0.16.2-cp311-cp311-win32.whl", hash = "sha256:0ea214c9b97046867d1657d55979021028d583704b30c481a9c165191b08d707" }, + { file = "pyzstd-0.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:c17c0fc02f0e75b0c7cd21f8eaf4c6ce4112333b447d93da1773a5f705b2c178" }, + { file = "pyzstd-0.16.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4081fd841a9efe9ded7290ee7502dbf042c4158b90edfadea3b8a072c8ec4e1" }, + { file = "pyzstd-0.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd3fa45d2aeb65367dd702806b2e779d13f1a3fa2d13d5ec777cfd09de6822de" }, + { file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8b5f0d2c07994a5180d8259d51df6227a57098774bb0618423d7eb4a7303467" }, + { file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60c9d25b15c7ae06ed5d516d096a0d8254f9bed4368b370a09cccf191eaab5cb" }, + { file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29acf31ce37254f6cad08deb24b9d9ba954f426fa08f8fae4ab4fdc51a03f4ae" }, + { file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec77612a17697a9f7cf6634ffcee616eba9b997712fdd896e77fd19ab3a0618" }, + { file = "pyzstd-0.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:313ea4974be93be12c9a640ab40f0fc50a023178aae004a8901507b74f190173" }, + { file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e91acdefc8c2c6c3b8d5b1b5fe837dce4e591ecb7c0a2a50186f552e57d11203" }, + { file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:929bd91a403539e72b5b5cb97f725ac4acafe692ccf52f075e20cd9bf6e5493d" }, + { file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:740837a379aa32d110911ebcbbc524f9a9b145355737527543a884bd8777ca4f" }, + { file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:adfc0e80dd157e6d1e0b0112c8ecc4b58a7a23760bd9623d74122ef637cfbdb6" }, + { file = "pyzstd-0.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79b183beae1c080ad3dca39019e49b7785391947f9aab68893ad85d27828c6e7" }, + { file = "pyzstd-0.16.2-cp312-cp312-win32.whl", hash = "sha256:b8d00631a3c466bc313847fab2a01f6b73b3165de0886fb03210e08567ae3a89" }, + { file = "pyzstd-0.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:c0d43764e9a60607f35d8cb3e60df772a678935ab0e02e2804d4147377f4942c" }, + { file = "pyzstd-0.16.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3ae9ae7ad730562810912d7ecaf1fff5eaf4c726f4b4dfe04784ed5f06d7b91f" }, + { file = "pyzstd-0.16.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2ce8d3c213f76a564420f3d0137066ac007ce9fb4e156b989835caef12b367a7" }, + { file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2c14dac23c865e2d78cebd9087e148674b7154f633afd4709b4cd1520b99a61" }, + { file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4527969d66a943e36ef374eda847e918077de032d58b5df84d98ffd717b6fa77" }, + { file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd8256149b88e657e99f31e6d4b114c8ff2935951f1d8bb8e1fe501b224999c0" }, + { file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bd1f1822d65c9054bf36d35307bf8ed4aa2d2d6827431761a813628ff671b1d" }, + { file = "pyzstd-0.16.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6733f4d373ec9ad2c1976cf06f973a3324c1f9abe236d114d6bb91165a397d" }, + { file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7bec165ab6524663f00b69bfefd13a46a69fed3015754abaf81b103ec73d92c6" }, + { file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e4460fa6949aac6528a1ad0de8871079600b12b3ef4db49316306786a3598321" }, + { file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75df79ea0315c97d88337953a17daa44023dbf6389f8151903d371513f503e3c" }, + { file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:93e1d45f4a196afb6f18682c79bdd5399277ead105b67f30b35c04c207966071" }, + { file = "pyzstd-0.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:075e18b871f38a503b5d23e40a661adfc750bd4bd0bb8b208c1e290f3ceb8fa2" }, + { file = "pyzstd-0.16.2-cp313-cp313-win32.whl", hash = "sha256:9e4295eb299f8d87e3487852bca033d30332033272a801ca8130e934475e07a9" }, + { file = "pyzstd-0.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:18deedc70f858f4cf574e59f305d2a0678e54db2751a33dba9f481f91bc71c28" }, + { file = "pyzstd-0.16.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a9892b707ef52f599098b1e9528df0e7849c5ec01d3e8035fb0e67de4b464839" }, + { file = "pyzstd-0.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4fbd647864341f3c174c4a6d7f20e6ea6b4be9d840fb900dc0faf0849561badc" }, + { file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20ac2c15656cc6194c4fed1cb0e8159f9394d4ea1d58be755448743d2ec6c9c4" }, + { file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b239fb9a20c1be3374b9a2bd183ba624fd22ad7a3f67738c0d80cda68b4ae1d3" }, + { file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc52400412cdae2635e0978b8d6bcc0028cc638fdab2fd301f6d157675d26896" }, + { file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b766a6aeb8dbb6c46e622e7a1aebfa9ab03838528273796941005a5ce7257b1" }, + { file = "pyzstd-0.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd4b8676052f9d59579242bf3cfe5fd02532b6a9a93ab7737c118ae3b8509dc" }, + { file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c6c0a677aac7c0e3d2d2605d4d68ffa9893fdeeb2e071040eb7c8750969d463" }, + { file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:15f9c2d612e7e2023d68d321d1b479846751f792af89141931d44e82ae391394" }, + { file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:11740bff847aad23beef4085a1bb767d101895881fe891f0a911aa27d43c372c" }, + { file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b9067483ebe860e4130a03ee665b3d7be4ec1608b208e645d5e7eb3492379464" }, + { file = "pyzstd-0.16.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:988f0ba19b14c2fe0afefc444ac1edfb2f497b7d7c3212b2f587504cc2ec804e" }, + { file = "pyzstd-0.16.2-cp39-cp39-win32.whl", hash = "sha256:8855acb1c3e3829030b9e9e9973b19e2d70f33efb14ad5c474b4d086864c959c" }, + { file = "pyzstd-0.16.2-cp39-cp39-win_amd64.whl", hash = "sha256:018e88378df5e76f5e1d8cf4416576603b6bc4a103cbc66bb593eaac54c758de" }, + { file = "pyzstd-0.16.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4b631117b97a42ff6dfd0ffc885a92fff462d7c34766b28383c57b996f863338" }, + { file = "pyzstd-0.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:56493a3fbe1b651a02102dd0902b0aa2377a732ff3544fb6fb3f114ca18db52f" }, + { file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1eae9bdba4a1e5d3181331f403114ff5b8ce0f4b569f48eba2b9beb2deef1e4" }, + { file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1be6972391c8aeecc7e61feb96ffc8e77a401bcba6ed994e7171330c45a1948" }, + { file = "pyzstd-0.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:761439d687e3a5687c2ff5c6a1190e1601362a4a3e8c6c82ff89719d51d73e19" }, + { file = "pyzstd-0.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f5fbdb8cf31b60b2dc586fecb9b73e2f172c21a0b320ed275f7b8d8a866d9003" }, + { file = "pyzstd-0.16.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:183f26e34f9becf0f2db38be9c0bfb136753d228bcb47c06c69175901bea7776" }, + { file = "pyzstd-0.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:88318b64b5205a67748148d6d244097fa6cf61fcea02ad3435511b9e7155ae16" }, + { file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73142aa2571b6480136a1865ebda8257e09eabbc8bcd54b222202f6fa4febe1e" }, + { file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d3f8877c29a97f1b1bba16f3d3ab01ad10ad3da7bad317aecf36aaf8848b37c" }, + { file = "pyzstd-0.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f25754562473ac7de856b8331ebd5964f5d85601045627a5f0bb0e4e899990" }, + { file = "pyzstd-0.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6ce17e84310080c55c02827ad9bb17893c00a845c8386a328b346f814aabd2c1" }, + { file = "pyzstd-0.16.2.tar.gz", hash = "sha256:179c1a2ea1565abf09c5f2fd72f9ce7c54b2764cf7369e05c0bfd8f1f67f63d2" }, ] [[package]] name = "qrcode" -version = "7.4.2" +version = "8.0" description = "QR Code image generator" optional = false -python-versions = ">=3.7" +python-versions = "<4.0,>=3.9" files = [ - {file = "qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"}, - {file = "qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"}, + { file = "qrcode-8.0-py3-none-any.whl", hash = "sha256:9fc05f03305ad27a709eb742cf3097fa19e6f6f93bb9e2f039c0979190f6f1b1" }, + { file = "qrcode-8.0.tar.gz", hash = "sha256:025ce2b150f7fe4296d116ee9bad455a6643ab4f6e7dce541613a4758cbce347" }, ] [package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -pillow = {version = ">=9.1.0", optional = true, markers = "extra == \"pil\""} -pypng = "*" -typing-extensions = "*" +colorama = { version = "*", markers = "sys_platform == \"win32\"" } +pillow = { version = ">=9.1.0", optional = true, markers = "extra == \"pil\" or extra == \"all\"" } [package.extras] -all = ["pillow (>=9.1.0)", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"] -dev = ["pytest", "pytest-cov", "tox"] -maintainer = ["zest.releaser[recommended]"] +all = ["pillow (>=9.1.0)", "pypng"] pil = ["pillow (>=9.1.0)"] -test = ["coverage", "pytest"] +png = ["pypng"] [[package]] name = "rapidfuzz" -version = "3.9.6" +version = "3.11.0" description = "rapid fuzzy string matching" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - { file = "rapidfuzz-3.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7ed0d0b9c85720f0ae33ac5efc8dc3f60c1489dad5c29d735fbdf2f66f0431f" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f3deff6ab7017ed21b9aec5874a07ad13e6b2a688af055837f88b743c7bfd947" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3f9fc060160507b2704f7d1491bd58453d69689b580cbc85289335b14fe8ca" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4e86c2b3827fa6169ad6e7d4b790ce02a20acefb8b78d92fa4249589bbc7a2c" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f982e1aafb4bd8207a5e073b1efef9e68a984e91330e1bbf364f9ed157ed83f0" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9196a51d0ec5eaaaf5bca54a85b7b1e666fc944c332f68e6427503af9fb8c49e" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb5a514064e02585b1cc09da2fe406a6dc1a7e5f3e92dd4f27c53e5f1465ec81" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e3a4244f65dbc3580b1275480118c3763f9dc29fc3dd96610560cb5e140a4d4a" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6ebb910a702e41641e1e1dada3843bc11ba9107a33c98daef6945a885a40a07" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:624fbe96115fb39addafa288d583b5493bc76dab1d34d0ebba9987d6871afdf9" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1c59f1c1507b7a557cf3c410c76e91f097460da7d97e51c985343798e9df7a3c" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f6f0256cb27b6a0fb2e1918477d1b56473cd04acfa245376a342e7c15806a396" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-win32.whl", hash = "sha256:24d473d00d23a30a85802b502b417a7f5126019c3beec91a6739fe7b95388b24" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:248f6d2612e661e2b5f9a22bbd5862a1600e720da7bb6ad8a55bb1548cdfa423" }, - { file = "rapidfuzz-3.9.6-cp310-cp310-win_arm64.whl", hash = "sha256:e03fdf0e74f346ed7e798135df5f2a0fb8d6b96582b00ebef202dcf2171e1d1d" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52e4675f642fbc85632f691b67115a243cd4d2a47bdcc4a3d9a79e784518ff97" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1f93a2f13038700bd245b927c46a2017db3dcd4d4ff94687d74b5123689b873b" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b70500bca460264b8141d8040caee22e9cf0418c5388104ff0c73fb69ee28f" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1e037fb89f714a220f68f902fc6300ab7a33349f3ce8ffae668c3b3a40b0b06" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6792f66d59b86ccfad5e247f2912e255c85c575789acdbad8e7f561412ffed8a" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68d9cffe710b67f1969cf996983608cee4490521d96ea91d16bd7ea5dc80ea98" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63daaeeea76da17fa0bbe7fb05cba8ed8064bb1a0edf8360636557f8b6511961" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d214e063bffa13e3b771520b74f674b22d309b5720d4df9918ff3e0c0f037720" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ed443a2062460f44c0346cb9d269b586496b808c2419bbd6057f54061c9b9c75" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5b0c9b227ee0076fb2d58301c505bb837a290ae99ee628beacdb719f0626d749" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:82c9722b7dfaa71e8b61f8c89fed0482567fb69178e139fe4151fc71ed7df782" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c18897c95c0a288347e29537b63608a8f63a5c3cb6da258ac46fcf89155e723e" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-win32.whl", hash = "sha256:3e910cf08944da381159587709daaad9e59d8ff7bca1f788d15928f3c3d49c2a" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:59c4a61fab676d37329fc3a671618a461bfeef53a4d0b8b12e3bc24a14e166f8" }, - { file = "rapidfuzz-3.9.6-cp311-cp311-win_arm64.whl", hash = "sha256:8b4afea244102332973377fddbe54ce844d0916e1c67a5123432291717f32ffa" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:70591b28b218fff351b88cdd7f2359a01a71f9f7f5a2e465ce3715ed4b3c422b" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee2d8355c7343c631a03e57540ea06e8717c19ecf5ff64ea07e0498f7f161457" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:708fb675de0f47b9635d1cc6fbbf80d52cb710d0a1abbfae5c84c46e3abbddc3" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d66c247c2d3bb7a9b60567c395a15a929d0ebcc5f4ceedb55bfa202c38c6e0c" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15146301b32e6e3d2b7e8146db1a26747919d8b13690c7f83a4cb5dc111b3a08" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7a03da59b6c7c97e657dd5cd4bcaab5fe4a2affd8193958d6f4d938bee36679" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d2c2fe19e392dbc22695b6c3b2510527e2b774647e79936bbde49db7742d6f1" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:91aaee4c94cb45930684f583ffc4e7c01a52b46610971cede33586cf8a04a12e" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3f5702828c10768f9281180a7ff8597da1e5002803e1304e9519dd0f06d79a85" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ccd1763b608fb4629a0b08f00b3c099d6395e67c14e619f6341b2c8429c2f310" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc7a0d4b2cb166bc46d02c8c9f7551cde8e2f3c9789df3827309433ee9771163" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7496f53d40560a58964207b52586783633f371683834a8f719d6d965d223a2eb" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-win32.whl", hash = "sha256:5eb1a9272ca71bc72be5415c2fa8448a6302ea4578e181bb7da9db855b367df0" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-win_amd64.whl", hash = "sha256:0d21fc3c0ca507a1180152a6dbd129ebaef48facde3f943db5c1055b6e6be56a" }, - { file = "rapidfuzz-3.9.6-cp312-cp312-win_arm64.whl", hash = "sha256:43bb27a57c29dc5fa754496ba6a1a508480d21ae99ac0d19597646c16407e9f3" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:83a5ac6547a9d6eedaa212975cb8f2ce2aa07e6e30833b40e54a52b9f9999aa4" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:10f06139142ecde67078ebc9a745965446132b998f9feebffd71acdf218acfcc" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74720c3f24597f76c7c3e2c4abdff55f1664f4766ff5b28aeaa689f8ffba5fab" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2bce52b5c150878e558a0418c2b637fb3dbb6eb38e4eb27d24aa839920483e" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1611199f178793ca9a060c99b284e11f6d7d124998191f1cace9a0245334d219" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0308b2ad161daf502908a6e21a57c78ded0258eba9a8f5e2545e2dafca312507" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3eda91832201b86e3b70835f91522587725bec329ec68f2f7faf5124091e5ca7" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ece873c093aedd87fc07c2a7e333d52e458dc177016afa1edaf157e82b6914d8" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d97d3c9d209d5c30172baea5966f2129e8a198fec4a1aeb2f92abb6e82a2edb1" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6c4550d0db4931f5ebe9f0678916d1b06f06f5a99ba0b8a48b9457fd8959a7d4" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b6b8dd4af6324fc325d9483bec75ecf9be33e590928c9202d408e4eafff6a0a6" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16122ae448bc89e2bea9d81ce6cb0f751e4e07da39bd1e70b95cae2493857853" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-win32.whl", hash = "sha256:71cc168c305a4445109cd0d4925406f6e66bcb48fde99a1835387c58af4ecfe9" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-win_amd64.whl", hash = "sha256:59ee78f2ecd53fef8454909cda7400fe2cfcd820f62b8a5d4dfe930102268054" }, - { file = "rapidfuzz-3.9.6-cp313-cp313-win_arm64.whl", hash = "sha256:58b4ce83f223605c358ae37e7a2d19a41b96aa65b1fede99cc664c9053af89ac" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f469dbc9c4aeaac7dd005992af74b7dff94aa56a3ea063ce64e4b3e6736dd2f" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a9ed7ad9adb68d0fe63a156fe752bbf5f1403ed66961551e749641af2874da92" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39ffe48ffbeedf78d120ddfb9d583f2ca906712159a4e9c3c743c9f33e7b1775" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8502ccdea9084d54b6f737d96a3b60a84e3afed9d016686dc979b49cdac71613" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6a4bec4956e06b170ca896ba055d08d4c457dac745548172443982956a80e118" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c0488b1c273be39e109ff885ccac0448b2fa74dea4c4dc676bcf756c15f16d6" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0542c036cb6acf24edd2c9e0411a67d7ba71e29e4d3001a082466b86fc34ff30" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0a96b52c9f26857bf009e270dcd829381e7a634f7ddd585fa29b87d4c82146d9" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:6edd3cd7c4aa8c68c716d349f531bd5011f2ca49ddade216bb4429460151559f" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:50b2fb55d7ed58c66d49c9f954acd8fc4a3f0e9fd0ff708299bd8abb68238d0e" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:32848dfe54391636b84cda1823fd23e5a6b1dbb8be0e9a1d80e4ee9903820994" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:29146cb7a1bf69c87e928b31bffa54f066cb65639d073b36e1425f98cccdebc6" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-win32.whl", hash = "sha256:aed13e5edacb0ecadcc304cc66e93e7e77ff24f059c9792ee602c0381808e10c" }, - { file = "rapidfuzz-3.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:af440e36b828922256d0b4d79443bf2cbe5515fc4b0e9e96017ec789b36bb9fc" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:efa674b407424553024522159296690d99d6e6b1192cafe99ca84592faff16b4" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0b40ff76ee19b03ebf10a0a87938f86814996a822786c41c3312d251b7927849" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16a6c7997cb5927ced6f617122eb116ba514ec6b6f60f4803e7925ef55158891" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3f42504bdc8d770987fc3d99964766d42b2a03e4d5b0f891decdd256236bae0" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9462aa2be9f60b540c19a083471fdf28e7cf6434f068b631525b5e6251b35e" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1629698e68f47609a73bf9e73a6da3a4cac20bc710529215cbdf111ab603665b" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68bc7621843d8e9a7fd1b1a32729465bf94b47b6fb307d906da168413331f8d6" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c6254c50f15bc2fcc33cb93a95a81b702d9e6590f432a7f7822b8c7aba9ae288" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7e535a114fa575bc143e175e4ca386a467ec8c42909eff500f5f0f13dc84e3e0" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d50acc0e9d67e4ba7a004a14c42d1b1e8b6ca1c515692746f4f8e7948c673167" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:fa742ec60bec53c5a211632cf1d31b9eb5a3c80f1371a46a23ac25a1fa2ab209" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c256fa95d29cbe5aa717db790b231a9a5b49e5983d50dc9df29d364a1db5e35b" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-win32.whl", hash = "sha256:89acbf728b764421036c173a10ada436ecca22999851cdc01d0aa904c70d362d" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:c608fcba8b14d86c04cb56b203fed31a96e8a1ebb4ce99e7b70313c5bf8cf497" }, - { file = "rapidfuzz-3.9.6-cp39-cp39-win_arm64.whl", hash = "sha256:d41c00ded0e22e9dba88ff23ebe0dc9d2a5f21ba2f88e185ea7374461e61daa9" }, - { file = "rapidfuzz-3.9.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a65c2f63218ea2dedd56fc56361035e189ca123bd9c9ce63a9bef6f99540d681" }, - { file = "rapidfuzz-3.9.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:680dc78a5f889d3b89f74824b89fe357f49f88ad10d2c121e9c3ad37bac1e4eb" }, - { file = "rapidfuzz-3.9.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8ca862927a0b05bd825e46ddf82d0724ea44b07d898ef639386530bf9b40f15" }, - { file = "rapidfuzz-3.9.6-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2116fa1fbff21fa52cd46f3cfcb1e193ba1d65d81f8b6e123193451cd3d6c15e" }, - { file = "rapidfuzz-3.9.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dcb7d9afd740370a897c15da61d3d57a8d54738d7c764a99cedb5f746d6a003" }, - { file = "rapidfuzz-3.9.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1a5bd6401bb489e14cbb5981c378d53ede850b7cc84b2464cad606149cc4e17d" }, - { file = "rapidfuzz-3.9.6-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:29fda70b9d03e29df6fc45cc27cbcc235534b1b0b2900e0a3ae0b43022aaeef5" }, - { file = "rapidfuzz-3.9.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:88144f5f52ae977df9352029488326afadd7a7f42c6779d486d1f82d43b2b1f2" }, - { file = "rapidfuzz-3.9.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:715aeaabafba2709b9dd91acb2a44bad59d60b4616ef90c08f4d4402a3bbca60" }, - { file = "rapidfuzz-3.9.6-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af26ebd3714224fbf9bebbc27bdbac14f334c15f5d7043699cd694635050d6ca" }, - { file = "rapidfuzz-3.9.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101bd2df438861a005ed47c032631b7857dfcdb17b82beeeb410307983aac61d" }, - { file = "rapidfuzz-3.9.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2185e8e29809b97ad22a7f99281d1669a89bdf5fa1ef4ef1feca36924e675367" }, - { file = "rapidfuzz-3.9.6-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9e53c72d08f0e9c6e4a369e52df5971f311305b4487690c62e8dd0846770260c" }, - { file = "rapidfuzz-3.9.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a0cb157162f0cdd62e538c7bd298ff669847fc43a96422811d5ab933f4c16c3a" }, - { file = "rapidfuzz-3.9.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bb5ff2bd48132ed5e7fbb8f619885facb2e023759f2519a448b2c18afe07e5d" }, - { file = "rapidfuzz-3.9.6-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dc37f601865e8407e3a8037ffbc3afe0b0f837b2146f7632bd29d087385babe" }, - { file = "rapidfuzz-3.9.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a657eee4b94668faf1fa2703bdd803654303f7e468eb9ba10a664d867ed9e779" }, - { file = "rapidfuzz-3.9.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:51be6ab5b1d5bb32abd39718f2a5e3835502e026a8272d139ead295c224a6f5e" }, - { file = "rapidfuzz-3.9.6.tar.gz", hash = "sha256:5cf2a7d621e4515fee84722e93563bf77ff2cbe832a77a48b81f88f9e23b9e8d" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb8a54543d16ab1b69e2c5ed96cabbff16db044a50eddfc028000138ca9ddf33" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231c8b2efbd7f8d2ecd1ae900363ba168b8870644bb8f2b5aa96e4a7573bde19" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54e7f442fb9cca81e9df32333fb075ef729052bcabe05b0afc0441f462299114" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:906f1f2a1b91c06599b3dd1be207449c5d4fc7bd1e1fa2f6aef161ea6223f165" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed59044aea9eb6c663112170f2399b040d5d7b162828b141f2673e822093fa8" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cb1965a28b0fa64abdee130c788a0bc0bb3cf9ef7e3a70bf055c086c14a3d7e" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b488b244931d0291412917e6e46ee9f6a14376625e150056fe7c4426ef28225" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f0ba13557fec9d5ffc0a22826754a7457cc77f1b25145be10b7bb1d143ce84c6" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3871fa7dfcef00bad3c7e8ae8d8fd58089bad6fb21f608d2bf42832267ca9663" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:b2669eafee38c5884a6e7cc9769d25c19428549dcdf57de8541cf9e82822e7db" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:ffa1bb0e26297b0f22881b219ffc82a33a3c84ce6174a9d69406239b14575bd5" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:45b15b8a118856ac9caac6877f70f38b8a0d310475d50bc814698659eabc1cdb" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-win32.whl", hash = "sha256:22033677982b9c4c49676f215b794b0404073f8974f98739cb7234e4a9ade9ad" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:be15496e7244361ff0efcd86e52559bacda9cd975eccf19426a0025f9547c792" }, + { file = "rapidfuzz-3.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:714a7ba31ba46b64d30fccfe95f8013ea41a2e6237ba11a805a27cdd3bce2573" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8724a978f8af7059c5323d523870bf272a097478e1471295511cf58b2642ff83" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b63cb1f2eb371ef20fb155e95efd96e060147bdd4ab9fc400c97325dfee9fe1" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82497f244aac10b20710448645f347d862364cc4f7d8b9ba14bd66b5ce4dec18" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:339607394941801e6e3f6c1ecd413a36e18454e7136ed1161388de674f47f9d9" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84819390a36d6166cec706b9d8f0941f115f700b7faecab5a7e22fc367408bc3" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eea8d9e20632d68f653455265b18c35f90965e26f30d4d92f831899d6682149b" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b659e1e2ea2784a9a397075a7fc395bfa4fe66424042161c4bcaf6e4f637b38" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1315cd2a351144572e31fe3df68340d4b83ddec0af8b2e207cd32930c6acd037" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a7743cca45b4684c54407e8638f6d07b910d8d811347b9d42ff21262c7c23245" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5bb636b0150daa6d3331b738f7c0f8b25eadc47f04a40e5c23c4bfb4c4e20ae3" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:42f4dd264ada7a9aa0805ea0da776dc063533917773cf2df5217f14eb4429eae" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51f24cb39e64256221e6952f22545b8ce21cacd59c0d3e367225da8fc4b868d8" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-win32.whl", hash = "sha256:aaf391fb6715866bc14681c76dc0308f46877f7c06f61d62cc993b79fc3c4a2a" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ebadd5b8624d8ad503e505a99b8eb26fe3ea9f8e9c2234e805a27b269e585842" }, + { file = "rapidfuzz-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:d895998fec712544c13cfe833890e0226585cf0391dd3948412441d5d68a2b8c" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f382fec4a7891d66fb7163c90754454030bb9200a13f82ee7860b6359f3f2fa8" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dfaefe08af2a928e72344c800dcbaf6508e86a4ed481e28355e8d4b6a6a5230e" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92ebb7c12f682b5906ed98429f48a3dd80dd0f9721de30c97a01473d1a346576" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a1b3ebc62d4bcdfdeba110944a25ab40916d5383c5e57e7c4a8dc0b6c17211a" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c6d7fea39cb33e71de86397d38bf7ff1a6273e40367f31d05761662ffda49e4" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99aebef8268f2bc0b445b5640fd3312e080bd17efd3fbae4486b20ac00466308" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4469307f464ae3089acf3210b8fc279110d26d10f79e576f385a98f4429f7d97" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:eb97c53112b593f89a90b4f6218635a9d1eea1d7f9521a3b7d24864228bbc0aa" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ef8937dae823b889c0273dfa0f0f6c46a3658ac0d851349c464d1b00e7ff4252" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d95f9e9f3777b96241d8a00d6377cc9c716981d828b5091082d0fe3a2924b43e" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b1d67d67f89e4e013a5295e7523bc34a7a96f2dba5dd812c7c8cb65d113cbf28" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d994cf27e2f874069884d9bddf0864f9b90ad201fcc9cb2f5b82bacc17c8d5f2" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-win32.whl", hash = "sha256:ba26d87fe7fcb56c4a53b549a9e0e9143f6b0df56d35fe6ad800c902447acd5b" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f7efdd7b7adb32102c2fa481ad6f11923e2deb191f651274be559d56fc913b" }, + { file = "rapidfuzz-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:ed78c8e94f57b44292c1a0350f580e18d3a3c5c0800e253f1583580c1b417ad2" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e60814edd0c9b511b5f377d48b9782b88cfe8be07a98f99973669299c8bb318a" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f28952da055dbfe75828891cd3c9abf0984edc8640573c18b48c14c68ca5e06" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e8f93bc736020351a6f8e71666e1f486bb8bd5ce8112c443a30c77bfde0eb68" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76a4a11ba8f678c9e5876a7d465ab86def047a4fcc043617578368755d63a1bc" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc0e0d41ad8a056a9886bac91ff9d9978e54a244deb61c2972cc76b66752de9c" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e8ea35f2419c7d56b3e75fbde2698766daedb374f20eea28ac9b1f668ef4f74" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd340bbd025302276b5aa221dccfe43040c7babfc32f107c36ad783f2ffd8775" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:494eef2c68305ab75139034ea25328a04a548d297712d9cf887bf27c158c388b" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5a167344c1d6db06915fb0225592afdc24d8bafaaf02de07d4788ddd37f4bc2f" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c7af25bda96ac799378ac8aba54a8ece732835c7b74cfc201b688a87ed11152" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d2a0f7e17f33e7890257367a1662b05fecaf56625f7dbb6446227aaa2b86448b" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d0d26c7172bdb64f86ee0765c5b26ea1dc45c52389175888ec073b9b28f4305" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-win32.whl", hash = "sha256:6ad02bab756751c90fa27f3069d7b12146613061341459abf55f8190d899649f" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:b1472986fd9c5d318399a01a0881f4a0bf4950264131bb8e2deba9df6d8c362b" }, + { file = "rapidfuzz-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c408f09649cbff8da76f8d3ad878b64ba7f7abdad1471efb293d2c075e80c822" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1bac4873f6186f5233b0084b266bfb459e997f4c21fc9f029918f44a9eccd304" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f9f12c2d0aa52b86206d2059916153876a9b1cf9dfb3cf2f344913167f1c3d4" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dd501de6f7a8f83557d20613b58734d1cb5f0be78d794cde64fe43cfc63f5f2" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4416ca69af933d4a8ad30910149d3db6d084781d5c5fdedb713205389f535385" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f0821b9bdf18c5b7d51722b906b233a39b17f602501a966cfbd9b285f8ab83cd" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0edecc3f90c2653298d380f6ea73b536944b767520c2179ec5d40b9145e47aa" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4513dd01cee11e354c31b75f652d4d466c9440b6859f84e600bdebfccb17735a" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d9727b85511b912571a76ce53c7640ba2c44c364e71cef6d7359b5412739c570" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ab9eab33ee3213f7751dc07a1a61b8d9a3d748ca4458fffddd9defa6f0493c16" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6b01c1ddbb054283797967ddc5433d5c108d680e8fa2684cf368be05407b07e4" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3857e335f97058c4b46fa39ca831290b70de554a5c5af0323d2f163b19c5f2a6" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d98a46cf07c0c875d27e8a7ed50f304d83063e49b9ab63f21c19c154b4c0d08d" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-win32.whl", hash = "sha256:c36539ed2c0173b053dafb221458812e178cfa3224ade0960599bec194637048" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:ec8d7d8567e14af34a7911c98f5ac74a3d4a743cd848643341fc92b12b3784ff" }, + { file = "rapidfuzz-3.11.0-cp39-cp39-win_arm64.whl", hash = "sha256:62171b270ecc4071be1c1f99960317db261d4c8c83c169e7f8ad119211fe7397" }, + { file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f06e3c4c0a8badfc4910b9fd15beb1ad8f3b8fafa8ea82c023e5e607b66a78e4" }, + { file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fe7aaf5a54821d340d21412f7f6e6272a9b17a0cbafc1d68f77f2fc11009dcd5" }, + { file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25398d9ac7294e99876a3027ffc52c6bebeb2d702b1895af6ae9c541ee676702" }, + { file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a52eea839e4bdc72c5e60a444d26004da00bb5bc6301e99b3dde18212e41465" }, + { file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c87319b0ab9d269ab84f6453601fd49b35d9e4a601bbaef43743f26fabf496c" }, + { file = "rapidfuzz-3.11.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3048c6ed29d693fba7d2a7caf165f5e0bb2b9743a0989012a98a47b975355cca" }, + { file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b04f29735bad9f06bb731c214f27253bd8bedb248ef9b8a1b4c5bde65b838454" }, + { file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7864e80a0d4e23eb6194254a81ee1216abdc53f9dc85b7f4d56668eced022eb8" }, + { file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3794df87313dfb56fafd679b962e0613c88a293fd9bd5dd5c2793d66bf06a101" }, + { file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d71da0012face6f45432a11bc59af19e62fac5a41f8ce489e80c0add8153c3d1" }, + { file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff38378346b7018f42cbc1f6d1d3778e36e16d8595f79a312b31e7c25c50bd08" }, + { file = "rapidfuzz-3.11.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6668321f90aa02a5a789d4e16058f2e4f2692c5230252425c3532a8a62bc3424" }, + { file = "rapidfuzz-3.11.0.tar.gz", hash = "sha256:a53ca4d3f52f00b393fab9b5913c5bafb9afc27d030c8a1db1283da6917a860f" }, ] [package.extras] -full = ["numpy"] +all = ["numpy"] [[package]] name = "rich" -version = "13.8.0" +version = "13.9.4" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - { file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc" }, - { file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4" }, + { file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" }, + { file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098" }, ] [package.dependencies] @@ -3330,6 +3539,33 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "ruff" +version = "0.8.4" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + { file = "ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60" }, + { file = "ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac" }, + { file = "ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296" }, + { file = "ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643" }, + { file = "ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e" }, + { file = "ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3" }, + { file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f" }, + { file = "ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604" }, + { file = "ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf" }, + { file = "ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720" }, + { file = "ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae" }, + { file = "ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7" }, + { file = "ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111" }, + { file = "ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8" }, + { file = "ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835" }, + { file = "ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d" }, + { file = "ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08" }, + { file = "ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -3343,13 +3579,13 @@ files = [ [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + { file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274" }, + { file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" }, ] [[package]] @@ -3365,60 +3601,68 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.32" +version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - { file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619" }, - { file = "SQLAlchemy-2.0.32-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389" }, - { file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b" }, - { file = "SQLAlchemy-2.0.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533" }, - { file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0" }, - { file = "SQLAlchemy-2.0.32-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8" }, - { file = "SQLAlchemy-2.0.32-cp310-cp310-win32.whl", hash = "sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d" }, - { file = "SQLAlchemy-2.0.32-cp310-cp310-win_amd64.whl", hash = "sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-win32.whl", hash = "sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28" }, - { file = "SQLAlchemy-2.0.32-cp311-cp311-win_amd64.whl", hash = "sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-win32.whl", hash = "sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d" }, - { file = "SQLAlchemy-2.0.32-cp312-cp312-win_amd64.whl", hash = "sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb" }, - { file = "SQLAlchemy-2.0.32-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e" }, - { file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad" }, - { file = "SQLAlchemy-2.0.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be" }, - { file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16" }, - { file = "SQLAlchemy-2.0.32-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1" }, - { file = "SQLAlchemy-2.0.32-cp37-cp37m-win32.whl", hash = "sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da" }, - { file = "SQLAlchemy-2.0.32-cp37-cp37m-win_amd64.whl", hash = "sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-win32.whl", hash = "sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65" }, - { file = "SQLAlchemy-2.0.32-cp38-cp38-win_amd64.whl", hash = "sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-win32.whl", hash = "sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78" }, - { file = "SQLAlchemy-2.0.32-cp39-cp39-win_amd64.whl", hash = "sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84" }, - { file = "SQLAlchemy-2.0.32-py3-none-any.whl", hash = "sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202" }, - { file = "SQLAlchemy-2.0.32.tar.gz", hash = "sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa" }, + { file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f" }, + { file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e" }, + { file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436" }, + { file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88" }, + { file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b" }, + { file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d" }, + { file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971" }, + { file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2" }, + { file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575" }, + { file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c" }, + { file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e" }, + { file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28" }, + { file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a" }, + { file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e" }, + { file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5" }, ] [package.dependencies] @@ -3431,7 +3675,7 @@ aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] @@ -3452,13 +3696,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "starlette" -version = "0.38.2" +version = "0.41.3" description = "The little ASGI library that shines." optional = false python-versions = ">=3.8" files = [ - { file = "starlette-0.38.2-py3-none-any.whl", hash = "sha256:4ec6a59df6bbafdab5f567754481657f7ed90dc9d69b0c9ff017907dd54faeff" }, - { file = "starlette-0.38.2.tar.gz", hash = "sha256:c7c0441065252160993a1a37cf2a73bb64d271b17303e0b0c1eb7191cfb12d75" }, + { file = "starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7" }, + { file = "starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835" }, ] [package.dependencies] @@ -3480,32 +3724,33 @@ files = [ [[package]] name = "textual" -version = "0.78.0" +version = "1.0.0" description = "Modern Text User Interface framework" optional = false python-versions = "<4.0.0,>=3.8.1" files = [ - { file = "textual-0.78.0-py3-none-any.whl", hash = "sha256:c9d3c7dc467c37ee2e54a0283ac2c85dac35e4fc949518ed054a65b8e3e9b822" }, - { file = "textual-0.78.0.tar.gz", hash = "sha256:421f508b0d41ea0b8ecf273bf83f0d19376667eb0a87f70575252395d90ab315" }, + { file = "textual-1.0.0-py3-none-any.whl", hash = "sha256:2d4a701781c05104925e463ae370c630567c70c2880e92ab838052e3e23c986f" }, + { file = "textual-1.0.0.tar.gz", hash = "sha256:bec9fe63547c1c552569d1b75d309038b7d456c03f86dfa3706ddb099b151399" }, ] [package.dependencies] markdown-it-py = {version = ">=2.1.0", extras = ["linkify", "plugins"]} +platformdirs = ">=3.6.0,<5" rich = ">=13.3.3" typing-extensions = ">=4.4.0,<5.0.0" [package.extras] -syntax = ["tree-sitter (>=0.20.1,<0.21.0)", "tree-sitter-languages (==1.10.2)"] +syntax = ["tree-sitter (>=0.23.0)", "tree-sitter-bash (>=0.23.0)", "tree-sitter-css (>=0.23.0)", "tree-sitter-go (>=0.23.0)", "tree-sitter-html (>=0.23.0)", "tree-sitter-java (>=0.23.0)", "tree-sitter-javascript (>=0.23.0)", "tree-sitter-json (>=0.24.0)", "tree-sitter-markdown (>=0.3.0)", "tree-sitter-python (>=0.23.0)", "tree-sitter-regex (>=0.24.0)", "tree-sitter-rust (>=0.23.0)", "tree-sitter-sql (>=0.3.0)", "tree-sitter-toml (>=0.6.0)", "tree-sitter-xml (>=0.7.0)", "tree-sitter-yaml (>=0.6.0)"] [[package]] name = "typer" -version = "0.12.5" +version = "0.15.1" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" files = [ - { file = "typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b" }, - { file = "typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722" }, + { file = "typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847" }, + { file = "typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a" }, ] [package.dependencies] @@ -3516,13 +3761,13 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "types-beautifulsoup4" -version = "4.12.0.20240511" +version = "4.12.0.20241020" description = "Typing stubs for beautifulsoup4" optional = false python-versions = ">=3.8" files = [ - {file = "types-beautifulsoup4-4.12.0.20240511.tar.gz", hash = "sha256:004f6096fdd83b19cdbf6cb10e4eae57b10205eccc365d0a69d77da836012e28"}, - {file = "types_beautifulsoup4-4.12.0.20240511-py3-none-any.whl", hash = "sha256:7ceda66a93ba28d759d5046d7fec9f4cad2f563a77b3a789efc90bcadafeefd1"}, + { file = "types-beautifulsoup4-4.12.0.20241020.tar.gz", hash = "sha256:158370d08d0cd448bd11b132a50ff5279237a5d4b5837beba074de152a513059" }, + { file = "types_beautifulsoup4-4.12.0.20241020-py3-none-any.whl", hash = "sha256:c95e66ce15a4f5f0835f7fbc5cd886321ae8294f977c495424eaf4225307fd30" }, ] [package.dependencies] @@ -3530,24 +3775,24 @@ types-html5lib = "*" [[package]] name = "types-html5lib" -version = "1.1.11.20240806" +version = "1.1.11.20241018" description = "Typing stubs for html5lib" optional = false python-versions = ">=3.8" files = [ - { file = "types-html5lib-1.1.11.20240806.tar.gz", hash = "sha256:8060dc98baf63d6796a765bbbc809fff9f7a383f6e3a9add526f814c086545ef" }, - { file = "types_html5lib-1.1.11.20240806-py3-none-any.whl", hash = "sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4" }, + { file = "types-html5lib-1.1.11.20241018.tar.gz", hash = "sha256:98042555ff78d9e3a51c77c918b1041acbb7eb6c405408d8a9e150ff5beccafa" }, + { file = "types_html5lib-1.1.11.20241018-py3-none-any.whl", hash = "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403" }, ] [[package]] name = "types-lxml" -version = "2024.8.7" +version = "2024.12.13" description = "Complete lxml external type annotation" optional = false python-versions = ">=3.8" files = [ - { file = "types_lxml-2024.8.7-py3-none-any.whl", hash = "sha256:a0b8669b2dc57d47dcf31fbbee5007f8ed71b37406f4c7e5fa650e2480568eb9" }, - { file = "types_lxml-2024.8.7.tar.gz", hash = "sha256:9ee5cdb1efd60f6eeb101b78f92591fd99202e4878b46d621b52f6cd67a9c80f" }, + { file = "types_lxml-2024.12.13-py3-none-any.whl", hash = "sha256:d4830c99ef6f7b9eae176297a2b8dc840b3a75986bf4449592ca09a9a449b27e" }, + { file = "types_lxml-2024.12.13.tar.gz", hash = "sha256:e2dadb92c7f730cd369daf1efe93ebc2ebfa8b692d4415cfc91b727419152e37" }, ] [package.dependencies] @@ -3556,7 +3801,8 @@ types-beautifulsoup4 = ">=4.12,<5.0" typing_extensions = { version = ">=4.10,<5.0", markers = "python_version < \"3.13\"" } [package.extras] -test = ["beautifulsoup4 (>=4.8,<5.0)", "html5lib (==1.1)", "lxml (>=4.9)", "mypy (>=1.11.0,<1.12.0)", "pyright (>=1.1.351)", "pytest (>=7.0,<9)", "pytest-mypy-plugins (>=2.0)", "tox (>=4.0,<5.0)", "typeguard (>=3.0)", "typeguard (>=4.3.0)"] +mypy = ["mypy (>=1.11,<2.0)"] +pyright = ["pyright (>=1.1.351)"] [[package]] name = "typing-extensions" @@ -3571,13 +3817,13 @@ files = [ [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + { file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd" }, + { file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc" }, ] [[package]] @@ -3700,20 +3946,20 @@ files = [ [[package]] name = "uvicorn" -version = "0.30.6" +version = "0.34.0" description = "The lightning-fast ASGI server." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - { file = "uvicorn-0.30.6-py3-none-any.whl", hash = "sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5" }, - { file = "uvicorn-0.30.6.tar.gz", hash = "sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788" }, + { file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4" }, + { file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9" }, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +httptools = { version = ">=0.6.3", optional = true, markers = "extra == \"standard\"" } python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} @@ -3721,142 +3967,137 @@ watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standar websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" -version = "0.20.0" +version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" files = [ - { file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996" }, - { file = "uvloop-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b" }, - { file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10" }, - { file = "uvloop-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae" }, - { file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006" }, - { file = "uvloop-0.20.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73" }, - { file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037" }, - { file = "uvloop-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9" }, - { file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e" }, - { file = "uvloop-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756" }, - { file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0" }, - { file = "uvloop-0.20.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf" }, - { file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d" }, - { file = "uvloop-0.20.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e" }, - { file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9" }, - { file = "uvloop-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab" }, - { file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5" }, - { file = "uvloop-0.20.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00" }, - { file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba" }, - { file = "uvloop-0.20.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf" }, - { file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847" }, - { file = "uvloop-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b" }, - { file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6" }, - { file = "uvloop-0.20.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2" }, - { file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95" }, - { file = "uvloop-0.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7" }, - { file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a" }, - { file = "uvloop-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541" }, - { file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315" }, - { file = "uvloop-0.20.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66" }, - { file = "uvloop-0.20.0.tar.gz", hash = "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469" }, + { file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f" }, + { file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d" }, + { file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26" }, + { file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb" }, + { file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f" }, + { file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c" }, + { file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8" }, + { file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0" }, + { file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e" }, + { file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb" }, + { file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6" }, + { file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d" }, + { file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c" }, + { file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2" }, + { file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d" }, + { file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc" }, + { file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb" }, + { file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f" }, + { file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281" }, + { file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af" }, + { file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6" }, + { file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816" }, + { file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc" }, + { file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553" }, + { file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414" }, + { file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206" }, + { file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe" }, + { file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79" }, + { file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a" }, + { file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc" }, + { file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b" }, + { file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2" }, + { file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0" }, + { file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75" }, + { file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd" }, + { file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff" }, + { file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3" }, ] [package.extras] +dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"] docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] +test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"] [[package]] name = "watchfiles" -version = "0.24.0" +version = "1.0.3" description = "Simple, modern and high performance file watching and code reload in python." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - { file = "watchfiles-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:083dc77dbdeef09fa44bb0f4d1df571d2e12d8a8f985dccde71ac3ac9ac067a0" }, - { file = "watchfiles-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e94e98c7cb94cfa6e071d401ea3342767f28eb5a06a58fafdc0d2a4974f4f35c" }, - { file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82ae557a8c037c42a6ef26c494d0631cacca040934b101d001100ed93d43f361" }, - { file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acbfa31e315a8f14fe33e3542cbcafc55703b8f5dcbb7c1eecd30f141df50db3" }, - { file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74fdffce9dfcf2dc296dec8743e5b0332d15df19ae464f0e249aa871fc1c571" }, - { file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:449f43f49c8ddca87c6b3980c9284cab6bd1f5c9d9a2b00012adaaccd5e7decd" }, - { file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf4ad269856618f82dee296ac66b0cd1d71450fc3c98532d93798e73399b7a" }, - { file = "watchfiles-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f895d785eb6164678ff4bb5cc60c5996b3ee6df3edb28dcdeba86a13ea0465e" }, - { file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ae3e208b31be8ce7f4c2c0034f33406dd24fbce3467f77223d10cd86778471c" }, - { file = "watchfiles-0.24.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2efec17819b0046dde35d13fb8ac7a3ad877af41ae4640f4109d9154ed30a188" }, - { file = "watchfiles-0.24.0-cp310-none-win32.whl", hash = "sha256:6bdcfa3cd6fdbdd1a068a52820f46a815401cbc2cb187dd006cb076675e7b735" }, - { file = "watchfiles-0.24.0-cp310-none-win_amd64.whl", hash = "sha256:54ca90a9ae6597ae6dc00e7ed0a040ef723f84ec517d3e7ce13e63e4bc82fa04" }, - { file = "watchfiles-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:bdcd5538e27f188dd3c804b4a8d5f52a7fc7f87e7fd6b374b8e36a4ca03db428" }, - { file = "watchfiles-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dadf8a8014fde6addfd3c379e6ed1a981c8f0a48292d662e27cabfe4239c83c" }, - { file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6509ed3f467b79d95fc62a98229f79b1a60d1b93f101e1c61d10c95a46a84f43" }, - { file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8360f7314a070c30e4c976b183d1d8d1585a4a50c5cb603f431cebcbb4f66327" }, - { file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:316449aefacf40147a9efaf3bd7c9bdd35aaba9ac5d708bd1eb5763c9a02bef5" }, - { file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73bde715f940bea845a95247ea3e5eb17769ba1010efdc938ffcb967c634fa61" }, - { file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3770e260b18e7f4e576edca4c0a639f704088602e0bc921c5c2e721e3acb8d15" }, - { file = "watchfiles-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa0fd7248cf533c259e59dc593a60973a73e881162b1a2f73360547132742823" }, - { file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7a2e3b7f5703ffbd500dabdefcbc9eafeff4b9444bbdd5d83d79eedf8428fab" }, - { file = "watchfiles-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d831ee0a50946d24a53821819b2327d5751b0c938b12c0653ea5be7dea9c82ec" }, - { file = "watchfiles-0.24.0-cp311-none-win32.whl", hash = "sha256:49d617df841a63b4445790a254013aea2120357ccacbed00253f9c2b5dc24e2d" }, - { file = "watchfiles-0.24.0-cp311-none-win_amd64.whl", hash = "sha256:d3dcb774e3568477275cc76554b5a565024b8ba3a0322f77c246bc7111c5bb9c" }, - { file = "watchfiles-0.24.0-cp311-none-win_arm64.whl", hash = "sha256:9301c689051a4857d5b10777da23fafb8e8e921bcf3abe6448a058d27fb67633" }, - { file = "watchfiles-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7211b463695d1e995ca3feb38b69227e46dbd03947172585ecb0588f19b0d87a" }, - { file = "watchfiles-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b8693502d1967b00f2fb82fc1e744df128ba22f530e15b763c8d82baee15370" }, - { file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdab9555053399318b953a1fe1f586e945bc8d635ce9d05e617fd9fe3a4687d6" }, - { file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e19e56d68b0dad5cff62273107cf5d9fbaf9d75c46277aa5d803b3ef8a9e9b" }, - { file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41face41f036fee09eba33a5b53a73e9a43d5cb2c53dad8e61fa6c9f91b5a51e" }, - { file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5148c2f1ea043db13ce9b0c28456e18ecc8f14f41325aa624314095b6aa2e9ea" }, - { file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e4bd963a935aaf40b625c2499f3f4f6bbd0c3776f6d3bc7c853d04824ff1c9f" }, - { file = "watchfiles-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c79d7719d027b7a42817c5d96461a99b6a49979c143839fc37aa5748c322f234" }, - { file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:32aa53a9a63b7f01ed32e316e354e81e9da0e6267435c7243bf8ae0f10b428ef" }, - { file = "watchfiles-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ce72dba6a20e39a0c628258b5c308779b8697f7676c254a845715e2a1039b968" }, - { file = "watchfiles-0.24.0-cp312-none-win32.whl", hash = "sha256:d9018153cf57fc302a2a34cb7564870b859ed9a732d16b41a9b5cb2ebed2d444" }, - { file = "watchfiles-0.24.0-cp312-none-win_amd64.whl", hash = "sha256:551ec3ee2a3ac9cbcf48a4ec76e42c2ef938a7e905a35b42a1267fa4b1645896" }, - { file = "watchfiles-0.24.0-cp312-none-win_arm64.whl", hash = "sha256:b52a65e4ea43c6d149c5f8ddb0bef8d4a1e779b77591a458a893eb416624a418" }, - { file = "watchfiles-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2e3ab79a1771c530233cadfd277fcc762656d50836c77abb2e5e72b88e3a48" }, - { file = "watchfiles-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327763da824817b38ad125dcd97595f942d720d32d879f6c4ddf843e3da3fe90" }, - { file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd82010f8ab451dabe36054a1622870166a67cf3fce894f68895db6f74bbdc94" }, - { file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d64ba08db72e5dfd5c33be1e1e687d5e4fcce09219e8aee893a4862034081d4e" }, - { file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1cf1f6dd7825053f3d98f6d33f6464ebdd9ee95acd74ba2c34e183086900a827" }, - { file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43e3e37c15a8b6fe00c1bce2473cfa8eb3484bbeecf3aefbf259227e487a03df" }, - { file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88bcd4d0fe1d8ff43675360a72def210ebad3f3f72cabfeac08d825d2639b4ab" }, - { file = "watchfiles-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:999928c6434372fde16c8f27143d3e97201160b48a614071261701615a2a156f" }, - { file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:30bbd525c3262fd9f4b1865cb8d88e21161366561cd7c9e1194819e0a33ea86b" }, - { file = "watchfiles-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:edf71b01dec9f766fb285b73930f95f730bb0943500ba0566ae234b5c1618c18" }, - { file = "watchfiles-0.24.0-cp313-none-win32.whl", hash = "sha256:f4c96283fca3ee09fb044f02156d9570d156698bc3734252175a38f0e8975f07" }, - { file = "watchfiles-0.24.0-cp313-none-win_amd64.whl", hash = "sha256:a974231b4fdd1bb7f62064a0565a6b107d27d21d9acb50c484d2cdba515b9366" }, - { file = "watchfiles-0.24.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ee82c98bed9d97cd2f53bdb035e619309a098ea53ce525833e26b93f673bc318" }, - { file = "watchfiles-0.24.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fd92bbaa2ecdb7864b7600dcdb6f2f1db6e0346ed425fbd01085be04c63f0b05" }, - { file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f83df90191d67af5a831da3a33dd7628b02a95450e168785586ed51e6d28943c" }, - { file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fca9433a45f18b7c779d2bae7beeec4f740d28b788b117a48368d95a3233ed83" }, - { file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b995bfa6bf01a9e09b884077a6d37070464b529d8682d7691c2d3b540d357a0c" }, - { file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed9aba6e01ff6f2e8285e5aa4154e2970068fe0fc0998c4380d0e6278222269b" }, - { file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5171ef898299c657685306d8e1478a45e9303ddcd8ac5fed5bd52ad4ae0b69b" }, - { file = "watchfiles-0.24.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4933a508d2f78099162da473841c652ad0de892719043d3f07cc83b33dfd9d91" }, - { file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95cf3b95ea665ab03f5a54765fa41abf0529dbaf372c3b83d91ad2cfa695779b" }, - { file = "watchfiles-0.24.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01def80eb62bd5db99a798d5e1f5f940ca0a05986dcfae21d833af7a46f7ee22" }, - { file = "watchfiles-0.24.0-cp38-none-win32.whl", hash = "sha256:4d28cea3c976499475f5b7a2fec6b3a36208656963c1a856d328aeae056fc5c1" }, - { file = "watchfiles-0.24.0-cp38-none-win_amd64.whl", hash = "sha256:21ab23fdc1208086d99ad3f69c231ba265628014d4aed31d4e8746bd59e88cd1" }, - { file = "watchfiles-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b665caeeda58625c3946ad7308fbd88a086ee51ccb706307e5b1fa91556ac886" }, - { file = "watchfiles-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c51749f3e4e269231510da426ce4a44beb98db2dce9097225c338f815b05d4f" }, - { file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b2509f08761f29a0fdad35f7e1638b8ab1adfa2666d41b794090361fb8b855" }, - { file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a60e2bf9dc6afe7f743e7c9b149d1fdd6dbf35153c78fe3a14ae1a9aee3d98b" }, - { file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7d9b87c4c55e3ea8881dfcbf6d61ea6775fffed1fedffaa60bd047d3c08c430" }, - { file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:78470906a6be5199524641f538bd2c56bb809cd4bf29a566a75051610bc982c3" }, - { file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07cdef0c84c03375f4e24642ef8d8178e533596b229d32d2bbd69e5128ede02a" }, - { file = "watchfiles-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d337193bbf3e45171c8025e291530fb7548a93c45253897cd764a6a71c937ed9" }, - { file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec39698c45b11d9694a1b635a70946a5bad066b593af863460a8e600f0dff1ca" }, - { file = "watchfiles-0.24.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e28d91ef48eab0afb939fa446d8ebe77e2f7593f5f463fd2bb2b14132f95b6e" }, - { file = "watchfiles-0.24.0-cp39-none-win32.whl", hash = "sha256:7138eff8baa883aeaa074359daabb8b6c1e73ffe69d5accdc907d62e50b1c0da" }, - { file = "watchfiles-0.24.0-cp39-none-win_amd64.whl", hash = "sha256:b3ef2c69c655db63deb96b3c3e587084612f9b1fa983df5e0c3379d41307467f" }, - { file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:632676574429bee8c26be8af52af20e0c718cc7f5f67f3fb658c71928ccd4f7f" }, - { file = "watchfiles-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:a2a9891723a735d3e2540651184be6fd5b96880c08ffe1a98bae5017e65b544b" }, - { file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7fa2bc0efef3e209a8199fd111b8969fe9db9c711acc46636686331eda7dd4" }, - { file = "watchfiles-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01550ccf1d0aed6ea375ef259706af76ad009ef5b0203a3a4cce0f6024f9b68a" }, - { file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:96619302d4374de5e2345b2b622dc481257a99431277662c30f606f3e22f42be" }, - { file = "watchfiles-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:85d5f0c7771dcc7a26c7a27145059b6bb0ce06e4e751ed76cdf123d7039b60b5" }, - { file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951088d12d339690a92cef2ec5d3cfd957692834c72ffd570ea76a6790222777" }, - { file = "watchfiles-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49fb58bcaa343fedc6a9e91f90195b20ccb3135447dc9e4e2570c3a39565853e" }, - { file = "watchfiles-0.24.0.tar.gz", hash = "sha256:afb72325b74fa7a428c009c1b8be4b4d7c2afedafb2982827ef2156646df2fe1" }, + { file = "watchfiles-1.0.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:1da46bb1eefb5a37a8fb6fd52ad5d14822d67c498d99bda8754222396164ae42" }, + { file = "watchfiles-1.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2b961b86cd3973f5822826017cad7f5a75795168cb645c3a6b30c349094e02e3" }, + { file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34e87c7b3464d02af87f1059fedda5484e43b153ef519e4085fe1a03dd94801e" }, + { file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d9dd2b89a16cf7ab9c1170b5863e68de6bf83db51544875b25a5f05a7269e678" }, + { file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b4691234d31686dca133c920f94e478b548a8e7c750f28dbbc2e4333e0d3da9" }, + { file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90b0fe1fcea9bd6e3084b44875e179b4adcc4057a3b81402658d0eb58c98edf8" }, + { file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0b90651b4cf9e158d01faa0833b073e2e37719264bcee3eac49fc3c74e7d304b" }, + { file = "watchfiles-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2e9fe695ff151b42ab06501820f40d01310fbd58ba24da8923ace79cf6d702d" }, + { file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62691f1c0894b001c7cde1195c03b7801aaa794a837bd6eef24da87d1542838d" }, + { file = "watchfiles-1.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:275c1b0e942d335fccb6014d79267d1b9fa45b5ac0639c297f1e856f2f532552" }, + { file = "watchfiles-1.0.3-cp310-cp310-win32.whl", hash = "sha256:06ce08549e49ba69ccc36fc5659a3d0ff4e3a07d542b895b8a9013fcab46c2dc" }, + { file = "watchfiles-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:f280b02827adc9d87f764972fbeb701cf5611f80b619c20568e1982a277d6146" }, + { file = "watchfiles-1.0.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ffe709b1d0bc2e9921257569675674cafb3a5f8af689ab9f3f2b3f88775b960f" }, + { file = "watchfiles-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:418c5ce332f74939ff60691e5293e27c206c8164ce2b8ce0d9abf013003fb7fe" }, + { file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f492d2907263d6d0d52f897a68647195bc093dafed14508a8d6817973586b6b" }, + { file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48c9f3bc90c556a854f4cab6a79c16974099ccfa3e3e150673d82d47a4bc92c9" }, + { file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75d3bcfa90454dba8df12adc86b13b6d85fda97d90e708efc036c2760cc6ba44" }, + { file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5691340f259b8f76b45fb31b98e594d46c36d1dc8285efa7975f7f50230c9093" }, + { file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e263cc718545b7f897baeac1f00299ab6fabe3e18caaacacb0edf6d5f35513c" }, + { file = "watchfiles-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c6cf7709ed3e55704cc06f6e835bf43c03bc8e3cb8ff946bf69a2e0a78d9d77" }, + { file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:703aa5e50e465be901e0e0f9d5739add15e696d8c26c53bc6fc00eb65d7b9469" }, + { file = "watchfiles-1.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bfcae6aecd9e0cb425f5145afee871465b98b75862e038d42fe91fd753ddd780" }, + { file = "watchfiles-1.0.3-cp311-cp311-win32.whl", hash = "sha256:6a76494d2c5311584f22416c5a87c1e2cb954ff9b5f0988027bc4ef2a8a67181" }, + { file = "watchfiles-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:cf745cbfad6389c0e331786e5fe9ae3f06e9d9c2ce2432378e1267954793975c" }, + { file = "watchfiles-1.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:2dcc3f60c445f8ce14156854a072ceb36b83807ed803d37fdea2a50e898635d6" }, + { file = "watchfiles-1.0.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:93436ed550e429da007fbafb723e0769f25bae178fbb287a94cb4ccdf42d3af3" }, + { file = "watchfiles-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c18f3502ad0737813c7dad70e3e1cc966cc147fbaeef47a09463bbffe70b0a00" }, + { file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5bc3ca468bb58a2ef50441f953e1f77b9a61bd1b8c347c8223403dc9b4ac9a" }, + { file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d1ec043f02ca04bf21b1b32cab155ce90c651aaf5540db8eb8ad7f7e645cba8" }, + { file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f58d3bfafecf3d81c15d99fc0ecf4319e80ac712c77cf0ce2661c8cf8bf84066" }, + { file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1df924ba82ae9e77340101c28d56cbaff2c991bd6fe8444a545d24075abb0a87" }, + { file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:632a52dcaee44792d0965c17bdfe5dc0edad5b86d6a29e53d6ad4bf92dc0ff49" }, + { file = "watchfiles-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bf4b459d94a0387617a1b499f314aa04d8a64b7a0747d15d425b8c8b151da0" }, + { file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca94c85911601b097d53caeeec30201736ad69a93f30d15672b967558df02885" }, + { file = "watchfiles-1.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:65ab1fb635476f6170b07e8e21db0424de94877e4b76b7feabfe11f9a5fc12b5" }, + { file = "watchfiles-1.0.3-cp312-cp312-win32.whl", hash = "sha256:49bc1bc26abf4f32e132652f4b3bfeec77d8f8f62f57652703ef127e85a3e38d" }, + { file = "watchfiles-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:48681c86f2cb08348631fed788a116c89c787fdf1e6381c5febafd782f6c3b44" }, + { file = "watchfiles-1.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:9e080cf917b35b20c889225a13f290f2716748362f6071b859b60b8847a6aa43" }, + { file = "watchfiles-1.0.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e153a690b7255c5ced17895394b4f109d5dcc2a4f35cb809374da50f0e5c456a" }, + { file = "watchfiles-1.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac1be85fe43b4bf9a251978ce5c3bb30e1ada9784290441f5423a28633a958a7" }, + { file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ec98e31e1844eac860e70d9247db9d75440fc8f5f679c37d01914568d18721" }, + { file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0179252846be03fa97d4d5f8233d1c620ef004855f0717712ae1c558f1974a16" }, + { file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:995c374e86fa82126c03c5b4630c4e312327ecfe27761accb25b5e1d7ab50ec8" }, + { file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29b9cb35b7f290db1c31fb2fdf8fc6d3730cfa4bca4b49761083307f441cac5a" }, + { file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f8dc09ae69af50bead60783180f656ad96bd33ffbf6e7a6fce900f6d53b08f1" }, + { file = "watchfiles-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:489b80812f52a8d8c7b0d10f0d956db0efed25df2821c7a934f6143f76938bd6" }, + { file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:228e2247de583475d4cebf6b9af5dc9918abb99d1ef5ee737155bb39fb33f3c0" }, + { file = "watchfiles-1.0.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1550be1a5cb3be08a3fb84636eaafa9b7119b70c71b0bed48726fd1d5aa9b868" }, + { file = "watchfiles-1.0.3-cp313-cp313-win32.whl", hash = "sha256:16db2d7e12f94818cbf16d4c8938e4d8aaecee23826344addfaaa671a1527b07" }, + { file = "watchfiles-1.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:160eff7d1267d7b025e983ca8460e8cc67b328284967cbe29c05f3c3163711a3" }, + { file = "watchfiles-1.0.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c05b021f7b5aa333124f2a64d56e4cb9963b6efdf44e8d819152237bbd93ba15" }, + { file = "watchfiles-1.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:310505ad305e30cb6c5f55945858cdbe0eb297fc57378f29bacceb534ac34199" }, + { file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddff3f8b9fa24a60527c137c852d0d9a7da2a02cf2151650029fdc97c852c974" }, + { file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46e86ed457c3486080a72bc837300dd200e18d08183f12b6ca63475ab64ed651" }, + { file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f79fe7993e230a12172ce7d7c7db061f046f672f2b946431c81aff8f60b2758b" }, + { file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea2b51c5f38bad812da2ec0cd7eec09d25f521a8b6b6843cbccedd9a1d8a5c15" }, + { file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fe4e740ea94978b2b2ab308cbf9270a246bcbb44401f77cc8740348cbaeac3d" }, + { file = "watchfiles-1.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af037d3df7188ae21dc1c7624501f2f90d81be6550904e07869d8d0e6766655" }, + { file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52bb50a4c4ca2a689fdba84ba8ecc6a4e6210f03b6af93181bb61c4ec3abaf86" }, + { file = "watchfiles-1.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c14a07bdb475eb696f85c715dbd0f037918ccbb5248290448488a0b4ef201aad" }, + { file = "watchfiles-1.0.3-cp39-cp39-win32.whl", hash = "sha256:be37f9b1f8934cd9e7eccfcb5612af9fb728fecbe16248b082b709a9d1b348bf" }, + { file = "watchfiles-1.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:ef9ec8068cf23458dbf36a08e0c16f0a2df04b42a8827619646637be1769300a" }, + { file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:84fac88278f42d61c519a6c75fb5296fd56710b05bbdcc74bdf85db409a03780" }, + { file = "watchfiles-1.0.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c68be72b1666d93b266714f2d4092d78dc53bd11cf91ed5a3c16527587a52e29" }, + { file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889a37e2acf43c377b5124166bece139b4c731b61492ab22e64d371cce0e6e80" }, + { file = "watchfiles-1.0.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca05cacf2e5c4a97d02a2878a24020daca21dbb8823b023b978210a75c79098" }, + { file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:8af4b582d5fc1b8465d1d2483e5e7b880cc1a4e99f6ff65c23d64d070867ac58" }, + { file = "watchfiles-1.0.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:127de3883bdb29dbd3b21f63126bb8fa6e773b74eaef46521025a9ce390e1073" }, + { file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713f67132346bdcb4c12df185c30cf04bdf4bf6ea3acbc3ace0912cab6b7cb8c" }, + { file = "watchfiles-1.0.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abd85de513eb83f5ec153a802348e7a5baa4588b818043848247e3e8986094e8" }, + { file = "watchfiles-1.0.3.tar.gz", hash = "sha256:f3ff7da165c99a5412fe5dd2304dd2dbaaaa5da718aad942dcb3a178eaa70c56" }, ] [package.dependencies] @@ -3864,215 +4105,277 @@ anyio = ">=3.0.0" [[package]] name = "websockets" -version = "13.0.1" +version = "14.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - { file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f" }, - { file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c" }, - { file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f" }, - { file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543" }, - { file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d" }, - { file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f" }, - { file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8" }, - { file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b" }, - { file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448" }, - { file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3" }, - { file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0" }, - { file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7" }, - { file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4" }, - { file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2" }, - { file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0" }, - { file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e" }, - { file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462" }, - { file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501" }, - { file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418" }, - { file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df" }, - { file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f" }, - { file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075" }, - { file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a" }, - { file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956" }, - { file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af" }, - { file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf" }, - { file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c" }, - { file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4" }, - { file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab" }, - { file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d" }, - { file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237" }, - { file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185" }, - { file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99" }, - { file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa" }, - { file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231" }, - { file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9" }, - { file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75" }, - { file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553" }, - { file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920" }, - { file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329" }, - { file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7" }, - { file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2" }, - { file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb" }, - { file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b" }, - { file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e" }, - { file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2" }, - { file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b" }, - { file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae" }, - { file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f" }, - { file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603" }, - { file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36" }, - { file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a" }, - { file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb" }, - { file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4" }, - { file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9" }, - { file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc" }, - { file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63" }, - { file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37" }, - { file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9" }, - { file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97" }, - { file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f" }, - { file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4" }, - { file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0" }, - { file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d" }, - { file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58" }, - { file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097" }, - { file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89" }, - { file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad" }, - { file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e" }, - { file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc" }, - { file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333" }, - { file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32" }, - { file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491" }, - { file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f" }, - { file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060" }, - { file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491" }, - { file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026" }, - { file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096" }, - { file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376" }, - { file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83" }, - { file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c" }, - { file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870" }, - { file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5" }, - { file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980" }, - { file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817" }, - { file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e" }, + { file = "websockets-14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29" }, + { file = "websockets-14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179" }, + { file = "websockets-14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250" }, + { file = "websockets-14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0" }, + { file = "websockets-14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0" }, + { file = "websockets-14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199" }, + { file = "websockets-14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58" }, + { file = "websockets-14.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078" }, + { file = "websockets-14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434" }, + { file = "websockets-14.1-cp310-cp310-win32.whl", hash = "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10" }, + { file = "websockets-14.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e" }, + { file = "websockets-14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512" }, + { file = "websockets-14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac" }, + { file = "websockets-14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280" }, + { file = "websockets-14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1" }, + { file = "websockets-14.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3" }, + { file = "websockets-14.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6" }, + { file = "websockets-14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0" }, + { file = "websockets-14.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89" }, + { file = "websockets-14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23" }, + { file = "websockets-14.1-cp311-cp311-win32.whl", hash = "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e" }, + { file = "websockets-14.1-cp311-cp311-win_amd64.whl", hash = "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09" }, + { file = "websockets-14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed" }, + { file = "websockets-14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d" }, + { file = "websockets-14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707" }, + { file = "websockets-14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a" }, + { file = "websockets-14.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45" }, + { file = "websockets-14.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58" }, + { file = "websockets-14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058" }, + { file = "websockets-14.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4" }, + { file = "websockets-14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05" }, + { file = "websockets-14.1-cp312-cp312-win32.whl", hash = "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0" }, + { file = "websockets-14.1-cp312-cp312-win_amd64.whl", hash = "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f" }, + { file = "websockets-14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9" }, + { file = "websockets-14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b" }, + { file = "websockets-14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3" }, + { file = "websockets-14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59" }, + { file = "websockets-14.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2" }, + { file = "websockets-14.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da" }, + { file = "websockets-14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9" }, + { file = "websockets-14.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7" }, + { file = "websockets-14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a" }, + { file = "websockets-14.1-cp313-cp313-win32.whl", hash = "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6" }, + { file = "websockets-14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0" }, + { file = "websockets-14.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a" }, + { file = "websockets-14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6" }, + { file = "websockets-14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56" }, + { file = "websockets-14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c" }, + { file = "websockets-14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b" }, + { file = "websockets-14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78" }, + { file = "websockets-14.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735" }, + { file = "websockets-14.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a" }, + { file = "websockets-14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc" }, + { file = "websockets-14.1-cp39-cp39-win32.whl", hash = "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4" }, + { file = "websockets-14.1-cp39-cp39-win_amd64.whl", hash = "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979" }, + { file = "websockets-14.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8" }, + { file = "websockets-14.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e" }, + { file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098" }, + { file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb" }, + { file = "websockets-14.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7" }, + { file = "websockets-14.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d" }, + { file = "websockets-14.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370" }, + { file = "websockets-14.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a" }, + { file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7" }, + { file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0" }, + { file = "websockets-14.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1" }, + { file = "websockets-14.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5" }, + { file = "websockets-14.1-py3-none-any.whl", hash = "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e" }, + { file = "websockets-14.1.tar.gz", hash = "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8" }, ] [[package]] name = "win32-setctime" -version = "1.1.0" +version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" files = [ - {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, - {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, + { file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390" }, + { file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0" }, ] [package.extras] dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] -name = "yarl" +name = "wordcloud" version = "1.9.4" -description = "Yet another URL library" +description = "A little word cloud generator" optional = false python-versions = ">=3.7" files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, + { file = "wordcloud-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:61a84e7311fce8415943edcb7b2ba65b4bfec1dc6dff8fe5a8ea76e278447fb2" }, + { file = "wordcloud-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e8752750726f31385f364823d3ef1d9c8ec829e5c07706c36beb40679945c71" }, + { file = "wordcloud-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:990dfd6dd43a1c7fa156be865eb98aba167a986b65f56cbf50e24772107fcd70" }, + { file = "wordcloud-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a70fe8999cd63aec64daa0377b720be6e5ff344963b828caeb4c2a081599a3a0" }, + { file = "wordcloud-1.9.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:37dcd5500cc2ea02950739390e89e2efa6624c2f54b5e2df1ee961fce685b2d7" }, + { file = "wordcloud-1.9.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5cc5c902dc2492b9fc0e29a1f5c688422d7e6eb9e5c0e43f0331d1c8e1341ba" }, + { file = "wordcloud-1.9.4-cp310-cp310-win32.whl", hash = "sha256:c20fbb51af2046c940b4fead4bafffc30b4191f5fb477c3af844446d8956bfd4" }, + { file = "wordcloud-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:61a153e76d73c72f5cc6c89ee80ddad70758a207c3c6b1d86be8635ec70164f1" }, + { file = "wordcloud-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af168eeaed67a675f35b5668a7804c4d64f8e4f62a273b909eb5cc39efc4c294" }, + { file = "wordcloud-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3092bf85cb20158c8b90d78650dc0226985109ac6fe13a0086ac47b9581b62ce" }, + { file = "wordcloud-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfb852f551681f5e33feb934505e060952b6aa98aaa48c781cdbf101f84e7cc" }, + { file = "wordcloud-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57ad8064a634a4870fcd00a9694c0a7839c6dfbac3d32522c69d5e1e9cbfd911" }, + { file = "wordcloud-1.9.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea14858973ad8561a20a5475eb8d7ad33622bc5f27c60206fbb3e10a036cee26" }, + { file = "wordcloud-1.9.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b27759f12dd235468ff8c1df875b106b23dbf2c74aae05cdcdc3ccd8e23ea89c" }, + { file = "wordcloud-1.9.4-cp311-cp311-win32.whl", hash = "sha256:0ac3d87627022fb8cce17297298be96c91185edd55ecf8906f89f981b55974f0" }, + { file = "wordcloud-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:85368249df056527f1b64e80e68636abb61f0f6bd2d1c430894d2af1feea7f73" }, + { file = "wordcloud-1.9.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3910494ce5acb27731fd5678d146e8aa8f588d5fdb455810c817ff4b84ee0f67" }, + { file = "wordcloud-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1c29a0089ee90778700cc96305fa830a6a5bbb342eaaa59d6ac8d37a9b232f" }, + { file = "wordcloud-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f369ae7bef16341c2bb208e658d5e4c56517046eb6176f89ac95525eaf8ace09" }, + { file = "wordcloud-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ec6ffba61ca20123e7c09103a5692bbc3163f75ee0bdc7893e80e0e2786ccd2" }, + { file = "wordcloud-1.9.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cdc4aac2bcce77fd91dbfe91db5a8c0cdc239e10d8954356d2ebf79a3b43646c" }, + { file = "wordcloud-1.9.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e4942fbed48a88a0c42c5b0a057651fc09d26b31be8b6c069adaaa5051836040" }, + { file = "wordcloud-1.9.4-cp312-cp312-win32.whl", hash = "sha256:96b801fe4b2aa39bb6c5e68b4a74c81fd8996dd5fb5cea31fda518dc5f77ad82" }, + { file = "wordcloud-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:360977705d0808a1795fcbe98afb5dc4833cb4bb8e421cbb10e93ef0bce816ff" }, + { file = "wordcloud-1.9.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:88c4c99f43b13df0e812fac0e4680cca2afd3ce16ade506812127ed7c7b9d132" }, + { file = "wordcloud-1.9.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2367ec70b2f195c278f91caf4674871ee9218eb57250e01a02b986d34e55f88e" }, + { file = "wordcloud-1.9.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6104a52936886dbc785844ab6986b5321a312238abb242ee4062c7b3fdcca7c" }, + { file = "wordcloud-1.9.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81bbe75b2725730bf5cbabfe86a5c38960e7ce1166f76ba7001964d8de50b3a7" }, + { file = "wordcloud-1.9.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a936b8e03c32cc84c99ad8f1bdaf261dfef6c44d31ca5b0c7d0df147220dbb3c" }, + { file = "wordcloud-1.9.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:046300566df97b48640bd3efd94957a56941ada98cc23f811bc3f9b6a0ac1350" }, + { file = "wordcloud-1.9.4-cp313-cp313-win32.whl", hash = "sha256:22357990a01d87579dbd38a06c2a5c7b601179c4e17517b1b8f73d25faa6a5ed" }, + { file = "wordcloud-1.9.4-cp313-cp313-win_amd64.whl", hash = "sha256:8c9a5af2fbcf029a19e827adbee58e86efe7536dca7a42380a8601113a86069b" }, + { file = "wordcloud-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:42affa75c1b033cb0a0afb674f653c4af16d51d97a0852c5770b659b903d9af5" }, + { file = "wordcloud-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0876722c35cf4d5d7717ab81ba98b946e07b0e869252248fdd9ea1fd6c977cc" }, + { file = "wordcloud-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:489079ef173fe83ccff8baffd7a3c2d5fedfd31221c25ad21b4de770ea37b49f" }, + { file = "wordcloud-1.9.4-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:3f3dc2dacca48eac9b130a8938b473db81cfbeeb1a738530a7098913941a8211" }, + { file = "wordcloud-1.9.4-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:2e509c4588ae2ce47ee5cc5cf353422e7f7ecc38f450998654ed50565c8a550d" }, + { file = "wordcloud-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:8009f53ba0c3b2d6f2b1dad83e0fb165ebcdfbd000ce62ebe0917106f51d975d" }, + { file = "wordcloud-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:30b1a59b9073eaaa4f2b0f27d5b6b6c3eb6aaa3a6e0b3dbb2220036b25b37dac" }, + { file = "wordcloud-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a685babefe032716c1a00b7d8cec3f6bfdc1c89fd839578432fc53824a02fea" }, + { file = "wordcloud-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b78b9fb292a243cf8fcdf63b9cc1fd157ec6abbf1a6e675303668b85e948f616" }, + { file = "wordcloud-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f51ab42c00bc4782ab45701de45226a269ca0850df14e1bd63a60da73271724e" }, + { file = "wordcloud-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38ee69d9404504cf2419d60c3017af7ab9e88f4ba6cf47bc1c96b2d5e58ef513" }, + { file = "wordcloud-1.9.4-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9955223708f196c1e431ae3b86074409bc256c5868e4f50eb9c36c6f06f8b1a3" }, + { file = "wordcloud-1.9.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3585ab8f4f09f1508f2d351ed48f9b56472ae26eaf6e2d2e76e975abd715d7a2" }, + { file = "wordcloud-1.9.4-cp38-cp38-win32.whl", hash = "sha256:d7d0b89c2ada0e65d84a6ebbdd8d36876b5da1a143cce2f7dcdaff6714232d24" }, + { file = "wordcloud-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:bd7caefe91d4084c1608d816052eeb605d9a7aee0c908f3a9d7421ee6363bde0" }, + { file = "wordcloud-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e5b2f7195adef0a071dc24a568d8a7715bc5cf5d752b4560f51da3aa4467dcf8" }, + { file = "wordcloud-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34843fa49135c4ed3739dea050696e707fd00e7335ee4ed62c33639589f90adf" }, + { file = "wordcloud-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6570cc4e48e8e951d24ef6599cd8bf7ff405fbe995ff6d596bcdfa290a6206a8" }, + { file = "wordcloud-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17f944805a17b8343eb877c9aa1dc9e5339eb14c02dd00ec80feccea899bbf81" }, + { file = "wordcloud-1.9.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7c1cd2a6ef876f5f9fe0255e44f131a6113f883447ed1cf8bdb86f569603bac9" }, + { file = "wordcloud-1.9.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2b129584327ba21d05869fcf9495f10f7b31a34a580c431c4942a71ce2317e79" }, + { file = "wordcloud-1.9.4-cp39-cp39-win32.whl", hash = "sha256:526dfd822600f158210a191a59cc4bdcaaa1ff05ab2aa199040d857a518b1db6" }, + { file = "wordcloud-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:ac32b851a19b7d2a9ee5e0aebc8210bf16eadc42c5c0da82e36d447552c8ec48" }, + { file = "wordcloud-1.9.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f733cca468eae79af83cdda1de2434f1799cefef461ed892e7679d5a4c929fa1" }, + { file = "wordcloud-1.9.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a99f96efe5983c6eed17abb8766ced713ddf18b26450da74addc91570922e62" }, + { file = "wordcloud-1.9.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80773ec6a9caa2048602bc347151e3b6e68e1d8fab148dfd0d2e7d4302ce5c01" }, + { file = "wordcloud-1.9.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ca95392bba150190cca8df4a97854b554bdeb28007f28bf4698bd7e1af91b310" }, + { file = "wordcloud-1.9.4-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:eed94b42676f4cfa9b9bdac777e3a1f046b16250216dd8ddcb583c4b6e4b1286" }, + { file = "wordcloud-1.9.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b38aae2ff7aa10ad00d57a5b87ed4a573ef04dbc9119d4a304349c9cb3e03b6e" }, + { file = "wordcloud-1.9.4-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3057be0d071afd57afb9be84fec767abdd78eac6396ead0f0f55c6775170945" }, + { file = "wordcloud-1.9.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9c39351d2cffc15e3794f7afab78e9135d700f61c5b51904c55d9f3729d1a0df" }, + { file = "wordcloud-1.9.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:914745f0312d248c1a0e1f16ae7b3ce82f78924a2b050ca912d2453c62586da4" }, + { file = "wordcloud-1.9.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:885d51d20cc7b0dad2306fb76b867de20e759e005a1a6e183f3865b5e5f53985" }, + { file = "wordcloud-1.9.4-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61fc126ed9ce8d55bf20acbdc00284f5a6da66900197a2dd7b62c5ac37585ac5" }, + { file = "wordcloud-1.9.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c7b8536955f5026b0587ff829265392185b6b4bc923f2ed933c805fcac412b28" }, + { file = "wordcloud-1.9.4-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6a30ed8aa50b98edb113f72ef619581c221ba3678adeeed88345263c90092561" }, + { file = "wordcloud-1.9.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a62627e5b081b23a4586104d4b01d064db7b53342ae123b511326585eaf7433c" }, + { file = "wordcloud-1.9.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e137493365770f59655c7308ff76addc95ada2c6bd50ac119e4c33091e2e4e08" }, + { file = "wordcloud-1.9.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:665f8e7de3dcc1e43aa5bdd9560d56ed51026ba638a33472eede2b9051108adb" }, + { file = "wordcloud-1.9.4.tar.gz", hash = "sha256:b273d8a5ded97d3ead904046b49464dcb71119ee79df875072a4c105cadd347a" }, +] + +[package.dependencies] +matplotlib = "*" +numpy = ">=1.6.1" +pillow = "*" + +[[package]] +name = "yarl" +version = "1.18.3" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +files = [ + { file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34" }, + { file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7" }, + { file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed" }, + { file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde" }, + { file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b" }, + { file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5" }, + { file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc" }, + { file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd" }, + { file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990" }, + { file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db" }, + { file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62" }, + { file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760" }, + { file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b" }, + { file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690" }, + { file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6" }, + { file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8" }, + { file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069" }, + { file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193" }, + { file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889" }, + { file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8" }, + { file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca" }, + { file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8" }, + { file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae" }, + { file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3" }, + { file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb" }, + { file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e" }, + { file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59" }, + { file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d" }, + { file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e" }, + { file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a" }, + { file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1" }, + { file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5" }, + { file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50" }, + { file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576" }, + { file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640" }, + { file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2" }, + { file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75" }, + { file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512" }, + { file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba" }, + { file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb" }, + { file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272" }, + { file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6" }, + { file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e" }, + { file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb" }, + { file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393" }, + { file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285" }, + { file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2" }, + { file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477" }, + { file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb" }, + { file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa" }, + { file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782" }, + { file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0" }, + { file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482" }, + { file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186" }, + { file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58" }, + { file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53" }, + { file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2" }, + { file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8" }, + { file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1" }, + { file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a" }, + { file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10" }, + { file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8" }, + { file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d" }, + { file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c" }, + { file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04" }, + { file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719" }, + { file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e" }, + { file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee" }, + { file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789" }, + { file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8" }, + { file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c" }, + { file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5" }, + { file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1" }, + { file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24" }, + { file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318" }, + { file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985" }, + { file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910" }, + { file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1" }, + { file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5" }, + { file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9" }, + { file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b" }, + { file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1" }, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" +propcache = ">=0.2.0" [[package]] name = "zhconv" @@ -4092,4 +4395,4 @@ sqlite = ["aiosqlite"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "f821477a2b1101a811fa723be8737fd239575dd5cb737d9c407d75fe3e9d7074" +content-hash = "40c5c766f0e3d6a9f80e671f953018266d04ac71dcd6149a7ac6d6a8ee1ae8c0" diff --git a/pyproject.toml b/pyproject.toml index ad077daa..fcf3a4bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,39 +7,42 @@ license = "MIT" readme = "README.md" package-mode = false + [tool.poetry.dependencies] python = "^3.12" -nonebot2 = { version = "2.3.3", extras = ["fastapi", "aiohttp", "httpx", "websockets"] } -pydantic = ">=2.7.0,<3.0.0" +nonebot2 = { version = "2.4.1", extras = ["fastapi", "aiohttp", "httpx", "websockets"] } +pydantic = ">=2.10.1,<3.0.0" nonebot-adapter-console = ">=0.6.0,<1.0.0" -nonebot-adapter-onebot = ">=2.4.4,<2.5.0" -nonebot-adapter-qq = ">=1.5.1,<1.6.0" -nonebot-adapter-telegram = ">=0.1.0b17,<0.2.0" +nonebot-adapter-onebot = ">=2.4.6,<2.5.0" +nonebot-adapter-qq = ">=1.6.0,<1.7.0" +nonebot-adapter-telegram = ">=0.1.0b20,<0.2.0" nonebot-plugin-apscheduler = ">=0.5.0,<1.0.0" -sqlalchemy = ">=2.0.30,<3.0.0" +sqlalchemy = ">=2.0.36,<3.0.0" asyncmy = {version = ">=0.2.9,<1.0.0", optional = true} aiomysql = {version = ">=0.2.0,<1.0.0", optional = true} -asyncpg = {version = ">=0.29.0,<1.0.0", optional = true} +asyncpg = { version = ">=0.30.0,<1.0.0", optional = true } aiosqlite = {version = ">=0.20.0,<1.0.0", optional = true} -apscheduler = ">=3.10.4,<4.0.0" +apscheduler = ">=3.11.0,<4.0.0" aiofiles = ">=24.0.0,<25.0.0" ujson = ">=5.10.0,<6.0.0" lxml = ">=5.3.0,<6.0.0" msgpack = ">=1.0.8,<2.0.0" -numpy = ">=1.26.4,<2.0.0" -matplotlib = ">=3.9.0,<4.0.0" -pillow = ">=10.4.0,<11.0.0" -imageio = ">=2.35.0,<3.0.0" -psutil = ">=5.9.8,<6.0.0" -pycryptodome = ">=3.20.0,<4.0.0" +numpy = ">=2.1.3,<3.0.0" +matplotlib = ">=3.10.0,<4.0.0" +pillow = ">=11.0.0,<12.0.0" +imageio = ">=2.36.0,<3.0.0" +psutil = ">=6.1.0,<7.0.0" +pycryptodome = ">=3.21.0,<4.0.0" py7zr = ">=0.21.0,<1.0.0" -pytz = "^2024.1" +pytz = "^2024.2" zhconv = ">=1.4.3,<2.0.0" -rapidfuzz = ">=3.9.3,<4.0.0" -emoji = ">=2.12.1,<3.0.0" +rapidfuzz = ">=3.11.0,<4.0.0" +emoji = ">=2.14.0,<3.0.0" openpyxl = ">=3.1.2,<4.0.0" -qrcode = {extras = ["pil"], version = "^7.4.2"} +qrcode = { extras = ["pil"], version = ">=8.0.0,<9.0.0" } onedice = "1.0.7" +jieba = ">=0.42.1,<1.0.0" +wordcloud = ">=1.9.4,<2.0.0" [tool.poetry.extras] @@ -49,12 +52,84 @@ sqlite = ["aiosqlite"] [tool.poetry.group.dev.dependencies] -mypy = ">=1.11.0,<2.0.0" +mypy = ">=1.14.0,<2.0.0" +ruff = ">=0.8.4,<1.0.0" bump-pydantic = "^0.8.0" types-lxml = "^2024.3.27" lxml-stubs = "^0.5.1" -alembic = "^1.13.2" +alembic = "^1.14.0" +memory-profiler = ">=0.61.0,<1.0.0" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".github", + ".hg", + ".idea", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] +line-length = 120 +indent-width = 4 +target-version = "py312" # Assume Python 3.12 + + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = [ + "F", # Pyflakes + "E", # pycodestyle Error (E) + "W", # pycodestyle Warning (W) + "I001", # unsorted-imports + "UP", # pyupgrade (UP) + "C4", # flake8-comprehensions (C4) + "T10", # flake8-debugger (T10) + "T20", # flake8-print (T20) + "PYI", # flake8-pyi (PYI) + "PT", # flake8-pytest-style (PT) + "Q", # flake8-quotes (Q) + "NPY", # NumPy-specific rules (NPY) + "FAST", # FastAPI (FAST) +] +ignore = [ + "E402", # module-import-not-at-top-of-file + "C901", # complex-structure + "PT023", # pytest-incorrect-mark-parentheses-style +] +flake8-quotes.inline-quotes = "single" + + +[tool.ruff.format] +quote-style = "single" # Use single quotes for strings. +indent-style = "space" # Indent with spaces, rather than tabs. diff --git a/src/__init__.py b/src/__init__.py index 3e60c4a5..2d71154d 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -2,8 +2,8 @@ @Author : Ailitonia @Date : 2022/12/01 19:53 @FileName : src -@Project : nonebot2_miya +@Project : nonebot2_miya @Description : omega source @GitHub : https://github.com/Ailitonia -@Software : PyCharm +@Software : PyCharm """ diff --git a/src/compat.py b/src/compat.py index 80957861..5c8a1e70 100644 --- a/src/compat.py +++ b/src/compat.py @@ -5,12 +5,12 @@ @Project : nonebot2_miya @Description : 一些兼容和 patch 模块 @GitHub : https://github.com/Ailitonia -@Software : PyCharm +@Software : PyCharm """ from typing import Annotated, Any -from pydantic import AnyUrl, AnyHttpUrl, BeforeValidator, TypeAdapter +from pydantic import AnyHttpUrl, AnyUrl, BeforeValidator, TypeAdapter # Compatibility for pydantic_core._pydantic_core.Url in V2 # See https://github.com/pydantic/pydantic/discussions/8211 and https://github.com/pydantic/pydantic/discussions/6395 diff --git a/src/database/__init__.py b/src/database/__init__.py index 6ab2f297..4f904c35 100644 --- a/src/database/__init__.py +++ b/src/database/__init__.py @@ -14,22 +14,19 @@ __all__ = [ 'ArtworkCollectionDAL', 'AuthSettingDAL', - 'BiliDynamicDAL', 'BotSelfDAL', 'CoolDownDAL', - 'EmailBoxDAL', - 'EmailBoxBindDAL', 'EntityDAL', 'FriendshipDAL', + 'GlobalCacheDAL', 'HistoryDAL', - 'PixivisionArticleDAL', 'PluginDAL', 'SignInDAL', + 'SocialMediaContentDAL', 'StatisticDAL', 'SubscriptionDAL', 'SubscriptionSourceDAL', 'SystemSettingDAL', - 'WeiboDetailDAL', 'WordBankDAL', 'begin_db_session', 'get_db_session', diff --git a/src/database/config.py b/src/database/config.py index 2b2c9ae2..a1a0e0d3 100644 --- a/src/database/config.py +++ b/src/database/config.py @@ -12,7 +12,7 @@ import pathlib import sys from enum import StrEnum, unique -from typing import Any, Literal, Optional +from typing import Any, Literal from urllib.parse import quote from nonebot import get_plugin_config, logger @@ -55,7 +55,7 @@ def connector(self) -> DatabaseConnector: raise NotImplementedError @property - def table_args(self) -> Optional[dict[str, Any]]: + def table_args(self) -> dict[str, Any] | None: return None @@ -75,9 +75,9 @@ def connector(self) -> DatabaseConnector: url=f'{self.database}+{self.db_driver.value}://{self.db_user}' f':{quote(str(self.db_password))}@{self.db_host}:{self.db_port}/{self.db_name}', connect_args={ - "pool_size": 10, - "max_overflow": 20, - 'connect_args': {"use_unicode": True, "charset": "utf8mb4"} + 'pool_size': 10, + 'max_overflow': 20, + 'connect_args': {'use_unicode': True, 'charset': 'utf8mb4'} } ) @@ -102,9 +102,9 @@ def connector(self) -> DatabaseConnector: url=f'{self.database}+{self.db_driver.value}://{self.db_user}' f':{quote(str(self.db_password))}@{self.db_host}:{self.db_port}/{self.db_name}', connect_args={ - "pool_size": 10, - "max_overflow": 20, - 'connect_args': {"server_settings": {"jit": "off"}} + 'pool_size': 10, + 'max_overflow': 20, + 'connect_args': {'server_settings': {'jit': 'off'}} } ) diff --git a/src/database/helpers.py b/src/database/helpers.py index 72a8dcbc..905bb6be 100644 --- a/src/database/helpers.py +++ b/src/database/helpers.py @@ -9,8 +9,9 @@ """ from asyncio import current_task +from collections.abc import AsyncGenerator from contextlib import asynccontextmanager -from typing import Any, AsyncGenerator +from typing import Any from nonebot import get_driver, logger from nonebot.matcher import current_event, current_matcher diff --git a/src/database/internal/__init__.py b/src/database/internal/__init__.py index d2fdf9ec..5300f8d4 100644 --- a/src/database/internal/__init__.py +++ b/src/database/internal/__init__.py @@ -10,43 +10,36 @@ from .artwork_collection import ArtworkCollectionDAL from .auth_setting import AuthSettingDAL -from .bili_dynamic import BiliDynamicDAL from .bot import BotSelfDAL from .cooldown import CoolDownDAL -from .email_box import EmailBoxDAL -from .email_box_bind import EmailBoxBindDAL from .entity import EntityDAL from .friendship import FriendshipDAL +from .global_cache import GlobalCacheDAL from .history import HistoryDAL -from .pixivision_article import PixivisionArticleDAL from .plugin import PluginDAL from .sign_in import SignInDAL +from .social_media_content import SocialMediaContentDAL from .statistic import StatisticDAL from .subscription import SubscriptionDAL from .subscription_source import SubscriptionSourceDAL from .system_setting import SystemSettingDAL -from .weibo_detail import WeiboDetailDAL from .word_bank import WordBankDAL - __all__ = [ 'ArtworkCollectionDAL', 'AuthSettingDAL', - 'BiliDynamicDAL', 'BotSelfDAL', 'CoolDownDAL', - 'EmailBoxDAL', - 'EmailBoxBindDAL', 'EntityDAL', 'FriendshipDAL', + 'GlobalCacheDAL', 'HistoryDAL', - 'PixivisionArticleDAL', 'PluginDAL', 'SignInDAL', + 'SocialMediaContentDAL', 'StatisticDAL', 'SubscriptionDAL', 'SubscriptionSourceDAL', 'SystemSettingDAL', - 'WeiboDetailDAL', 'WordBankDAL', ] diff --git a/src/database/internal/artwork_collection.py b/src/database/internal/artwork_collection.py index bc467ca9..9978a351 100644 --- a/src/database/internal/artwork_collection.py +++ b/src/database/internal/artwork_collection.py @@ -8,22 +8,19 @@ @Software : PyCharm """ +from collections.abc import Sequence from datetime import datetime -from typing import Literal, Optional, Sequence +from typing import Literal -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete, desc, or_, and_ -from sqlalchemy.future import select -from sqlalchemy.sql.expression import func +from sqlalchemy import and_, delete, desc, func, or_, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import ArtworkCollectionOrm -class ArtworkCollection(BaseModel): +class ArtworkCollection(BaseDataQueryResultModel): """图库作品 Model""" - id: int origin: str aid: str title: str @@ -34,31 +31,27 @@ class ArtworkCollection(BaseModel): width: int height: int tags: str - description: Optional[str] = None + description: str | None = None source: str cover_page: str - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class ArtworkClassificationStatistic(BaseModel): +class ArtworkClassificationStatistic(BaseDataQueryResultModel): """分类统计信息查询结果""" - unknown: int = 0 + unused: int = 0 unclassified: int = 0 ai_generated: int = 0 automatic: int = 0 confirmed: int = 0 - model_config = ConfigDict(extra='ignore', frozen=True) - @property def total(self) -> int: - return self.unknown + self.unclassified + self.ai_generated + self.automatic + self.confirmed + return self.unused + self.unclassified + self.ai_generated + self.automatic + self.confirmed -class ArtworkRatingStatistic(BaseModel): +class ArtworkRatingStatistic(BaseDataQueryResultModel): """分级统计信息查询结果""" unknown: int = 0 general: int = 0 @@ -66,27 +59,25 @@ class ArtworkRatingStatistic(BaseModel): questionable: int = 0 explicit: int = 0 - model_config = ConfigDict(extra='ignore', frozen=True) - @property def total(self) -> int: return self.unknown + self.general + self.sensitive + self.questionable + self.explicit -class ArtworkCollectionDAL(BaseDataAccessLayerModel): +class ArtworkCollectionDAL(BaseDataAccessLayerModel[ArtworkCollectionOrm, ArtworkCollection]): """图库作品 数据库操作对象""" async def query_unique(self, origin: str, aid: str) -> ArtworkCollection: - stmt = (select(ArtworkCollectionOrm). - where(ArtworkCollectionOrm.origin == origin). - where(ArtworkCollectionOrm.aid == aid)) + stmt = (select(ArtworkCollectionOrm) + .where(ArtworkCollectionOrm.origin == origin) + .where(ArtworkCollectionOrm.aid == aid)) session_result = await self.db_session.execute(stmt) return ArtworkCollection.model_validate(session_result.scalar_one()) async def query_by_condition( self, - origin: Optional[str | Sequence[str]], - keywords: Optional[Sequence[str]], + origin: str | Sequence[str] | None, + keywords: Sequence[str] | None, num: int = 3, *, classification_min: int = 2, @@ -94,8 +85,8 @@ async def query_by_condition( rating_min: int = 0, rating_max: int = 0, acc_mode: bool = False, - ratio: Optional[int] = None, - order_mode: Literal['random', 'aid', 'aid_desc', 'create_time', 'create_time_desc'] = 'random' + ratio: int | None = None, + order_mode: Literal['random', 'latest', 'aid', 'aid_desc'] = 'random', ) -> list[ArtworkCollection]: """按条件搜索图库收录作品 @@ -168,16 +159,14 @@ async def query_by_condition( # 根据 order_mode 构造排序语句 match order_mode: - case 'random': - stmt = stmt.order_by(func.random()) case 'aid': stmt = stmt.order_by(ArtworkCollectionOrm.aid) case 'aid_desc': stmt = stmt.order_by(desc(ArtworkCollectionOrm.aid)) - case 'create_time': - stmt = stmt.order_by(ArtworkCollectionOrm.created_at) - case 'create_time_desc': + case 'latest': stmt = stmt.order_by(desc(ArtworkCollectionOrm.created_at)) + case 'random' | _: + stmt = stmt.order_by(func.random()) # 结果数量限制 if num is None: @@ -190,11 +179,11 @@ async def query_by_condition( async def query_classification_statistic( self, - origin: Optional[str] = None, - keywords: Optional[Sequence[str]] = None + origin: str | None = None, + keywords: Sequence[str] | None = None ) -> ArtworkClassificationStatistic: """按分类统计收录作品数""" - stmt = select(ArtworkCollectionOrm.classification, func.count(ArtworkCollectionOrm.id)) + stmt = select(ArtworkCollectionOrm.classification, func.count(ArtworkCollectionOrm.aid)) if origin is not None: stmt = stmt.where(ArtworkCollectionOrm.origin == origin) @@ -222,17 +211,17 @@ async def query_classification_statistic( case 3: result.update({'confirmed': v}) case _: - result.update({'unknown': v}) + result.update({'unused': v}) return ArtworkClassificationStatistic.model_validate(result) async def query_rating_statistic( self, - origin: Optional[str] = None, - keywords: Optional[Sequence[str]] = None + origin: str | None = None, + keywords: Sequence[str] | None = None ) -> ArtworkRatingStatistic: """按分级统计收录作品数""" - stmt = select(ArtworkCollectionOrm.rating, func.count(ArtworkCollectionOrm.id)) + stmt = select(ArtworkCollectionOrm.rating, func.count(ArtworkCollectionOrm.aid)) if origin is not None: stmt = stmt.where(ArtworkCollectionOrm.origin == origin) @@ -266,9 +255,9 @@ async def query_rating_statistic( async def query_user_all( self, - origin: Optional[str] = None, - uid: Optional[str] = None, - uname: Optional[str] = None + origin: str | None = None, + uid: str | None = None, + uname: str | None = None ) -> list[ArtworkCollection]: """通过 uid 或用户名精准查找用户所有作品""" if uid is None and uname is None: @@ -288,9 +277,9 @@ async def query_user_all( async def query_user_all_aids( self, - origin: Optional[str] = None, - uid: Optional[str] = None, - uname: Optional[str] = None + origin: str | None = None, + uid: str | None = None, + uname: str | None = None ) -> list[str]: """通过 uid 或用户名精准查找用户所有作品的 artwork_id""" if uid is None and uname is None: @@ -308,23 +297,57 @@ async def query_user_all_aids( session_result = await self.db_session.execute(stmt) return parse_obj_as(list[str], session_result.scalars().all()) - async def query_exists_aids(self, origin: Optional[str], aids: Sequence[str]) -> list[str]: - """根据提供的 aids 列表查询数据库中已存在的列表中的 aid""" + async def query_exists_aids( + self, + origin: str | None, + aids: Sequence[str], + *, + filter_classification: int | None = None, + filter_rating: int | None = None, + ) -> list[str]: + """根据提供的 aids 列表查询数据库中已存在的列表中的 aid + + :param origin: 指定作品源 + :param aids: 待匹配的作品 artwork_id 清单 + :param filter_classification: 筛选指定的作品分类, 只有该分类的作品都会被视为存在 + :param filter_rating: 筛选指定的作品分级, 只有该分级的作品都会被视为存在 + :return: 数据库中已存在的, 匹配提供的作品清单的 artwork_id 列表 + """ stmt = select(ArtworkCollectionOrm.aid) if origin is not None: stmt = stmt.where(ArtworkCollectionOrm.origin == origin) + if filter_classification is not None: + stmt = stmt.where(ArtworkCollectionOrm.classification == filter_classification) + if filter_rating is not None: + stmt = stmt.where(ArtworkCollectionOrm.rating == filter_rating) stmt = stmt.where(ArtworkCollectionOrm.aid.in_(aids)).order_by(desc(ArtworkCollectionOrm.aid)) session_result = await self.db_session.execute(stmt) return parse_obj_as(list[str], session_result.scalars().all()) - async def query_not_exists_aids(self, origin: Optional[str], aids: Sequence[str]) -> list[str]: - """根据提供的 aids 列表查询数据库中不存在的列表中的 aid""" - exists_aids = await self.query_exists_aids(origin=origin, aids=aids) + async def query_not_exists_aids( + self, + origin: str | None, + aids: Sequence[str], + *, + exclude_classification: int | None = None, + exclude_rating: int | None = None, + ) -> list[str]: + """根据提供的 aids 列表查询数据库中不存在的列表中的 aid + + :param origin: 指定作品源 + :param aids: 待匹配的作品 artwork_id 清单 + :param exclude_classification: 排除指定的作品分类, 所有非该分类的作品都会被视为不存在 + :param exclude_rating: 排除指定的作品分级, 所有非该分级的作品都会被视为不存在 + :return: 数据库中不存在的, 匹配提供的作品清单的 artwork_id 列表 + """ + exists_aids = await self.query_exists_aids( + origin=origin, aids=aids, filter_classification=exclude_classification, filter_rating=exclude_rating + ) return sorted(list(set(aids) - set(exists_aids)), reverse=True) async def query_all(self) -> list[ArtworkCollection]: - raise NotImplementedError('method not supported') + raise NotImplementedError async def add( self, @@ -340,41 +363,60 @@ async def add( tags: str, source: str, cover_page: str, - description: Optional[str] = None, + description: str | None = None, ) -> None: - new_obj = ArtworkCollectionOrm( - origin=origin, aid=aid, title=title, uid=uid, uname=uname, - classification=classification, rating=rating, width=width, height=height, - tags=tags[:2048], source=source, cover_page=cover_page, - description=description if description is None else description[:2048], - created_at=datetime.now() - ) - self.db_session.add(new_obj) - await self.db_session.flush() + new_obj = ArtworkCollectionOrm(origin=origin, aid=aid, title=title, uid=uid, uname=uname, + classification=classification, rating=rating, + width=width, height=height, + tags=tags[:4096], source=source, cover_page=cover_page, + description=description if description is None else description[:4096], + created_at=datetime.now()) + await self._add(new_obj) + + async def upsert( + self, + origin: str, + aid: str, + title: str, + uid: str, + uname: str, + classification: int, + rating: int, + width: int, + height: int, + tags: str, + source: str, + cover_page: str, + description: str | None = None, + ) -> None: + new_obj = ArtworkCollectionOrm(origin=origin, aid=aid, title=title, uid=uid, uname=uname, + classification=classification, rating=rating, + width=width, height=height, + tags=tags[:4096], source=source, cover_page=cover_page, + description=description if description is None else description[:4096], + updated_at=datetime.now()) + await self._merge(new_obj) async def update( self, - id_: int, + origin: str, + aid: str, *, - origin: Optional[str] = None, - aid: Optional[str] = None, - title: Optional[str] = None, - uid: Optional[str] = None, - uname: Optional[str] = None, - classification: Optional[int] = None, - rating: Optional[int] = None, - width: Optional[int] = None, - height: Optional[int] = None, - tags: Optional[str] = None, - source: Optional[str] = None, - cover_page: Optional[str] = None, - description: Optional[str] = None, + title: str | None = None, + uid: str | None = None, + uname: str | None = None, + classification: int | None = None, + rating: int | None = None, + width: int | None = None, + height: int | None = None, + tags: str | None = None, + source: str | None = None, + cover_page: str | None = None, + description: str | None = None, ) -> None: - stmt = update(ArtworkCollectionOrm).where(ArtworkCollectionOrm.id == id_) - if origin is not None: - stmt = stmt.values(origin=origin) - if aid is not None: - stmt = stmt.values(aid=aid) + stmt = (update(ArtworkCollectionOrm) + .where(ArtworkCollectionOrm.origin == origin) + .where(ArtworkCollectionOrm.aid == aid)) if title is not None: stmt = stmt.values(title=title) if uid is not None: @@ -390,20 +432,22 @@ async def update( if height is not None: stmt = stmt.values(height=height) if tags is not None: - stmt = stmt.values(tags=tags[:2048]) + stmt = stmt.values(tags=tags[:4096]) if source is not None: stmt = stmt.values(source=source) if cover_page is not None: stmt = stmt.values(cover_page=cover_page) if description is not None: - stmt = stmt.values(description=description[:2048]) + stmt = stmt.values(description=description[:4096]) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) - async def delete(self, id_: int) -> None: - stmt = delete(ArtworkCollectionOrm).where(ArtworkCollectionOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + async def delete(self, origin: str, aid: str) -> None: + stmt = (delete(ArtworkCollectionOrm) + .where(ArtworkCollectionOrm.origin == origin) + .where(ArtworkCollectionOrm.aid == aid)) + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/auth_setting.py b/src/database/internal/auth_setting.py index 1a40b930..3cdab3dc 100644 --- a/src/database/internal/auth_setting.py +++ b/src/database/internal/auth_setting.py @@ -9,18 +9,15 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import AuthSettingOrm -class AuthSetting(BaseModel): +class AuthSetting(BaseDataQueryResultModel): """授权配置 Model""" id: int entity_index_id: int @@ -28,30 +25,28 @@ class AuthSetting(BaseModel): plugin: str node: str available: int - value: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + value: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class AuthSettingDAL(BaseDataAccessLayerModel): +class AuthSettingDAL(BaseDataAccessLayerModel[AuthSettingOrm, AuthSetting]): """授权配置 数据库操作对象""" async def query_unique(self, entity_index_id: int, module: str, plugin: str, node: str) -> AuthSetting: - stmt = select(AuthSettingOrm).\ - where(AuthSettingOrm.entity_index_id == entity_index_id).\ - where(AuthSettingOrm.module == module).\ - where(AuthSettingOrm.plugin == plugin).\ - where(AuthSettingOrm.node == node) + stmt = (select(AuthSettingOrm) + .where(AuthSettingOrm.entity_index_id == entity_index_id) + .where(AuthSettingOrm.module == module) + .where(AuthSettingOrm.plugin == plugin) + .where(AuthSettingOrm.node == node)) session_result = await self.db_session.execute(stmt) return AuthSetting.model_validate(session_result.scalar_one()) async def query_entity_all( self, entity_index_id: int, - module: Optional[str] = None, - plugin: Optional[str] = None + module: str | None = None, + plugin: str | None = None ) -> list[AuthSetting]: """查询 Entity 具有的全部/某个模块/插件的权限配置""" stmt = select(AuthSettingOrm).where(AuthSettingOrm.entity_index_id == entity_index_id) @@ -65,10 +60,12 @@ async def query_entity_all( async def query_module_plugin_all(self, module: str, plugin: str) -> list[AuthSetting]: """查询某个模块/插件所有已配置的权限配置""" - stmt = select(AuthSettingOrm).\ - where(AuthSettingOrm.module == module).\ - where(AuthSettingOrm.plugin == plugin).\ - order_by(AuthSettingOrm.module).order_by(AuthSettingOrm.plugin).order_by(AuthSettingOrm.node) + stmt = (select(AuthSettingOrm) + .where(AuthSettingOrm.module == module) + .where(AuthSettingOrm.plugin == plugin) + .order_by(AuthSettingOrm.module) + .order_by(AuthSettingOrm.plugin) + .order_by(AuthSettingOrm.node)) session_result = await self.db_session.execute(stmt) return parse_obj_as(list[AuthSetting], session_result.scalars().all()) @@ -84,23 +81,25 @@ async def add( plugin: str, node: str, available: int, - value: Optional[str] = None + value: str | None = None ) -> None: new_obj = AuthSettingOrm(entity_index_id=entity_index_id, module=module, plugin=plugin, node=node, available=available, value=value, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - entity_index_id: Optional[int] = None, - module: Optional[str] = None, - plugin: Optional[str] = None, - node: Optional[str] = None, - available: Optional[int] = None, - value: Optional[str] = None + entity_index_id: int | None = None, + module: str | None = None, + plugin: str | None = None, + node: str | None = None, + available: int | None = None, + value: str | None = None ) -> None: stmt = update(AuthSettingOrm).where(AuthSettingOrm.id == id_) if entity_index_id is not None: @@ -116,12 +115,12 @@ async def update( if value is not None: stmt = stmt.values(value=value) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(AuthSettingOrm).where(AuthSettingOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/bili_dynamic.py b/src/database/internal/bili_dynamic.py deleted file mode 100644 index 099b0218..00000000 --- a/src/database/internal/bili_dynamic.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2022/12/04 21:29 -@FileName : bili_dynamic.py -@Project : nonebot2_miya -@Description : BiliDynamic DAL -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from datetime import datetime -from typing import Optional, Sequence - -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete, desc -from sqlalchemy.future import select - -from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel -from ..schema import BiliDynamicOrm - - -class BiliDynamic(BaseModel): - """bilibili 主站动态 Model""" - id: int - dynamic_id: int - dynamic_type: int - uid: int - content: str - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - - -class BiliDynamicDAL(BaseDataAccessLayerModel): - """B站动态 数据库操作对象""" - - async def query_unique(self, dynamic_id: int) -> BiliDynamic: - stmt = select(BiliDynamicOrm).where(BiliDynamicOrm.dynamic_id == dynamic_id) - session_result = await self.db_session.execute(stmt) - return BiliDynamic.model_validate(session_result.scalar_one()) - - async def query_user_all(self, uid: int) -> list[BiliDynamic]: - """查询用户的全部动态""" - stmt = select(BiliDynamicOrm).where(BiliDynamicOrm.uid == uid).order_by(desc(BiliDynamicOrm.dynamic_id)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[BiliDynamic], session_result.scalars().all()) - - async def query_user_all_dynamic_ids(self, uid: int) -> list[int]: - """查询用户的全部动态id""" - stmt = select(BiliDynamicOrm.dynamic_id).\ - where(BiliDynamicOrm.uid == uid).\ - order_by(desc(BiliDynamicOrm.dynamic_id)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[int], session_result.scalars().all()) - - async def query_exists_ids(self, dynamic_ids: Sequence[int]) -> list[int]: - """查询数据库中 dynamic_id 列表中已有的动态 id""" - stmt = select(BiliDynamicOrm.dynamic_id).\ - where(BiliDynamicOrm.dynamic_id.in_(dynamic_ids)).\ - order_by(desc(BiliDynamicOrm.dynamic_id)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[int], session_result.scalars().all()) - - async def query_not_exists_ids(self, dynamic_ids: Sequence[int]) -> list[int]: - """查询数据库中 dynamic_id 列表中没有的动态 id""" - exists_ids = await self.query_exists_ids(dynamic_ids=dynamic_ids) - return sorted(list(set(dynamic_ids) - set(exists_ids)), reverse=True) - - async def query_all(self) -> list[BiliDynamic]: - stmt = select(BiliDynamicOrm).order_by(desc(BiliDynamicOrm.dynamic_id)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[BiliDynamic], session_result.scalars().all()) - - async def add(self, dynamic_id: int, dynamic_type: int, uid: int, content: str) -> None: - new_obj = BiliDynamicOrm(dynamic_id=dynamic_id, dynamic_type=dynamic_type, - uid=uid, content=content, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() - - async def update( - self, - id_: int, - *, - dynamic_id: Optional[int] = None, - dynamic_type: Optional[int] = None, - uid: Optional[int] = None, - content: Optional[str] = None - ) -> None: - stmt = update(BiliDynamicOrm).where(BiliDynamicOrm.id == id_) - if dynamic_id is not None: - stmt = stmt.values(dynamic_id=dynamic_id) - if dynamic_type is not None: - stmt = stmt.values(dynamic_type=dynamic_type) - if uid is not None: - stmt = stmt.values(uid=uid) - if content is not None: - stmt = stmt.values(content=content) - stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - async def delete(self, id_: int) -> None: - stmt = delete(BiliDynamicOrm).where(BiliDynamicOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - -__all__ = [ - 'BiliDynamic', - 'BiliDynamicDAL', -] diff --git a/src/database/internal/bot.py b/src/database/internal/bot.py index 194f7d75..e17561e8 100644 --- a/src/database/internal/bot.py +++ b/src/database/internal/bot.py @@ -11,14 +11,11 @@ from copy import deepcopy from datetime import datetime from enum import StrEnum, unique -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import BotSelfOrm @@ -36,23 +33,21 @@ def get_supported_adapter_names(cls) -> set[str]: return set(member.value for _, member in cls.__members__.items()) -class BotSelf(BaseModel): +class BotSelf(BaseDataQueryResultModel): """BotSelf Model""" id: int self_id: str bot_type: BotType bot_status: int - bot_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) + bot_info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None def __str__(self) -> str: return f'{self.bot_type.value} Bot(id={self.id}, self_id={self.self_id}, status={self.bot_status})' -class BotSelfDAL(BaseDataAccessLayerModel): +class BotSelfDAL(BaseDataAccessLayerModel[BotSelfOrm, BotSelf]): """BotSelf 数据库操作对象""" @property @@ -79,7 +74,7 @@ async def query_all_online(self) -> list[BotSelf]: session_result = await self.db_session.execute(stmt) return parse_obj_as(list[BotSelf], session_result.scalars().all()) - async def add(self, self_id: str, bot_type: str, bot_status: int, bot_info: Optional[str] = None) -> None: + async def add(self, self_id: str, bot_type: str, bot_status: int, bot_info: str | None = None) -> None: new_obj = BotSelfOrm( self_id=self_id, bot_type=BotType(bot_type), @@ -87,16 +82,18 @@ async def add(self, self_id: str, bot_type: str, bot_status: int, bot_info: Opti bot_info=bot_info, created_at=datetime.now() ) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - bot_type: Optional[str] = None, - bot_status: Optional[int] = None, - bot_info: Optional[str] = None + bot_type: str | None = None, + bot_status: int | None = None, + bot_info: str | None = None ) -> None: stmt = update(BotSelfOrm).where(BotSelfOrm.id == id_) if bot_type is not None: @@ -106,12 +103,12 @@ async def update( if bot_info is not None: stmt = stmt.values(bot_info=bot_info) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(BotSelfOrm).where(BotSelfOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/cooldown.py b/src/database/internal/cooldown.py index caeb0108..a91106b2 100644 --- a/src/database/internal/cooldown.py +++ b/src/database/internal/cooldown.py @@ -9,37 +9,32 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import CoolDownOrm -class CoolDown(BaseModel): +class CoolDown(BaseDataQueryResultModel): """冷却事件 Model""" id: int entity_index_id: int event: str stop_at: datetime - description: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + description: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class CoolDownDAL(BaseDataAccessLayerModel): +class CoolDownDAL(BaseDataAccessLayerModel[CoolDownOrm, CoolDown]): """冷却事件 数据库操作对象""" async def query_unique(self, entity_index_id: int, event: str) -> CoolDown: - stmt = select(CoolDownOrm).\ - where(CoolDownOrm.entity_index_id == entity_index_id).\ - where(CoolDownOrm.event == event) + stmt = (select(CoolDownOrm) + .where(CoolDownOrm.entity_index_id == entity_index_id) + .where(CoolDownOrm.event == event)) session_result = await self.db_session.execute(stmt) return CoolDown.model_validate(session_result.scalar_one()) @@ -53,21 +48,23 @@ async def add( entity_index_id: int, event: str, stop_at: datetime, - description: Optional[str] = None + description: str | None = None ) -> None: new_obj = CoolDownOrm(entity_index_id=entity_index_id, event=event, stop_at=stop_at, description=description, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - entity_index_id: Optional[int] = None, - event: Optional[str] = None, - stop_at: Optional[datetime] = None, - description: Optional[str] = None + entity_index_id: int | None = None, + event: str | None = None, + stop_at: datetime | None = None, + description: str | None = None ) -> None: stmt = update(CoolDownOrm).where(CoolDownOrm.id == id_) if entity_index_id is not None: @@ -79,18 +76,18 @@ async def update( if description is not None: stmt = stmt.values(description=description) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(CoolDownOrm).where(CoolDownOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def clear_expired(self) -> None: """清理所有已过期的冷却事件""" stmt = delete(CoolDownOrm).where(CoolDownOrm.stop_at <= datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/email_box.py b/src/database/internal/email_box.py deleted file mode 100644 index a6248e01..00000000 --- a/src/database/internal/email_box.py +++ /dev/null @@ -1,98 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2022/12/04 16:11 -@FileName : email_box.py -@Project : nonebot2_miya -@Description : EmailBox DAL -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select - -from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel -from ..schema import EmailBoxOrm, EmailBoxBindOrm - - -class EmailBox(BaseModel): - """邮箱 Model""" - id: int - address: str - server_host: str - protocol: str - port: int - password: str - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - - -class EmailBoxDAL(BaseDataAccessLayerModel): - """邮箱 数据库操作对象""" - - async def query_unique(self, address: str) -> EmailBox: - stmt = select(EmailBoxOrm).where(EmailBoxOrm.address == address) - session_result = await self.db_session.execute(stmt) - return EmailBox.model_validate(session_result.scalar_one()) - - async def query_entity_bound_all(self, entity_index_id: int) -> list[EmailBox]: - """查询 Entity 所绑定的全部邮箱""" - stmt = select(EmailBoxOrm).join(EmailBoxBindOrm).\ - where(EmailBoxBindOrm.entity_index_id == entity_index_id).\ - order_by(EmailBoxOrm.address) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[EmailBox], session_result.scalars().all()) - - async def query_all(self) -> list[EmailBox]: - stmt = select(EmailBoxOrm).order_by(EmailBoxOrm.address) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[EmailBox], session_result.scalars().all()) - - async def add(self, address: str, server_host: str, protocol: str, port: int, password: str) -> None: - new_obj = EmailBoxOrm(address=address, server_host=server_host, protocol=protocol, port=port, - password=password, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() - - async def update( - self, - id_: int, - *, - address: Optional[str] = None, - server_host: Optional[str] = None, - protocol: Optional[str] = None, - port: Optional[int] = None, - password: Optional[str] = None - ) -> None: - stmt = update(EmailBoxOrm).where(EmailBoxOrm.id == id_) - if address is not None: - stmt = stmt.values(address=address) - if server_host is not None: - stmt = stmt.values(server_host=server_host) - if protocol is not None: - stmt = stmt.values(protocol=protocol) - if port is not None: - stmt = stmt.values(port=port) - if password is not None: - stmt = stmt.values(password=password) - stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - async def delete(self, id_: int) -> None: - stmt = delete(EmailBoxOrm).where(EmailBoxOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - -__all__ = [ - 'EmailBox', - 'EmailBoxDAL', -] diff --git a/src/database/internal/email_box_bind.py b/src/database/internal/email_box_bind.py deleted file mode 100644 index 290dd5b9..00000000 --- a/src/database/internal/email_box_bind.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2022/12/04 16:29 -@FileName : email_box_bind.py -@Project : nonebot2_miya -@Description : EmailBoxBind DAL -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select - -from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel -from ..schema import EmailBoxBindOrm - - -class EmailBoxBind(BaseModel): - """邮箱绑定 Model""" - id: int - email_box_index_id: int - entity_index_id: int - bind_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - - -class EmailBoxBindDAL(BaseDataAccessLayerModel): - """邮箱绑定 数据库操作对象""" - - async def query_unique(self, email_box_index_id: int, entity_index_id: int) -> EmailBoxBind: - stmt = select(EmailBoxBindOrm).\ - where(EmailBoxBindOrm.email_box_index_id == email_box_index_id).\ - where(EmailBoxBindOrm.entity_index_id == entity_index_id) - session_result = await self.db_session.execute(stmt) - return EmailBoxBind.model_validate(session_result.scalar_one()) - - async def query_all(self) -> list[EmailBoxBind]: - stmt = select(EmailBoxBindOrm).order_by(EmailBoxBindOrm.email_box_index_id) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[EmailBoxBind], session_result.scalars().all()) - - async def add(self, email_box_index_id: int, entity_index_id: int, bind_info: Optional[str] = None) -> None: - new_obj = EmailBoxBindOrm(email_box_index_id=email_box_index_id, entity_index_id=entity_index_id, - bind_info=bind_info, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() - - async def update( - self, - id_: int, - *, - email_box_index_id: Optional[int] = None, - entity_index_id: Optional[int] = None, - bind_info: Optional[str] = None - ) -> None: - stmt = update(EmailBoxBindOrm).where(EmailBoxBindOrm.id == id_) - if email_box_index_id is not None: - stmt = stmt.values(email_box_index_id=email_box_index_id) - if entity_index_id is not None: - stmt = stmt.values(entity_index_id=entity_index_id) - if bind_info is not None: - stmt = stmt.values(bind_info=bind_info) - stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - async def delete(self, id_: int) -> None: - stmt = delete(EmailBoxBindOrm).where(EmailBoxBindOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - -__all__ = [ - 'EmailBoxBind', - 'EmailBoxBindDAL', -] diff --git a/src/database/internal/entity.py b/src/database/internal/entity.py index 6530240b..507fdb09 100644 --- a/src/database/internal/entity.py +++ b/src/database/internal/entity.py @@ -11,14 +11,11 @@ from copy import deepcopy from datetime import datetime from enum import StrEnum, unique -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import AuthSettingOrm, EntityOrm, SubscriptionOrm @@ -52,7 +49,7 @@ def get_supported_target_names(cls) -> set[str]: return set(member.value for _, member in cls.__members__.items()) -class Entity(BaseModel): +class Entity(BaseDataQueryResultModel): """实体对象 Model""" id: int bot_index_id: int # 所属 Bot 索引 ID @@ -60,17 +57,15 @@ class Entity(BaseModel): entity_type: EntityType # 实体对象类型 parent_id: str # 父实体 ID entity_name: str # 实体名称 - entity_info: Optional[str] = None # 实体描述信息 - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) + entity_info: str | None = None # 实体描述信息 + created_at: datetime | None = None + updated_at: datetime | None = None def __str__(self) -> str: return f'Entity.{self.entity_type.value}(id={self.id}, entity_id={self.entity_id}, name={self.entity_name})' -class EntityDAL(BaseDataAccessLayerModel): +class EntityDAL(BaseDataAccessLayerModel[EntityOrm, Entity]): """实体对象 数据库操作对象""" @property @@ -84,11 +79,11 @@ async def query_unique( entity_type: str, parent_id: str ) -> Entity: - stmt = (select(EntityOrm). - where(EntityOrm.bot_index_id == bot_index_id). - where(EntityOrm.entity_id == entity_id). - where(EntityOrm.entity_type == EntityType(entity_type)). - where(EntityOrm.parent_id == parent_id)) + stmt = (select(EntityOrm) + .where(EntityOrm.bot_index_id == bot_index_id) + .where(EntityOrm.entity_id == entity_id) + .where(EntityOrm.entity_type == EntityType(entity_type)) + .where(EntityOrm.parent_id == parent_id)) session_result = await self.db_session.execute(stmt) return Entity.model_validate(session_result.scalar_one()) @@ -118,10 +113,11 @@ async def query_all_entity_has_auth_setting( strict_match_available: bool = True ) -> list[Entity]: """根据权限节点查询具备该节点的 Entity 对象""" - stmt = (select(EntityOrm).join(AuthSettingOrm). - where(AuthSettingOrm.module == module). - where(AuthSettingOrm.plugin == plugin). - where(AuthSettingOrm.node == node)) + stmt = (select(EntityOrm) + .join(AuthSettingOrm) + .where(AuthSettingOrm.module == module) + .where(AuthSettingOrm.plugin == plugin) + .where(AuthSettingOrm.node == node)) if strict_match_available: stmt = stmt.where(AuthSettingOrm.available == available) @@ -135,11 +131,12 @@ async def query_all_entity_has_auth_setting( async def query_all_entity_subscribed_source( self, sub_source_index_id: int, - entity_type: Optional[str] = None + entity_type: str | None = None ) -> list[Entity]: """查询订阅了某订阅源的 Entity 对象""" - stmt = (select(EntityOrm).join(SubscriptionOrm). - where(SubscriptionOrm.sub_source_index_id == sub_source_index_id)) + stmt = (select(EntityOrm) + .join(SubscriptionOrm) + .where(SubscriptionOrm.sub_source_index_id == sub_source_index_id)) if entity_type is not None: stmt = stmt.where(EntityOrm.entity_type == entity_type) @@ -155,7 +152,7 @@ async def add( entity_type: str, parent_id: str, entity_name: str, - entity_info: Optional[str] = None + entity_info: str | None = None ) -> None: new_obj = EntityOrm( bot_index_id=bot_index_id, @@ -166,19 +163,21 @@ async def add( entity_info=entity_info, created_at=datetime.now() ) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - bot_index_id: Optional[int] = None, - entity_id: Optional[str] = None, - entity_type: Optional[str] = None, - parent_id: Optional[str] = None, - entity_name: Optional[str] = None, - entity_info: Optional[str] = None + bot_index_id: int | None = None, + entity_id: str | None = None, + entity_type: str | None = None, + parent_id: str | None = None, + entity_name: str | None = None, + entity_info: str | None = None ) -> None: stmt = update(EntityOrm).where(EntityOrm.id == id_) if bot_index_id is not None: @@ -194,12 +193,12 @@ async def update( if entity_info is not None: stmt = stmt.values(entity_info=entity_info) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(EntityOrm).where(EntityOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/friendship.py b/src/database/internal/friendship.py index ec07f810..07d96220 100644 --- a/src/database/internal/friendship.py +++ b/src/database/internal/friendship.py @@ -9,18 +9,15 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import FriendshipOrm -class Friendship(BaseModel): +class Friendship(BaseDataQueryResultModel): """好感度 Model""" id: int entity_index_id: int @@ -30,13 +27,11 @@ class Friendship(BaseModel): energy: float currency: float response_threshold: float - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class FriendshipDAL(BaseDataAccessLayerModel): +class FriendshipDAL(BaseDataAccessLayerModel[FriendshipOrm, Friendship]): """好感度 数据库操作对象""" async def query_unique(self, entity_index_id: int) -> Friendship: @@ -62,20 +57,22 @@ async def add( new_obj = FriendshipOrm(entity_index_id=entity_index_id, status=status, mood=mood, friendship=friendship, energy=energy, currency=currency, response_threshold=response_threshold, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - entity_index_id: Optional[int] = None, - status: Optional[str] = None, - mood: Optional[float] = None, - friendship: Optional[float] = None, - energy: Optional[float] = None, - currency: Optional[float] = None, - response_threshold: Optional[float] = None + entity_index_id: int | None = None, + status: str | None = None, + mood: float | None = None, + friendship: float | None = None, + energy: float | None = None, + currency: float | None = None, + response_threshold: float | None = None ) -> None: stmt = update(FriendshipOrm).where(FriendshipOrm.id == id_) if entity_index_id is not None: @@ -93,12 +90,12 @@ async def update( if response_threshold is not None: stmt = stmt.values(response_threshold=response_threshold) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(FriendshipOrm).where(FriendshipOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/global_cache.py b/src/database/internal/global_cache.py new file mode 100644 index 00000000..2dce2621 --- /dev/null +++ b/src/database/internal/global_cache.py @@ -0,0 +1,122 @@ +""" +@Author : Ailitonia +@Date : 2024/11/12 17:16:22 +@FileName : global_cache.py +@Project : omega-miya +@Description : Global Cache DAL +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from datetime import datetime, timedelta + +from sqlalchemy import delete, select + +from src.compat import parse_obj_as +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel +from ..schema import GlobalCacheOrm + + +class GlobalCache(BaseDataQueryResultModel): + """全局缓存 Model""" + cache_name: str + cache_key: str + cache_value: str + expired_at: datetime + created_at: datetime | None + updated_at: datetime | None + + +class GlobalCacheDAL(BaseDataAccessLayerModel[GlobalCacheOrm, GlobalCache]): + """全局缓存 数据库操作对象""" + + async def query_unique(self, cache_name: str, cache_key: str, *, include_expired: bool = False) -> GlobalCache: + stmt = (select(GlobalCacheOrm) + .where(GlobalCacheOrm.cache_name == cache_name) + .where(GlobalCacheOrm.cache_key == cache_key)) + + if not include_expired: + stmt = stmt.where(GlobalCacheOrm.expired_at >= datetime.now()) + + session_result = await self.db_session.execute(stmt) + return GlobalCache.model_validate(session_result.scalar_one()) + + async def query_series(self, cache_name: str, *, include_expired: bool = False) -> list[GlobalCache]: + stmt = select(GlobalCacheOrm).where(GlobalCacheOrm.cache_name == cache_name) + + if not include_expired: + stmt = stmt.where(GlobalCacheOrm.expired_at >= datetime.now()) + + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[GlobalCache], session_result.scalars().all()) + + async def query_all(self, *, include_expired: bool = False) -> list[GlobalCache]: + stmt = select(GlobalCacheOrm).order_by(GlobalCacheOrm.cache_name) + + if not include_expired: + stmt = stmt.where(GlobalCacheOrm.expired_at >= datetime.now()) + + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[GlobalCache], session_result.scalars().all()) + + async def add( + self, + cache_name: str, + cache_key: str, + cache_value: str, + expired_time: datetime | timedelta | None = None, + ) -> None: + if expired_time is None: + expired_at = datetime(year=9999, month=12, day=31) + elif isinstance(expired_time, datetime): + expired_at = expired_time + else: + expired_at = datetime.now() + expired_time + new_obj = GlobalCacheOrm(cache_name=cache_name, cache_key=cache_key, + cache_value=cache_value, expired_at=expired_at, created_at=datetime.now()) + await self._add(new_obj) + + async def upsert( + self, + cache_name: str, + cache_key: str, + cache_value: str, + expired_time: datetime | timedelta | None = None, + ) -> None: + if expired_time is None: + expired_at = datetime(year=9999, month=12, day=31) + elif isinstance(expired_time, datetime): + expired_at = expired_time + else: + expired_at = datetime.now() + expired_time + new_obj = GlobalCacheOrm(cache_name=cache_name, cache_key=cache_key, + cache_value=cache_value, expired_at=expired_at, updated_at=datetime.now()) + await self._merge(new_obj) + + async def update(self, *args, **kwargs) -> None: + raise NotImplementedError + + async def delete(self, cache_name: str, cache_key: str) -> None: + stmt = (delete(GlobalCacheOrm) + .where(GlobalCacheOrm.cache_name == cache_name) + .where(GlobalCacheOrm.cache_key == cache_key)) + stmt.execution_options(synchronize_session='fetch') + await self.db_session.execute(stmt) + + async def delete_series_expired(self, cache_name: str) -> None: + stmt = (delete(GlobalCacheOrm) + .where(GlobalCacheOrm.cache_name == cache_name) + .where(GlobalCacheOrm.expired_at <= datetime.now())) + stmt.execution_options(synchronize_session='fetch') + await self.db_session.execute(stmt) + + async def delete_all_expired(self) -> None: + stmt = delete(GlobalCacheOrm).where(GlobalCacheOrm.expired_at <= datetime.now()) + stmt.execution_options(synchronize_session='fetch') + await self.db_session.execute(stmt) + + +__all__ = [ + 'GlobalCache', + 'GlobalCacheDAL', +] diff --git a/src/database/internal/history.py b/src/database/internal/history.py index 5a6bc8de..8fc1ab83 100644 --- a/src/database/internal/history.py +++ b/src/database/internal/history.py @@ -9,130 +9,116 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete, desc -from sqlalchemy.future import select +from sqlalchemy import desc, select from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import HistoryOrm -class History(BaseModel): +class History(BaseDataQueryResultModel): """系统参数 Model""" id: int - time: int + message_id: str bot_self_id: str - parent_entity_id: str - entity_id: str - event_type: str - event_id: str - raw_data: str - msg_data: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + event_entity_id: str + user_entity_id: str + received_time: int + message_type: str + message_raw: str + message_text: str + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class HistoryDAL(BaseDataAccessLayerModel): +class HistoryDAL(BaseDataAccessLayerModel[HistoryOrm, History]): """系统参数 数据库操作对象""" - async def query_unique(self): - raise NotImplementedError('method not supported') + async def query_unique( + self, + message_id: int, + bot_self_id: str, + event_entity_id: str, + user_entity_id: str + ) -> History: + stmt = (select(HistoryOrm) + .where(HistoryOrm.message_id == message_id) + .where(HistoryOrm.bot_self_id == bot_self_id) + .where(HistoryOrm.event_entity_id == event_entity_id) + .where(HistoryOrm.user_entity_id == user_entity_id)) + session_result = await self.db_session.execute(stmt) + return History.model_validate(session_result.scalar_one()) - async def query_by_condition( + async def query_entity_records( self, - bot_self_id: Optional[str] = None, - parent_entity_id: Optional[str] = None, - entity_id: Optional[str] = None, - event_type: Optional[str] = None, - start_time: Optional[datetime] = None + bot_self_id: str, + event_entity_id: str | None = None, + user_entity_id: str | None = None, + *, + start_time: datetime | None = None, + end_time: datetime | None = None, + message_type: str | None = None, + exclude_bot_self_message: bool = False, ) -> list[History]: - """按条件查询历史记录 + """查询某个实体一段时间内的消息历史记录 - :param bot_self_id: bot id, 为空则返回全部 - :param parent_entity_id: 父对象 id, 为空则返回全部 - :param entity_id: 对象 id, 为空则返回全部 - :param event_type: 事件类型, 为空则返回全部 + :param bot_self_id: 收到消息的机器人ID + :param event_entity_id: 消息事件实体ID, 为空则返回全部 + :param user_entity_id: 发送对象实体ID, 为空则返回全部 :param start_time: 起始时间, 为空则返回全部 + :param end_time: 结束时间, 为空则返回全部 + :param message_type: 消息事件类型, 为空则返回全部 + :param exclude_bot_self_message: 是否排除机器人自身的消息 """ - stmt = select(HistoryOrm).order_by(desc(HistoryOrm.time)) - if bot_self_id is not None: - stmt = stmt.where(HistoryOrm.bot_self_id == bot_self_id) - if parent_entity_id is not None: - stmt = stmt.where(HistoryOrm.parent_entity_id == parent_entity_id) - if entity_id is not None: - stmt = stmt.where(HistoryOrm.event_id == entity_id) - if event_type is not None: - stmt = stmt.where(HistoryOrm.event_type == event_type) + if event_entity_id is None and user_entity_id is None: + raise ValueError('need at least one of the event_entity_id and user_entity_id parameters') + + stmt = (select(HistoryOrm) + .where(HistoryOrm.bot_self_id == bot_self_id) + .order_by(desc(HistoryOrm.received_time))) + if event_entity_id is not None: + stmt = stmt.where(HistoryOrm.event_entity_id == event_entity_id) + if user_entity_id is not None: + stmt = stmt.where(HistoryOrm.user_entity_id == user_entity_id) if start_time is not None: - stmt = stmt.where(HistoryOrm.time >= int(start_time.timestamp())) + stmt = stmt.where(HistoryOrm.received_time >= int(start_time.timestamp())) + if end_time is not None: + stmt = stmt.where(HistoryOrm.received_time <= int(end_time.timestamp())) + if message_type is not None: + stmt = stmt.where(HistoryOrm.message_type == message_type) + if exclude_bot_self_message: + stmt = stmt.where(HistoryOrm.bot_self_id != HistoryOrm.user_entity_id) session_result = await self.db_session.execute(stmt) return parse_obj_as(list[History], session_result.scalars().all()) async def query_all(self) -> list[History]: - stmt = select(HistoryOrm).order_by(desc(HistoryOrm.time)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[History], session_result.scalars().all()) + raise NotImplementedError async def add( self, - time: int, + message_id: str, bot_self_id: str, - parent_entity_id: str, - entity_id: str, - event_type: str, - event_id: str, - raw_data: str, - msg_data: Optional[str] = None + event_entity_id: str, + user_entity_id: str, + received_time: int, + message_type: str, + message_raw: str, + message_text: str, ) -> None: - new_obj = HistoryOrm(bot_self_id=bot_self_id, parent_entity_id=parent_entity_id, entity_id=entity_id, - event_type=event_type, event_id=event_id, time=time, - raw_data=raw_data, msg_data=msg_data, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + new_obj = HistoryOrm(bot_self_id=bot_self_id, event_entity_id=event_entity_id, user_entity_id=user_entity_id, + message_id=message_id, received_time=received_time, message_type=message_type, + message_raw=message_raw, message_text=message_text, created_at=datetime.now()) + await self._add(new_obj) - async def update( - self, - id_: int, - *, - time: Optional[int] = None, - bot_self_id: Optional[str] = None, - parent_entity_id: Optional[str] = None, - entity_id: Optional[str] = None, - event_type: Optional[str] = None, - event_id: Optional[str] = None, - raw_data: Optional[str] = None, - msg_data: Optional[str] = None - ) -> None: - stmt = update(HistoryOrm).where(HistoryOrm.id == id_) - if time is not None: - stmt = stmt.values(time=time) - if bot_self_id is not None: - stmt = stmt.values(bot_self_id=bot_self_id) - if parent_entity_id is not None: - stmt = stmt.values(parent_entity_id=parent_entity_id) - if entity_id is not None: - stmt = stmt.values(entity_id=entity_id) - if event_type is not None: - stmt = stmt.values(event_type=event_type) - if event_id is not None: - stmt = stmt.values(event_id=event_id) - if raw_data is not None: - stmt = stmt.values(raw_data=raw_data) - if msg_data is not None: - stmt = stmt.values(msg_data=msg_data) - stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - async def delete(self, id_: int) -> None: - stmt = delete(HistoryOrm).where(HistoryOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError + + async def update(self, *args, **kwargs) -> None: + raise NotImplementedError + + async def delete(self, *args, **kwargs) -> None: + raise NotImplementedError __all__ = [ diff --git a/src/database/internal/pixivision_article.py b/src/database/internal/pixivision_article.py deleted file mode 100644 index f6f79713..00000000 --- a/src/database/internal/pixivision_article.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2022/12/04 22:14 -@FileName : pixivision_article.py -@Project : nonebot2_miya -@Description : PixivisionArticle DAL -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from datetime import datetime -from typing import Optional, Sequence - -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete, desc -from sqlalchemy.future import select - -from src.compat import AnyUrlStr as AnyUrl, parse_obj_as -from ..model import BaseDataAccessLayerModel -from ..schema import PixivisionArticleOrm - - -class PixivisionArticle(BaseModel): - """Pixivision 特辑 Model""" - id: int - aid: int - title: str - description: str - tags: str - artworks_id: str - url: AnyUrl - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - - -class PixivisionArticleDAL(BaseDataAccessLayerModel): - """Pixivision 特辑 数据库操作对象""" - - async def query_unique(self, aid: int) -> PixivisionArticle: - stmt = select(PixivisionArticleOrm).where(PixivisionArticleOrm.aid == aid) - session_result = await self.db_session.execute(stmt) - return PixivisionArticle.model_validate(session_result.scalar_one()) - - async def query_all_aids(self) -> list[int]: - stmt = select(PixivisionArticleOrm.aid).order_by(desc(PixivisionArticleOrm.aid)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[int], session_result.scalars().all()) - - async def query_exists_ids(self, aids: Sequence[int]) -> list[int]: - """查询数据库中已有的特辑文章 aid""" - stmt = select(PixivisionArticleOrm.aid).\ - where(PixivisionArticleOrm.aid.in_(aids)).\ - order_by(desc(PixivisionArticleOrm.aid)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[int], session_result.scalars().all()) - - async def query_not_exists_ids(self, aids: Sequence[int]) -> list[int]: - """查询数据库中没有的特辑文章 aid""" - exists_aids = await self.query_exists_ids(aids=aids) - return sorted(list(set(aids) - set(exists_aids)), reverse=True) - - async def query_all(self) -> list[PixivisionArticle]: - stmt = select(PixivisionArticleOrm).order_by(desc(PixivisionArticleOrm.aid)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[PixivisionArticle], session_result.scalars().all()) - - async def add( - self, - aid: int, - title: str, - description: str, - tags: str, - artworks_id: str, - url: str - ) -> None: - new_obj = PixivisionArticleOrm(aid=aid, title=title, description=description, tags=tags, - artworks_id=artworks_id, url=url, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() - - async def update( - self, - id_: int, - *, - aid: Optional[int] = None, - title: Optional[str] = None, - description: Optional[str] = None, - tags: Optional[str] = None, - artworks_id: Optional[str] = None, - url: Optional[str] = None - ) -> None: - stmt = update(PixivisionArticleOrm).where(PixivisionArticleOrm.id == id_) - if aid is not None: - stmt = stmt.values(aid=aid) - if title is not None: - stmt = stmt.values(title=title) - if description is not None: - stmt = stmt.values(description=description) - if tags is not None: - stmt = stmt.values(tags=tags) - if artworks_id is not None: - stmt = stmt.values(artworks_id=artworks_id) - if url is not None: - stmt = stmt.values(url=url) - stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - async def delete(self, id_: int) -> None: - stmt = delete(PixivisionArticleOrm).where(PixivisionArticleOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - -__all__ = [ - 'PixivisionArticle', - 'PixivisionArticleDAL', -] diff --git a/src/database/internal/plugin.py b/src/database/internal/plugin.py index ec2cef2f..94b6c014 100644 --- a/src/database/internal/plugin.py +++ b/src/database/internal/plugin.py @@ -9,35 +9,31 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import PluginOrm -class Plugin(BaseModel): +class Plugin(BaseDataQueryResultModel): """插件 Model""" - id: int plugin_name: str module_name: str enabled: int - info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class PluginDAL(BaseDataAccessLayerModel): +class PluginDAL(BaseDataAccessLayerModel[PluginOrm, Plugin]): """插件 数据库操作对象""" async def query_unique(self, plugin_name: str, module_name: str) -> Plugin: - stmt = select(PluginOrm).where(PluginOrm.plugin_name == plugin_name).where(PluginOrm.module_name == module_name) + stmt = (select(PluginOrm) + .where(PluginOrm.plugin_name == plugin_name) + .where(PluginOrm.module_name == module_name)) session_result = await self.db_session.execute(stmt) return Plugin.model_validate(session_result.scalar_one()) @@ -55,37 +51,58 @@ async def query_all(self) -> list[Plugin]: session_result = await self.db_session.execute(stmt) return parse_obj_as(list[Plugin], session_result.scalars().all()) - async def add(self, plugin_name: str, module_name: str, enabled: int, info: Optional[str] = None) -> None: + async def add( + self, + plugin_name: str, + module_name: str, + enabled: int, + info: str | None = None, + ) -> None: new_obj = PluginOrm(plugin_name=plugin_name, module_name=module_name, enabled=enabled, info=info, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert( + self, + plugin_name: str, + module_name: str, + enabled: int, + info: str | None = None, + ) -> None: + obj_attrs = { + 'plugin_name': plugin_name, + 'module_name': module_name, + 'enabled': enabled, + 'updated_at': datetime.now() + } + if info is not None: + obj_attrs.update({'info': info}) + await self._merge(PluginOrm(**obj_attrs)) async def update( self, - id_: int, + plugin_name: str, + module_name: str, *, - plugin_name: Optional[str] = None, - module_name: Optional[str] = None, - enabled: Optional[int] = None, - info: Optional[str] = None + enabled: int | None = None, + info: str | None = None, ) -> None: - stmt = update(PluginOrm).where(PluginOrm.id == id_) - if plugin_name is not None: - stmt = stmt.values(plugin_name=plugin_name) - if module_name is not None: - stmt = stmt.values(module_name=module_name) + stmt = (update(PluginOrm) + .where(PluginOrm.plugin_name == plugin_name) + .where(PluginOrm.module_name == module_name)) if enabled is not None: stmt = stmt.values(enabled=enabled) if info is not None: stmt = stmt.values(info=info) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) - async def delete(self, id_: int) -> None: - stmt = delete(PluginOrm).where(PluginOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + async def delete(self, plugin_name: str, module_name: str) -> None: + stmt = (delete(PluginOrm) + .where(PluginOrm.plugin_name == plugin_name) + .where(PluginOrm.module_name == module_name)) + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/sign_in.py b/src/database/internal/sign_in.py index 1fc7a4d5..1ad6c90a 100644 --- a/src/database/internal/sign_in.py +++ b/src/database/internal/sign_in.py @@ -9,43 +9,38 @@ """ from datetime import date, datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete, desc -from sqlalchemy.future import select +from sqlalchemy import delete, desc, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import SignInOrm -class SignIn(BaseModel): +class SignIn(BaseDataQueryResultModel): """签到 Model""" id: int entity_index_id: int sign_in_date: date - sign_in_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + sign_in_info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class SignInDAL(BaseDataAccessLayerModel): +class SignInDAL(BaseDataAccessLayerModel[SignInOrm, SignIn]): """签到 数据库操作对象""" async def query_unique(self, entity_index_id: int, sign_in_date: date) -> SignIn: - stmt = select(SignInOrm).\ - where(SignInOrm.entity_index_id == entity_index_id).\ - where(SignInOrm.sign_in_date == sign_in_date) + stmt = (select(SignInOrm) + .where(SignInOrm.entity_index_id == entity_index_id) + .where(SignInOrm.sign_in_date == sign_in_date)) session_result = await self.db_session.execute(stmt) return SignIn.model_validate(session_result.scalar_one()) async def query_entity_sign_in_days(self, entity_index_id: int) -> list[date]: - stmt = select(SignInOrm.sign_in_date).\ - where(SignInOrm.entity_index_id == entity_index_id).\ - order_by(desc(SignInOrm.sign_in_date)) + stmt = (select(SignInOrm.sign_in_date) + .where(SignInOrm.entity_index_id == entity_index_id) + .order_by(desc(SignInOrm.sign_in_date))) session_result = await self.db_session.execute(stmt) return parse_obj_as(list[date], session_result.scalars().all()) @@ -58,20 +53,22 @@ async def add( self, entity_index_id: int, sign_in_date: date, - sign_in_info: Optional[str] = None + sign_in_info: str | None = None ) -> None: new_obj = SignInOrm(entity_index_id=entity_index_id, sign_in_date=sign_in_date, sign_in_info=sign_in_info, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - entity_index_id: Optional[int] = None, - sign_in_date: Optional[date] = None, - sign_in_info: Optional[str] = None + entity_index_id: int | None = None, + sign_in_date: date | None = None, + sign_in_info: str | None = None ) -> None: stmt = update(SignInOrm).where(SignInOrm.id == id_) if entity_index_id is not None: @@ -81,12 +78,12 @@ async def update( if sign_in_info is not None: stmt = stmt.values(sign_in_info=sign_in_info) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(SignInOrm).where(SignInOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/social_media_content.py b/src/database/internal/social_media_content.py new file mode 100644 index 00000000..f520241a --- /dev/null +++ b/src/database/internal/social_media_content.py @@ -0,0 +1,149 @@ +""" +@Author : Ailitonia +@Date : 2024/10/23 19:54 +@FileName : social_media_content +@Project : omega-miya +@Description : SocialMediaContent DAL +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from collections.abc import Sequence +from datetime import datetime + +from sqlalchemy import desc, select + +from src.compat import parse_obj_as +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel +from ..schema import SocialMediaContentOrm + + +class SocialMediaContent(BaseDataQueryResultModel): + """社交媒体平台内容 Model""" + source: str + m_id: str + m_type: str + m_uid: str + content: str + ref_content: str + created_at: datetime | None = None + updated_at: datetime | None = None + + +class SocialMediaContentDAL(BaseDataAccessLayerModel[SocialMediaContentOrm, SocialMediaContent]): + """社交媒体平台内容 数据库操作对象""" + + async def query_unique(self, source: str, m_id: str) -> SocialMediaContent: + stmt = (select(SocialMediaContentOrm) + .where(SocialMediaContentOrm.source == source) + .where(SocialMediaContentOrm.m_id == m_id)) + session_result = await self.db_session.execute(stmt) + return SocialMediaContent.model_validate(session_result.scalar_one()) + + async def query_all(self) -> list[SocialMediaContent]: + raise NotImplementedError + + async def query_source_all(self, source: str) -> list[SocialMediaContent]: + """查询指定来源平台所有记录行""" + stmt = (select(SocialMediaContentOrm) + .where(SocialMediaContentOrm.source == source) + .order_by(desc(SocialMediaContentOrm.m_id))) + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[SocialMediaContent], session_result.scalars().all()) + + async def query_source_all_mids(self, source: str) -> list[str]: + """查询指定来源平台所有记录行中的 mid""" + stmt = (select(SocialMediaContentOrm.m_id) + .where(SocialMediaContentOrm.source == source) + .order_by(desc(SocialMediaContentOrm.m_id))) + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[str], session_result.scalars().all()) + + async def query_source_exists_mids(self, source: str, mids: Sequence[str]) -> list[str]: + """根据提供的 mids 查询其中已经存在于数据库记录中的条目""" + stmt = (select(SocialMediaContentOrm.m_id) + .where(SocialMediaContentOrm.source == source) + .where(SocialMediaContentOrm.m_id.in_(mids)) + .order_by(desc(SocialMediaContentOrm.m_id))) + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[str], session_result.scalars().all()) + + async def query_source_not_exists_mids(self, source: str, mids: Sequence[str]) -> list[str]: + """根据提供的 mids 查询其中不存在于数据库记录中的条目""" + exists_mids = await self.query_source_exists_mids(source=source, mids=mids) + return sorted(list(set(mids) - set(exists_mids)), reverse=True) + + async def query_user_all(self, source: str, uid: str) -> list[SocialMediaContent]: + """查询指定来源平台指定用户所有记录行""" + stmt = (select(SocialMediaContentOrm) + .where(SocialMediaContentOrm.source == source) + .where(SocialMediaContentOrm.m_uid == uid) + .order_by(desc(SocialMediaContentOrm.m_id))) + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[SocialMediaContent], session_result.scalars().all()) + + async def query_user_all_mids(self, source: str, uid: str) -> list[str]: + """查询指定来源平台指定用户所有记录行中的 mid""" + stmt = (select(SocialMediaContentOrm.m_id) + .where(SocialMediaContentOrm.source == source) + .where(SocialMediaContentOrm.m_uid == uid) + .order_by(desc(SocialMediaContentOrm.m_id))) + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[str], session_result.scalars().all()) + + async def query_user_exists_mids(self, source: str, uid: str, mids: Sequence[str]) -> list[str]: + """根据提供的 mids 查询对应用户其中已经存在于数据库记录中的条目""" + stmt = (select(SocialMediaContentOrm.m_id) + .where(SocialMediaContentOrm.source == source) + .where(SocialMediaContentOrm.m_uid == uid) + .where(SocialMediaContentOrm.m_id.in_(mids)) + .order_by(desc(SocialMediaContentOrm.m_id))) + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[str], session_result.scalars().all()) + + async def query_user_not_exists_mids(self, source: str, uid: str, mids: Sequence[str]) -> list[str]: + """根据提供的 mids 查询对应用户其中不存在于数据库记录中的条目""" + exists_mids = await self.query_user_exists_mids(source=source, uid=uid, mids=mids) + return sorted(list(set(mids) - set(exists_mids)), reverse=True) + + async def add( + self, + source: str, + m_id: str, + m_type: str, + m_uid: str, + title: str, + content: str, + ref_content: str = '', + ) -> None: + new_obj = SocialMediaContentOrm(source=source, m_id=m_id, m_type=m_type, m_uid=m_uid, + title=title[:255], content=content[:4096], ref_content=ref_content[:4096], + created_at=datetime.now()) + await self._add(new_obj) + + async def upsert( + self, + source: str, + m_id: str, + m_type: str, + m_uid: str, + title: str, + content: str, + ref_content: str = '', + ) -> None: + new_obj = SocialMediaContentOrm(source=source, m_id=m_id, m_type=m_type, m_uid=m_uid, + title=title[:255], content=content[:4096], ref_content=ref_content[:4096], + updated_at=datetime.now()) + await self._merge(new_obj) + + async def update(self, *args, **kwargs) -> None: + raise NotImplementedError + + async def delete(self, *args, **kwargs) -> None: + raise NotImplementedError + + +__all__ = [ + 'SocialMediaContent', + 'SocialMediaContentDAL', +] diff --git a/src/database/internal/statistic.py b/src/database/internal/statistic.py index b9f514e5..7c7067be 100644 --- a/src/database/internal/statistic.py +++ b/src/database/internal/statistic.py @@ -9,19 +9,15 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete, desc -from sqlalchemy.future import select -from sqlalchemy.sql.expression import func +from sqlalchemy import desc, func, select from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import StatisticOrm -class Statistic(BaseModel): +class Statistic(BaseDataQueryResultModel): """统计信息 Model""" id: int module_name: str @@ -30,33 +26,30 @@ class Statistic(BaseModel): parent_entity_id: str entity_id: str call_time: datetime - call_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + call_info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class CountStatisticModel(BaseModel): +class CountStatisticModel(BaseDataQueryResultModel): """查询统计信息结果 Model""" custom_name: str call_count: int - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class StatisticDAL(BaseDataAccessLayerModel): +class StatisticDAL(BaseDataAccessLayerModel[StatisticOrm, Statistic]): """统计信息 数据库操作对象""" - async def query_unique(self): - raise NotImplementedError('method not supported') + async def query_unique(self, *args, **kwargs) -> Statistic: + raise NotImplementedError async def count_by_condition( self, - bot_self_id: Optional[str] = None, - parent_entity_id: Optional[str] = None, - entity_id: Optional[str] = None, - start_time: Optional[datetime] = None + *, + bot_self_id: str | None = None, + parent_entity_id: str | None = None, + entity_id: str | None = None, + start_time: datetime | None = None, ) -> list[CountStatisticModel]: """按条件查询统计信息 @@ -92,49 +85,21 @@ async def add( parent_entity_id: str, entity_id: str, call_time: datetime, - call_info: Optional[str] = None + call_info: str | None = None, ) -> None: new_obj = StatisticOrm(module_name=module_name, plugin_name=plugin_name, bot_self_id=bot_self_id, parent_entity_id=parent_entity_id, entity_id=entity_id, call_time=call_time, call_info=call_info, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) - async def update( - self, - id_: int, - *, - module_name: Optional[str] = None, - plugin_name: Optional[str] = None, - bot_self_id: Optional[str] = None, - parent_entity_id: Optional[str] = None, - entity_id: Optional[str] = None, - call_time: Optional[datetime] = None, - call_info: Optional[str] = None - ) -> None: - stmt = update(StatisticOrm).where(StatisticOrm.id == id_) - if module_name is not None: - stmt = stmt.values(module_name=module_name) - if plugin_name is not None: - stmt = stmt.values(plugin_name=plugin_name) - if bot_self_id is not None: - stmt = stmt.values(bot_self_id=bot_self_id) - if parent_entity_id is not None: - stmt = stmt.values(parent_entity_id=parent_entity_id) - if entity_id is not None: - stmt = stmt.values(entity_id=entity_id) - if call_time is not None: - stmt = stmt.values(call_time=call_time) - if call_info is not None: - stmt = stmt.values(call_info=call_info) - stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - async def delete(self, id_: int) -> None: - stmt = delete(StatisticOrm).where(StatisticOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError + + async def update(self, *args, **kwargs) -> None: + raise NotImplementedError + + async def delete(self, *args, **kwargs) -> None: + raise NotImplementedError __all__ = [ diff --git a/src/database/internal/subscription.py b/src/database/internal/subscription.py index 3b0e475b..bf223b33 100644 --- a/src/database/internal/subscription.py +++ b/src/database/internal/subscription.py @@ -9,36 +9,31 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import SubscriptionOrm -class Subscription(BaseModel): +class Subscription(BaseDataQueryResultModel): """订阅 Model""" id: int sub_source_index_id: int entity_index_id: int - sub_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + sub_info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class SubscriptionDAL(BaseDataAccessLayerModel): +class SubscriptionDAL(BaseDataAccessLayerModel[SubscriptionOrm, Subscription]): """订阅 数据库操作对象""" async def query_unique(self, sub_source_index_id: int, entity_index_id: int) -> Subscription: - stmt = select(SubscriptionOrm).\ - where(SubscriptionOrm.sub_source_index_id == sub_source_index_id).\ - where(SubscriptionOrm.entity_index_id == entity_index_id) + stmt = (select(SubscriptionOrm) + .where(SubscriptionOrm.sub_source_index_id == sub_source_index_id) + .where(SubscriptionOrm.entity_index_id == entity_index_id)) session_result = await self.db_session.execute(stmt) return Subscription.model_validate(session_result.scalar_one()) @@ -47,19 +42,21 @@ async def query_all(self) -> list[Subscription]: session_result = await self.db_session.execute(stmt) return parse_obj_as(list[Subscription], session_result.scalars().all()) - async def add(self, sub_source_index_id: int, entity_index_id: int, sub_info: Optional[str] = None) -> None: + async def add(self, sub_source_index_id: int, entity_index_id: int, sub_info: str | None = None) -> None: new_obj = SubscriptionOrm(sub_source_index_id=sub_source_index_id, entity_index_id=entity_index_id, sub_info=sub_info, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - sub_source_index_id: Optional[int] = None, - entity_index_id: Optional[int] = None, - sub_info: Optional[str] = None + sub_source_index_id: int | None = None, + entity_index_id: int | None = None, + sub_info: str | None = None ) -> None: stmt = update(SubscriptionOrm).where(SubscriptionOrm.id == id_) if sub_source_index_id is not None: @@ -69,12 +66,12 @@ async def update( if sub_info is not None: stmt = stmt.values(sub_info=sub_info) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(SubscriptionOrm).where(SubscriptionOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/subscription_source.py b/src/database/internal/subscription_source.py index ee2512f1..e146a86d 100644 --- a/src/database/internal/subscription_source.py +++ b/src/database/internal/subscription_source.py @@ -11,14 +11,11 @@ from copy import deepcopy from datetime import datetime from enum import StrEnum, unique -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import SubscriptionOrm, SubscriptionSourceOrm @@ -32,20 +29,18 @@ class SubscriptionSourceType(StrEnum): weibo_user = 'weibo_user' -class SubscriptionSource(BaseModel): +class SubscriptionSource(BaseDataQueryResultModel): """订阅源 Model""" id: int sub_type: SubscriptionSourceType sub_id: str sub_user_name: str - sub_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + sub_info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class SubscriptionSourceDAL(BaseDataAccessLayerModel): +class SubscriptionSourceDAL(BaseDataAccessLayerModel[SubscriptionSourceOrm, SubscriptionSource]): """订阅源 数据库操作对象""" @property @@ -53,20 +48,21 @@ def subscription_source_type(self) -> type[SubscriptionSourceType]: return deepcopy(SubscriptionSourceType) async def query_unique(self, sub_type: str, sub_id: str) -> SubscriptionSource: - stmt = (select(SubscriptionSourceOrm). - where(SubscriptionSourceOrm.sub_type == sub_type). - where(SubscriptionSourceOrm.sub_id == sub_id)) + stmt = (select(SubscriptionSourceOrm) + .where(SubscriptionSourceOrm.sub_type == sub_type) + .where(SubscriptionSourceOrm.sub_id == sub_id)) session_result = await self.db_session.execute(stmt) return SubscriptionSource.model_validate(session_result.scalar_one()) async def query_entity_subscribed_all( self, entity_index_id: int, - sub_type: Optional[str] = None + sub_type: str | None = None ) -> list[SubscriptionSource]: """查询 Entity 所订阅的全部订阅源""" - stmt = (select(SubscriptionSourceOrm).join(SubscriptionOrm). - where(SubscriptionOrm.entity_index_id == entity_index_id)) + stmt = (select(SubscriptionSourceOrm) + .join(SubscriptionOrm) + .where(SubscriptionOrm.entity_index_id == entity_index_id)) if sub_type is not None: stmt = stmt.where(SubscriptionSourceOrm.sub_type == SubscriptionSourceType(sub_type)) @@ -77,9 +73,9 @@ async def query_entity_subscribed_all( async def query_type_all(self, sub_type: str) -> list[SubscriptionSource]: """查询 sub_type 对应的全部订阅源""" - stmt = (select(SubscriptionSourceOrm). - where(SubscriptionSourceOrm.sub_type == SubscriptionSourceType(sub_type)). - order_by(SubscriptionSourceOrm.sub_type)) + stmt = (select(SubscriptionSourceOrm) + .where(SubscriptionSourceOrm.sub_type == SubscriptionSourceType(sub_type)) + .order_by(SubscriptionSourceOrm.sub_type)) session_result = await self.db_session.execute(stmt) return parse_obj_as(list[SubscriptionSource], session_result.scalars().all()) @@ -88,7 +84,7 @@ async def query_all(self) -> list[SubscriptionSource]: session_result = await self.db_session.execute(stmt) return parse_obj_as(list[SubscriptionSource], session_result.scalars().all()) - async def add(self, sub_type: str, sub_id: str, sub_user_name: str, sub_info: Optional[str] = None) -> None: + async def add(self, sub_type: str, sub_id: str, sub_user_name: str, sub_info: str | None = None) -> None: new_obj = SubscriptionSourceOrm( sub_type=SubscriptionSourceType(sub_type), sub_id=sub_id, @@ -96,17 +92,19 @@ async def add(self, sub_type: str, sub_id: str, sub_user_name: str, sub_info: Op sub_info=sub_info, created_at=datetime.now() ) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - sub_type: Optional[str] = None, - sub_id: Optional[str] = None, - sub_user_name: Optional[str] = None, - sub_info: Optional[str] = None + sub_type: str | None = None, + sub_id: str | None = None, + sub_user_name: str | None = None, + sub_info: str | None = None ) -> None: stmt = update(SubscriptionSourceOrm).where(SubscriptionSourceOrm.id == id_) if sub_type is not None: @@ -118,12 +116,12 @@ async def update( if sub_info is not None: stmt = stmt.values(sub_info=sub_info) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(SubscriptionSourceOrm).where(SubscriptionSourceOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/system_setting.py b/src/database/internal/system_setting.py index aec3af38..72b78976 100644 --- a/src/database/internal/system_setting.py +++ b/src/database/internal/system_setting.py @@ -9,70 +9,96 @@ """ from datetime import datetime -from typing import Optional -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import SystemSettingOrm -class SystemSetting(BaseModel): +class SystemSetting(BaseDataQueryResultModel): """系统参数 Model""" - id: int setting_name: str + setting_key: str setting_value: str - info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class SystemSettingDAL(BaseDataAccessLayerModel): +class SystemSettingDAL(BaseDataAccessLayerModel[SystemSettingOrm, SystemSetting]): """系统参数 数据库操作对象""" - async def query_unique(self, setting_name: str) -> SystemSetting: - stmt = select(SystemSettingOrm).where(SystemSettingOrm.setting_name == setting_name) + async def query_unique(self, setting_name: str, setting_key: str) -> SystemSetting: + stmt = (select(SystemSettingOrm) + .where(SystemSettingOrm.setting_name == setting_name) + .where(SystemSettingOrm.setting_key == setting_key)) session_result = await self.db_session.execute(stmt) return SystemSetting.model_validate(session_result.scalar_one()) + async def query_series(self, setting_name: str) -> list[SystemSetting]: + stmt = select(SystemSettingOrm).where(SystemSettingOrm.setting_name == setting_name) + session_result = await self.db_session.execute(stmt) + return parse_obj_as(list[SystemSetting], session_result.scalars().all()) + async def query_all(self) -> list[SystemSetting]: stmt = select(SystemSettingOrm).order_by(SystemSettingOrm.setting_name) session_result = await self.db_session.execute(stmt) return parse_obj_as(list[SystemSetting], session_result.scalars().all()) - async def add(self, setting_name: str, setting_value: str, info: Optional[str] = None) -> None: - new_obj = SystemSettingOrm(setting_name=setting_name, setting_value=setting_value, - info=info, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + async def add( + self, + setting_name: str, + setting_key: str, + setting_value: str, + info: str | None = None, + ) -> None: + new_obj = SystemSettingOrm(setting_name=setting_name, setting_key=setting_key, + setting_value=setting_value, info=info, created_at=datetime.now()) + await self._add(new_obj) + + async def upsert( + self, + setting_name: str, + setting_key: str, + setting_value: str, + info: str | None = None, + ) -> None: + obj_attrs = { + 'setting_name': setting_name, + 'setting_key': setting_key, + 'setting_value': setting_value, + 'updated_at': datetime.now() + } + if info is not None: + obj_attrs.update({'info': info}) + await self._merge(SystemSettingOrm(**obj_attrs)) async def update( self, - id_: int, + setting_name: str, + setting_key: str, *, - setting_name: Optional[str] = None, - setting_value: Optional[str] = None, - info: Optional[str] = None + setting_value: str | None = None, + info: str | None = None, ) -> None: - stmt = update(SystemSettingOrm).where(SystemSettingOrm.id == id_) - if setting_name is not None: - stmt = stmt.values(setting_name=setting_name) + stmt = (update(SystemSettingOrm) + .where(SystemSettingOrm.setting_name == setting_name) + .where(SystemSettingOrm.setting_key == setting_key)) if setting_value is not None: stmt = stmt.values(setting_value=setting_value) if info is not None: stmt = stmt.values(info=info) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) - async def delete(self, id_: int) -> None: - stmt = delete(SystemSettingOrm).where(SystemSettingOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + async def delete(self, setting_name: str, setting_key: str) -> None: + stmt = (delete(SystemSettingOrm) + .where(SystemSettingOrm.setting_name == setting_name) + .where(SystemSettingOrm.setting_key == setting_key)) + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/internal/weibo_detail.py b/src/database/internal/weibo_detail.py deleted file mode 100644 index e061b11d..00000000 --- a/src/database/internal/weibo_detail.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2023/8/6 11:52 -@FileName : weibo_detail -@Project : nonebot2_miya -@Description : WeiboDetail DAL -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from datetime import datetime -from typing import Optional, Sequence - -from pydantic import BaseModel, ConfigDict -from sqlalchemy import update, delete, desc -from sqlalchemy.future import select - -from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel -from ..schema import WeiboDetailOrm - - -class WeiboDetail(BaseModel): - """微博内容 Model""" - id: int - mid: int - uid: int - content: str - retweeted_content: str - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - - -class WeiboDetailDAL(BaseDataAccessLayerModel): - """微博内容 数据库操作对象""" - - async def query_unique(self, mid: int) -> WeiboDetail: - stmt = select(WeiboDetailOrm).where(WeiboDetailOrm.mid == mid) - session_result = await self.db_session.execute(stmt) - return WeiboDetail.model_validate(session_result.scalar_one()) - - async def query_exists_ids(self, mids: Sequence[int]) -> list[int]: - """查询数据库中 mids 列表中已有的微博 id""" - stmt = select(WeiboDetailOrm.mid).\ - where(WeiboDetailOrm.mid.in_(mids)).\ - order_by(desc(WeiboDetailOrm.mid)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[int], session_result.scalars().all()) - - async def query_not_exists_ids(self, mids: Sequence[int]) -> list[int]: - """查询数据库中 mids 列表中没有的微博 id""" - exists_mids = await self.query_exists_ids(mids=mids) - return sorted(list(set(mids) - set(exists_mids)), reverse=True) - - async def query_all(self) -> list[WeiboDetail]: - stmt = select(WeiboDetailOrm).order_by(desc(WeiboDetailOrm.mid)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[WeiboDetail], session_result.scalars().all()) - - async def query_user_all(self, uid: int) -> list[WeiboDetail]: - """查询用户的全部微博内容""" - stmt = select(WeiboDetailOrm).where(WeiboDetailOrm.uid == uid).order_by(desc(WeiboDetailOrm.mid)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[WeiboDetail], session_result.scalars().all()) - - async def query_user_all_weibo_mids(self, uid: int) -> list[int]: - """查询用户的全部微博id""" - stmt = select(WeiboDetailOrm.mid).\ - where(WeiboDetailOrm.uid == uid).\ - order_by(desc(WeiboDetailOrm.mid)) - session_result = await self.db_session.execute(stmt) - return parse_obj_as(list[int], session_result.scalars().all()) - - async def add(self, mid: int, uid: int, content: str, retweeted_content: str = '') -> None: - new_obj = WeiboDetailOrm( - mid=mid, uid=uid, - content=content[:2048], retweeted_content=retweeted_content[:2048], - created_at=datetime.now() - ) - self.db_session.add(new_obj) - await self.db_session.flush() - - async def update( - self, - id_: int, - *, - mid: Optional[int] = None, - uid: Optional[int] = None, - content: Optional[str] = None, - retweeted_content: Optional[str] = None - ) -> None: - stmt = update(WeiboDetailOrm).where(WeiboDetailOrm.id == id_) - if mid is not None: - stmt = stmt.values(mid=mid) - if uid is not None: - stmt = stmt.values(uid=uid) - if content is not None: - stmt = stmt.values(content=content[:2048]) - if retweeted_content is not None: - stmt = stmt.values(retweeted_content=retweeted_content[:2048]) - stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - async def delete(self, id_: int) -> None: - stmt = delete(WeiboDetailOrm).where(WeiboDetailOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") - await self.db_session.execute(stmt) - - -__all__ = [ - 'WeiboDetail', - 'WeiboDetailDAL', -] diff --git a/src/database/internal/word_bank.py b/src/database/internal/word_bank.py index fde52694..f3ce8d77 100644 --- a/src/database/internal/word_bank.py +++ b/src/database/internal/word_bank.py @@ -9,36 +9,31 @@ """ from datetime import datetime -from typing import Optional -from pydantic import ConfigDict, BaseModel -from sqlalchemy import update, delete -from sqlalchemy.future import select +from sqlalchemy import delete, select, update from src.compat import parse_obj_as -from ..model import BaseDataAccessLayerModel +from ..model import BaseDataAccessLayerModel, BaseDataQueryResultModel from ..schema import WordBankOrm -class WordBank(BaseModel): +class WordBank(BaseDataQueryResultModel): """问答语料词句 Model""" id: int key_word: str reply_entity: str result_word: str - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + created_at: datetime | None = None + updated_at: datetime | None = None - model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True) - -class WordBankDAL(BaseDataAccessLayerModel): +class WordBankDAL(BaseDataAccessLayerModel[WordBankOrm, WordBank]): """问答语料词句 数据库操作对象""" async def query_unique(self, key_word: str, reply_entity: str) -> WordBank: - stmt = select(WordBankOrm).\ - where(WordBankOrm.key_word == key_word).\ - where(WordBankOrm.reply_entity == reply_entity) + stmt = (select(WordBankOrm) + .where(WordBankOrm.key_word == key_word) + .where(WordBankOrm.reply_entity == reply_entity)) session_result = await self.db_session.execute(stmt) return WordBank.model_validate(session_result.scalar_one()) @@ -56,16 +51,18 @@ async def query_all(self) -> list[WordBank]: async def add(self, key_word: str, reply_entity: str, result_word: str) -> None: new_obj = WordBankOrm(key_word=key_word, reply_entity=reply_entity, result_word=result_word, created_at=datetime.now()) - self.db_session.add(new_obj) - await self.db_session.flush() + await self._add(new_obj) + + async def upsert(self, *args, **kwargs) -> None: + raise NotImplementedError async def update( self, id_: int, *, - key_word: Optional[str] = None, - reply_entity: Optional[str] = None, - result_word: Optional[str] = None + key_word: str | None = None, + reply_entity: str | None = None, + result_word: str | None = None ) -> None: stmt = update(WordBankOrm).where(WordBankOrm.id == id_) if key_word is not None: @@ -75,12 +72,12 @@ async def update( if result_word is not None: stmt = stmt.values(result_word=result_word) stmt = stmt.values(updated_at=datetime.now()) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) async def delete(self, id_: int) -> None: stmt = delete(WordBankOrm).where(WordBankOrm.id == id_) - stmt.execution_options(synchronize_session="fetch") + stmt.execution_options(synchronize_session='fetch') await self.db_session.execute(stmt) diff --git a/src/database/migrate.py b/src/database/migrate.py index 146dc5ad..50653e21 100644 --- a/src/database/migrate.py +++ b/src/database/migrate.py @@ -25,8 +25,8 @@ def construct_config() -> Config: sqlalchemy_url = database_config.connector.url.replace('%', '%%') alembic_cfg = Config() - alembic_cfg.set_main_option('prepend_sys_path', str(prepend_sys_path.resolve())) - alembic_cfg.set_main_option('script_location', str(script_location.resolve())) + alembic_cfg.set_main_option('prepend_sys_path', prepend_sys_path.resolve().as_posix()) + alembic_cfg.set_main_option('script_location', script_location.resolve().as_posix()) alembic_cfg.set_main_option('file_template', file_template) alembic_cfg.set_main_option('sqlalchemy.url', sqlalchemy_url) diff --git a/src/database/model.py b/src/database/model.py index 0f4901a7..14841ef0 100644 --- a/src/database/model.py +++ b/src/database/model.py @@ -9,15 +9,26 @@ """ import abc -from typing import Any, AsyncGenerator, Self +from collections.abc import AsyncGenerator +from typing import TYPE_CHECKING, Self +from pydantic import BaseModel, ConfigDict from sqlalchemy.ext.asyncio import AsyncSession from .helpers import begin_db_session +if TYPE_CHECKING: + from .schema_base import OmegaDeclarativeBase -class BaseDataAccessLayerModel(abc.ABC): - """数据库操作对象""" + +class BaseDataQueryResultModel(BaseModel): + """数据库查询结果数据模型基类""" + + model_config = ConfigDict(extra='ignore', coerce_numbers_to_str=True, from_attributes=True, frozen=True) + + +class BaseDataAccessLayerModel[TB: 'OmegaDeclarativeBase', TR: BaseDataQueryResultModel](abc.ABC): + """数据库操作对象基类""" def __init__(self, session: AsyncSession): self.db_session = session @@ -30,13 +41,23 @@ async def dal_dependence(cls) -> AsyncGenerator[Self, None]: async with begin_db_session() as session: yield cls(session) + async def _add(self, obj: TB) -> None: + """内部方法, 向数据库插入新行""" + self.db_session.add(obj) + await self.db_session.flush() + + async def _merge(self, obj: TB) -> None: + """内部方法, 向数据库插入新行, 如主键存在则更新""" + await self.db_session.merge(obj) + await self.db_session.flush() + @abc.abstractmethod - async def query_unique(self, *args, **kwargs) -> Any: + async def query_unique(self, *args, **kwargs) -> TR: """查询唯一行""" raise NotImplementedError @abc.abstractmethod - async def query_all(self) -> list[Any]: + async def query_all(self) -> list[TR]: """查询全部行""" raise NotImplementedError @@ -45,6 +66,11 @@ async def add(self, *args, **kwargs) -> None: """新增行""" raise NotImplementedError + @abc.abstractmethod + async def upsert(self, *args, **kwargs) -> None: + """新增行, 若主键存在则更新行""" + raise NotImplementedError + @abc.abstractmethod async def update(self, *args, **kwargs) -> None: """更新行""" @@ -63,4 +89,5 @@ async def commit_session(self) -> None: __all__ = [ 'BaseDataAccessLayerModel', + 'BaseDataQueryResultModel', ] diff --git a/src/database/schema.py b/src/database/schema.py index 014a28df..c64ea4de 100644 --- a/src/database/schema.py +++ b/src/database/schema.py @@ -10,7 +10,7 @@ from datetime import date, datetime -from sqlalchemy import Sequence, ForeignKey +from sqlalchemy import ForeignKey, Sequence from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.types import BigInteger, Date, DateTime, Float, Integer, String @@ -19,6 +19,26 @@ from .types import IndexInt +class GlobalCacheOrm(Base): + """全局缓存表, 存放各种需要持久化的缓存数据""" + __tablename__ = f'{database_config.db_prefix}global_cache' + if database_config.table_args is not None: + __table_args__ = database_config.table_args + + # 表结构 + cache_name: Mapped[str] = mapped_column(String(64), primary_key=True, nullable=False, index=True) + cache_key: Mapped[str] = mapped_column(String(64), primary_key=True, nullable=False, index=True) + cache_value: Mapped[str] = mapped_column(String(4096), nullable=False) + expired_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, index=True, comment='缓存到期时间') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) + updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + + def __repr__(self) -> str: + return (f'GlobalCacheOrm(cache_name={self.cache_name!r}, cache_key={self.cache_key!r}, ' + f'cache_value={self.cache_value!r}, expired_at={self.expired_at!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') + + class SystemSettingOrm(Base): """系统参数表, 存放运行时配置""" __tablename__ = f'{database_config.db_prefix}system_setting' @@ -26,18 +46,17 @@ class SystemSettingOrm(Base): __table_args__ = database_config.table_args # 表结构 - id: Mapped[int] = mapped_column( - Integer, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True - ) - setting_name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, unique=True, comment='参数名称') - setting_value: Mapped[str] = mapped_column(String(512), nullable=False, index=True, comment='参数值') - info: Mapped[str] = mapped_column(String(512), nullable=True, comment='参数说明') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + setting_name: Mapped[str] = mapped_column(String(64), primary_key=True, nullable=False, index=True) + setting_key: Mapped[str] = mapped_column(String(64), primary_key=True, nullable=False, index=True) + setting_value: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment='参数值') + info: Mapped[str] = mapped_column(String(64), nullable=True, comment='参数说明') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: - return (f"SystemSettingOrm(setting_name={self.setting_name!r}, setting_value={self.setting_value!r}, " - f"info={self.info!r}, created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'SystemSettingOrm(setting_name={self.setting_name!r}, setting_key={self.setting_key!r}, ' + f'setting_value={self.setting_value!r}, info={self.info!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class PluginOrm(Base): @@ -47,22 +66,19 @@ class PluginOrm(Base): __table_args__ = database_config.table_args # 表结构 - id: Mapped[int] = mapped_column( - Integer, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True - ) - plugin_name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, unique=True, comment='插件名称') - module_name: Mapped[str] = mapped_column(String(128), nullable=False, index=True, unique=True, comment='插件模块名称') + plugin_name: Mapped[str] = mapped_column(String(64), primary_key=True, nullable=False, index=True, unique=True) + module_name: Mapped[str] = mapped_column(String(128), primary_key=True, nullable=False, index=True, unique=True) enabled: Mapped[int] = mapped_column( Integer, nullable=False, index=True, comment='启用状态, 1: 启用, 0: 禁用, -1: 失效或未安装' ) - info: Mapped[str] = mapped_column(String(255), nullable=True, comment='附加说明') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + info: Mapped[str] = mapped_column(String(255), nullable=True, comment='插件信息及附加说明') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: - return (f"PluginOrm(plugin_name={self.plugin_name!r}, module_name={self.module_name!r}, " - f"enabled={self.enabled!r}, info={self.info!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'PluginOrm(plugin_name={self.plugin_name!r}, module_name={self.module_name!r}, ' + f'enabled={self.enabled!r}, info={self.info!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class StatisticOrm(Base): @@ -75,26 +91,26 @@ class StatisticOrm(Base): id: Mapped[int] = mapped_column( IndexInt, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True ) - module_name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='插件模块名称') + module_name: Mapped[str] = mapped_column(String(128), nullable=False, index=True, comment='插件模块名称') plugin_name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='插件显示名称') bot_self_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='对应的Bot') parent_entity_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='对应调用用户父实体信息') entity_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='对应调用用户实体信息') call_time: Mapped[datetime] = mapped_column(DateTime, nullable=False, index=True, comment='调用时间') call_info: Mapped[str] = mapped_column(String(4096), nullable=True, index=False, comment='调用信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: - return (f"StatisticOrm(module_name={self.module_name!r}, plugin_name={self.plugin_name!r}, " - f"bot_self_id={self.bot_self_id!r}, parent_entity_id={self.parent_entity_id!r}, " - f"entity_id={self.entity_id!r}, call_time={self.call_time!r}, call_info={self.call_info!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'StatisticOrm(module_name={self.module_name!r}, plugin_name={self.plugin_name!r}, ' + f'bot_self_id={self.bot_self_id!r}, parent_entity_id={self.parent_entity_id!r}, ' + f'entity_id={self.entity_id!r}, call_time={self.call_time!r}, call_info={self.call_info!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class HistoryOrm(Base): - """记录表""" - __tablename__ = f'{database_config.db_prefix}history' + """原始消息记录表""" + __tablename__ = f'{database_config.db_prefix}message_history' if database_config.table_args is not None: __table_args__ = database_config.table_args @@ -102,23 +118,23 @@ class HistoryOrm(Base): id: Mapped[int] = mapped_column( IndexInt, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True ) - time: Mapped[int] = mapped_column(BigInteger, nullable=False, comment='事件发生的时间戳') - bot_self_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='收到事件的机器人id') - parent_entity_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='事件对应对象父实体id') - entity_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='事件对应对象实体id') - event_type: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='事件类型') - event_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='事件id') - raw_data: Mapped[str] = mapped_column(String(4096), nullable=False, comment='原始事件内容') - msg_data: Mapped[str] = mapped_column(String(4096), nullable=True, comment='经处理的事件内容') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + message_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='消息ID') + bot_self_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='收到消息的机器人ID') + event_entity_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='消息事件实体ID') + user_entity_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='发送对象实体ID') + received_time: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True, comment='收到消息事件的时间戳') + message_type: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='消息事件类型') + message_raw: Mapped[str] = mapped_column(String(4096), nullable=False, comment='原始消息数据') + message_text: Mapped[str] = mapped_column(String(4096), nullable=False, comment='经处理的消息文本内容') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: - return (f"HistoryOrm(time={self.time!r}, bot_self_id={self.bot_self_id!r}, " - f"parent_entity_id={self.parent_entity_id!r}, entity_id={self.entity_id!r}, " - f"event_type={self.event_type!r}, event_id={self.event_id!r}, " - f"raw_data={self.raw_data!r}, msg_data={self.msg_data!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'HistoryOrm(message_id={self.message_id!r}, bot_self_id={self.bot_self_id!r}, ' + f'event_entity_id={self.event_entity_id!r}, user_entity_id={self.user_entity_id!r}, ' + f'received_time={self.received_time!r}, message_type={self.message_type!r}, ' + f'message_raw={self.message_raw!r}, message_text={self.message_text!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class BotSelfOrm(Base): @@ -137,7 +153,7 @@ class BotSelfOrm(Base): bot_type: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='Bot类型, 具体使用的协议') bot_status: Mapped[int] = mapped_column(Integer, nullable=False, comment='Bot在线状态') bot_info: Mapped[str] = mapped_column(String(512), nullable=True, comment='Bot描述信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -146,8 +162,8 @@ class BotSelfOrm(Base): ) def __repr__(self) -> str: - return (f"BotSelfOrm(self_id={self.self_id!r}, bot_type={self.bot_type!r}, bot_status={self.bot_status!r}, " - f"bot_info={self.bot_info!r}, created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'BotSelfOrm(self_id={self.self_id!r}, bot_type={self.bot_type!r}, bot_status={self.bot_status!r}, ' + f'bot_info={self.bot_info!r}, created_at={self.created_at!r}, updated_at={self.updated_at!r})') class EntityOrm(Base): @@ -170,7 +186,7 @@ class EntityOrm(Base): parent_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='父实体id, qq号/群号等') entity_name: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='实体名称') entity_info: Mapped[str] = mapped_column(String(512), nullable=True, comment='实体描述信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -190,19 +206,15 @@ class EntityOrm(Base): entity_cooldown: Mapped[list['CoolDownOrm']] = relationship( 'CoolDownOrm', back_populates='cooldown_back_entity', cascade='all, delete-orphan', passive_deletes=True ) - entity_email_box_bind: Mapped[list['EmailBoxBindOrm']] = relationship( - 'EmailBoxBindOrm', - back_populates='email_box_bind_back_entity', cascade='all, delete-orphan', passive_deletes=True - ) entity_subscription: Mapped[list['SubscriptionOrm']] = relationship( 'SubscriptionOrm', back_populates='subscription_back_entity', cascade='all, delete-orphan', passive_deletes=True ) def __repr__(self) -> str: - return (f"EntityOrm(bot_index_id={self.bot_index_id!r}, entity_id={self.entity_id!r}, " - f"entity_type={self.entity_type!r}, parent_id={self.parent_id!r}, " - f"entity_name={self.entity_name!r}, entity_info={self.entity_info!r} " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'EntityOrm(bot_index_id={self.bot_index_id!r}, entity_id={self.entity_id!r}, ' + f'entity_type={self.entity_type!r}, parent_id={self.parent_id!r}, ' + f'entity_name={self.entity_name!r}, entity_info={self.entity_info!r} ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class FriendshipOrm(Base): @@ -225,7 +237,7 @@ class FriendshipOrm(Base): response_threshold: Mapped[float] = mapped_column( Float, nullable=False, comment='响应阈值, 控制对交互做出响应的概率或频率, 根据具体插件使用数值' ) - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -234,10 +246,10 @@ class FriendshipOrm(Base): ) def __repr__(self) -> str: - return (f"FriendshipOrm(entity_index_id={self.entity_index_id!r}, status={self.status!r}, " - f"mood={self.mood!r}, friendship={self.friendship!r}, energy={self.energy!r}, " - f"currency={self.currency!r}, response_threshold={self.response_threshold!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'FriendshipOrm(entity_index_id={self.entity_index_id!r}, status={self.status!r}, ' + f'mood={self.mood!r}, friendship={self.friendship!r}, energy={self.energy!r}, ' + f'currency={self.currency!r}, response_threshold={self.response_threshold!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class SignInOrm(Base): @@ -252,7 +264,7 @@ class SignInOrm(Base): entity_index_id: Mapped[int] = mapped_column(Integer, ForeignKey(EntityOrm.id, ondelete='CASCADE'), nullable=False) sign_in_date: Mapped[date] = mapped_column(Date, nullable=False, index=True, comment='签到日期') sign_in_info: Mapped[str] = mapped_column(String(64), nullable=True, comment='签到信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -261,8 +273,8 @@ class SignInOrm(Base): ) def __repr__(self) -> str: - return (f"SignInOrm(entity_index_id={self.entity_index_id!r}, sign_in_date={self.sign_in_date!r}, " - f"sign_in_info={self.sign_in_info!r}, created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'SignInOrm(entity_index_id={self.entity_index_id!r}, sign_in_date={self.sign_in_date!r}, ' + f'sign_in_info={self.sign_in_info!r}, created_at={self.created_at!r}, updated_at={self.updated_at!r})') class AuthSettingOrm(Base): @@ -281,8 +293,8 @@ class AuthSettingOrm(Base): available: Mapped[int] = mapped_column( Integer, nullable=False, index=True, comment='需求值, 0=deny/disable, 1=allow/enable, 1<=level' ) - value: Mapped[str] = mapped_column(String(8192), nullable=True, comment='若为插件配置项且对象具有的配置信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + value: Mapped[str] = mapped_column(String(4096), nullable=True, comment='若为插件配置项且对象具有的配置信息') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -291,9 +303,9 @@ class AuthSettingOrm(Base): ) def __repr__(self) -> str: - return (f"AuthSettingOrm(entity_index_id={self.entity_index_id!r}, module={self.module!r}, " - f"plugin={self.plugin!r}, node={self.node!r}, available={self.available!r}, value={self.value!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'AuthSettingOrm(entity_index_id={self.entity_index_id!r}, module={self.module!r}, ' + f'plugin={self.plugin!r}, node={self.node!r}, available={self.available!r}, value={self.value!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class CoolDownOrm(Base): @@ -310,7 +322,7 @@ class CoolDownOrm(Base): event: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='冷却事件, 用于唯一标识某个/类冷却') stop_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, index=True, comment='冷却结束时间') description: Mapped[str] = mapped_column(String(128), nullable=True, comment='事件描述') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -319,71 +331,9 @@ class CoolDownOrm(Base): ) def __repr__(self) -> str: - return (f"CoolDownOrm(entity_index_id={self.entity_index_id!r}, event={self.event!r}, " - f"stop_at={self.stop_at!r}, description={self.description!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") - - -class EmailBoxOrm(Base): - """邮箱表""" - __tablename__ = f'{database_config.db_prefix}email_box' - if database_config.table_args is not None: - __table_args__ = database_config.table_args - - id: Mapped[int] = mapped_column( - Integer, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True - ) - address: Mapped[str] = mapped_column(String(128), nullable=False, index=True, unique=True, comment='邮箱地址') - server_host: Mapped[str] = mapped_column(String(128), nullable=False, comment='IMAP/POP3服务器地址') - protocol: Mapped[str] = mapped_column(String(16), nullable=False, comment='协议') - port: Mapped[int] = mapped_column(Integer, nullable=False, comment='服务器端口') - password: Mapped[str] = mapped_column(String(255), nullable=False, comment='密码') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - - # 设置级联和关系加载 - email_box_email_box_bind: Mapped[list['EmailBoxBindOrm']] = relationship( - 'EmailBoxBindOrm', - back_populates='email_box_bind_back_email_box', cascade='all, delete-orphan', passive_deletes=True - ) - - def __repr__(self) -> str: - return (f"EmailBoxOrm(address={self.address!r}, server_host={self.server_host!r}, " - f"protocol={self.protocol!r}, port={self.port!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") - - -class EmailBoxBindOrm(Base): - """邮箱绑定表""" - __tablename__ = f'{database_config.db_prefix}email_box_bind' - if database_config.table_args is not None: - __table_args__ = database_config.table_args - - id: Mapped[int] = mapped_column( - Integer, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True - ) - email_box_index_id: Mapped[int] = mapped_column( - Integer, ForeignKey(EmailBoxOrm.id, ondelete='CASCADE'), nullable=False - ) - entity_index_id: Mapped[int] = mapped_column( - Integer, ForeignKey(EntityOrm.id, ondelete='CASCADE'), nullable=False - ) - bind_info: Mapped[str] = mapped_column(String(64), nullable=True, comment='邮箱绑定信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - - # 设置级联和关系加载 - email_box_bind_back_email_box: Mapped[EmailBoxOrm] = relationship( - EmailBoxOrm, back_populates='email_box_email_box_bind', lazy='joined', innerjoin=True - ) - email_box_bind_back_entity: Mapped[EntityOrm] = relationship( - EntityOrm, back_populates='entity_email_box_bind', lazy='joined', innerjoin=True - ) - - def __repr__(self) -> str: - return (f"EmailBoxBindOrm(email_box_index_id={self.email_box_index_id!r}, " - f"entity_index_id={self.entity_index_id!r}, bind_info={self.bind_info!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'CoolDownOrm(entity_index_id={self.entity_index_id!r}, event={self.event!r}, ' + f'stop_at={self.stop_at!r}, description={self.description!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class SubscriptionSourceOrm(Base): @@ -399,7 +349,7 @@ class SubscriptionSourceOrm(Base): sub_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='订阅id,直播间房间号/用户uid等') sub_user_name: Mapped[str] = mapped_column(String(64), nullable=False, comment='订阅用户的名称') sub_info: Mapped[str] = mapped_column(String(64), nullable=True, comment='订阅源信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -409,9 +359,9 @@ class SubscriptionSourceOrm(Base): ) def __repr__(self) -> str: - return (f"SubscriptionSourceOrm(sub_type={self.sub_type!r}, sub_id={self.sub_id!r}, " - f"sub_user_name={self.sub_user_name!r}, sub_info={self.sub_info!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'SubscriptionSourceOrm(sub_type={self.sub_type!r}, sub_id={self.sub_id!r}, ' + f'sub_user_name={self.sub_user_name!r}, sub_info={self.sub_info!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class SubscriptionOrm(Base): @@ -430,7 +380,7 @@ class SubscriptionOrm(Base): Integer, ForeignKey(EntityOrm.id, ondelete='CASCADE'), nullable=False ) sub_info: Mapped[str] = mapped_column(String(64), nullable=True, comment='订阅信息') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # 设置级联和关系加载 @@ -442,32 +392,33 @@ class SubscriptionOrm(Base): ) def __repr__(self) -> str: - return (f"SubscriptionOrm(sub_source_index_id={self.sub_source_index_id!r}, " - f"entity_index_id={self.entity_index_id!r}, sub_info={self.sub_info!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'SubscriptionOrm(sub_source_index_id={self.sub_source_index_id!r}, ' + f'entity_index_id={self.entity_index_id!r}, sub_info={self.sub_info!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') -class BiliDynamicOrm(Base): - """B站动态表""" - __tablename__ = f'{database_config.db_prefix}bili_dynamic' +class SocialMediaContentOrm(Base): + """社交媒体平台内容表""" + __tablename__ = f'{database_config.db_prefix}social_media_content' if database_config.table_args is not None: __table_args__ = database_config.table_args # 表结构 - id: Mapped[int] = mapped_column( - IndexInt, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True - ) - dynamic_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True, unique=True, comment='动态id') - dynamic_type: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment='动态类型') - uid: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment='用户uid') - content: Mapped[str] = mapped_column(String(4096), nullable=False, comment='动态内容') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + source: Mapped[str] = mapped_column(String(64), primary_key=True, nullable=False, index=True, comment='出处平台') + m_id: Mapped[str] = mapped_column(String(64), primary_key=True, nullable=False, index=True, comment='内容原始ID') + m_type: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='内容原始类型') + m_uid: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='内容发布者ID') + title: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment='内容标题') + content: Mapped[str] = mapped_column(String(4096), nullable=False, comment='内容文本') + ref_content: Mapped[str] = mapped_column(String(4096), nullable=True, comment='引用/转发内容文本') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: - return (f"BiliDynamicOrm(dynamic_id={self.dynamic_id!r}, dynamic_type={self.dynamic_type!r}, " - f"uid={self.uid!r}, content={self.content!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'SocialMediaContentOrm(source={self.source!r}, m_id={self.m_id!r}, ' + f'm_type={self.m_type!r}, m_uid={self.m_uid!r}, ' + f'title={self.title!r}, content={self.content!r}, ref_content={self.ref_content!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class ArtworkCollectionOrm(Base): @@ -477,18 +428,18 @@ class ArtworkCollectionOrm(Base): __table_args__ = database_config.table_args # 表结构 - id: Mapped[int] = mapped_column( - IndexInt, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True + origin: Mapped[str] = mapped_column( + String(64), primary_key=True, nullable=False, index=True, comment='作品来源/收录该作品的站点' + ) + aid: Mapped[str] = mapped_column( + String(64), primary_key=True, nullable=False, index=True, comment='作品原始ID/收录该作品的站点索引ID' ) - # 作品信息部分 - origin: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment='作品来源/收录该作品的站点') - aid: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment='作品id') title: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment='作品标题title') uid: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment='作者uid') uname: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment='作者名') # 分类分级信息 classification: Mapped[int] = mapped_column( - Integer, nullable=False, index=True, comment='标记标签, -1=未知, 0=未分类, 1=AI生成, 2=外部来源, 3=人工分类' + Integer, nullable=False, index=True, comment='标记标签, -2=忽略, -1=未知, 0=未分类, 1=AI生成, 2=外部来源, 3=人工分类' ) rating: Mapped[int] = mapped_column( Integer, nullable=False, index=True, comment='分级标签, -1=Unknown, 0=G, 1=S, 2=Q, 3=E' @@ -496,68 +447,20 @@ class ArtworkCollectionOrm(Base): # 作品图片信息 width: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment='原始图片宽度') height: Mapped[int] = mapped_column(Integer, nullable=False, index=True, comment='原始图片高度') - tags: Mapped[str] = mapped_column(String(2048), nullable=False, comment='作品标签') - description: Mapped[str] = mapped_column(String(2048), nullable=True, comment='作品描述') - source: Mapped[str] = mapped_column(String(512), nullable=False, comment='作品原始出处地址') - cover_page: Mapped[str] = mapped_column(String(512), nullable=False, comment='作品首页/封面原图链接') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - - def __repr__(self) -> str: - return (f"ArtworkCollectionOrm(artwork_id={self.aid!r}, title={self.title!r}, " - f"uid={self.uid!r}, uname={self.uname!r}, " - f"classification={self.classification!r}, rating={self.rating!r}, " - f"width={self.width!r}, height={self.height!r}, tags={self.tags!r}, " - f"source={self.source!r}, cover_page={self.cover_page!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") - - -class PixivisionArticleOrm(Base): - """Pixivision 表""" - __tablename__ = f'{database_config.db_prefix}pixivision_article' - if database_config.table_args is not None: - __table_args__ = database_config.table_args - - # 表结构 - id: Mapped[int] = mapped_column( - Integer, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True - ) - aid: Mapped[int] = mapped_column(Integer, nullable=False, index=True, unique=True, comment='aid') - title: Mapped[str] = mapped_column(String(255), nullable=False, comment='title') - description: Mapped[str] = mapped_column(String(1024), nullable=False, comment='description') - tags: Mapped[str] = mapped_column(String(1024), nullable=False, comment='tags') - artworks_id: Mapped[str] = mapped_column(String(1024), nullable=False, comment='article artwork_id') - url: Mapped[str] = mapped_column(String(1024), nullable=False, comment='url') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) - - def __repr__(self) -> str: - return (f"PixivisionArticleOrm(aid={self.aid!r}, title={self.title!r}, description={self.description!r}, " - f"tags={self.tags!r}, artworks_id={self.artworks_id!r}, url={self.url!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") - - -class WeiboDetailOrm(Base): - """微博内容表""" - __tablename__ = f'{database_config.db_prefix}weibo_detail' - if database_config.table_args is not None: - __table_args__ = database_config.table_args - - # 表结构 - id: Mapped[int] = mapped_column( - IndexInt, Sequence(f'{__tablename__}_id_seq'), primary_key=True, nullable=False, index=True, unique=True - ) - mid: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True, unique=True, comment='微博id') - uid: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True, comment='用户uid') - content: Mapped[str] = mapped_column(String(2048), nullable=False, comment='微博内容') - retweeted_content: Mapped[str] = mapped_column(String(2048), nullable=False, comment='转发的微博内容') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + tags: Mapped[str] = mapped_column(String(4096), nullable=False, comment='作品标签') + description: Mapped[str] = mapped_column(String(4096), nullable=True, comment='作品描述') + source: Mapped[str] = mapped_column(String(1024), nullable=False, comment='作品原始出处地址') + cover_page: Mapped[str] = mapped_column(String(1024), nullable=False, comment='作品首页/封面原图链接') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: - return (f"WeiboDetailOrm(mid={self.mid!r}, uid={self.uid!r}, " - f"content={self.content!r}, retweeted_content={self.retweeted_content!r}, " - f"created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'ArtworkCollectionOrm(origin={self.origin!r}, aid={self.aid!r}, title={self.title!r}, ' + f'uid={self.uid!r}, uname={self.uname!r}, ' + f'classification={self.classification!r}, rating={self.rating!r}, ' + f'width={self.width!r}, height={self.height!r}, tags={self.tags!r}, ' + f'description={self.description!r}, source={self.source!r}, cover_page={self.cover_page!r}, ' + f'created_at={self.created_at!r}, updated_at={self.updated_at!r})') class WordBankOrm(Base): @@ -574,17 +477,18 @@ class WordBankOrm(Base): reply_entity: Mapped[str] = mapped_column( String(64), nullable=False, index=True, comment='响应对象, 可为群号/用户qq/频道id等标识' ) - result_word: Mapped[str] = mapped_column(String(8192), nullable=False, comment='结果文本') - created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) + result_word: Mapped[str] = mapped_column(String(4096), nullable=False, comment='结果文本') + created_at: Mapped[datetime] = mapped_column(DateTime, nullable=True, default=datetime.now) updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) def __repr__(self) -> str: - return (f"WordBankOrm(key_word={self.key_word!r}, reply_entity={self.reply_entity!r}, " - f"result_word={self.result_word!r}, created_at={self.created_at!r}, updated_at={self.updated_at!r})") + return (f'WordBankOrm(key_word={self.key_word!r}, reply_entity={self.reply_entity!r}, ' + f'result_word={self.result_word!r}, created_at={self.created_at!r}, updated_at={self.updated_at!r})') __all__ = [ 'Base', + 'GlobalCacheOrm', 'SystemSettingOrm', 'PluginOrm', 'StatisticOrm', @@ -595,13 +499,9 @@ def __repr__(self) -> str: 'SignInOrm', 'AuthSettingOrm', 'CoolDownOrm', - 'EmailBoxOrm', - 'EmailBoxBindOrm', 'SubscriptionSourceOrm', 'SubscriptionOrm', - 'BiliDynamicOrm', + 'SocialMediaContentOrm', 'ArtworkCollectionOrm', - 'PixivisionArticleOrm', - 'WeiboDetailOrm', 'WordBankOrm', ] diff --git a/src/database/types.py b/src/database/types.py index 78f92772..c3e9092b 100644 --- a/src/database/types.py +++ b/src/database/types.py @@ -8,10 +8,9 @@ @Software : PyCharm """ -from sqlalchemy.dialects import postgresql, mysql, sqlite +from sqlalchemy.dialects import mysql, postgresql, sqlite from sqlalchemy.types import BigInteger - # BigInt 在 sqlite 中不能作为自增主键 # SQLAlchemy does not map BigInt to Int by default on the sqlite dialect. # https://stackoverflow.com/questions/18835740/does-bigint-auto-increment-work-for-sqlalchemy-with-sqlite/23175518#23175518 diff --git a/src/exception.py b/src/exception.py index 84dc2548..673a1736 100644 --- a/src/exception.py +++ b/src/exception.py @@ -2,27 +2,30 @@ @Author : Ailitonia @Date : 2022/12/01 19:55 @FileName : exception.py -@Project : nonebot2_miya +@Project : nonebot2_miya @Description : Omega exceptions @GitHub : https://github.com/Ailitonia -@Software : PyCharm +@Software : PyCharm """ +from typing import TYPE_CHECKING, final + +if TYPE_CHECKING: + from pathlib import PurePath + class OmegaException(Exception): """所有 Omega 异常的基类""" - -class DatabaseException(OmegaException): - """数据库异常""" + def __str__(self) -> str: + return self.__repr__() class LocalSourceException(OmegaException): """本地资源异常""" - -class WebSourceException(OmegaException): - """网络资源异常""" + def __init__(self, path: 'PurePath'): + self.path = path class PlatformException(OmegaException): @@ -33,11 +36,22 @@ class PluginException(OmegaException): """由插件自定义的异常""" +@final +class WebSourceException(OmegaException): + """网络资源异常""" + + def __init__(self, status_code: int, message: str): + self.status_code = status_code + self.message = message + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(status_code={self.status_code!r}, message={self.message})' + + __all__ = [ 'OmegaException', - 'DatabaseException', 'LocalSourceException', - 'WebSourceException', 'PlatformException', 'PluginException', + 'WebSourceException', ] diff --git a/src/params/__init__.py b/src/params/__init__.py index cfbad6e0..eaf14f94 100644 --- a/src/params/__init__.py +++ b/src/params/__init__.py @@ -9,13 +9,14 @@ """ from typing import Any + +from nonebot.internal.adapter.message import Message +from nonebot.matcher import Matcher from nonebot.params import Depends from nonebot.typing import T_State -from nonebot.matcher import Matcher -from nonebot.internal.adapter.message import Message -class StatePlainTextInner(object): +class StatePlainTextInner: """State 中的纯文本值""" def __init__(self, key: Any): diff --git a/src/params/handler.py b/src/params/handler.py index 8c9fa1e8..e7679b95 100644 --- a/src/params/handler.py +++ b/src/params/handler.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from typing import Annotated, Any, Optional +from typing import Annotated, Any from nonebot.exception import ParserExit from nonebot.internal.adapter import Message @@ -107,9 +107,9 @@ async def handle_parse_command_message_arg(matcher: Matcher, cmd_arg: Annotated[ def get_set_default_state_handler( key: str, - value: Optional[Any] = None, + value: Any | None = None, *, - extra_data: Optional[dict[str, Optional[Any]]] = None + extra_data: dict[str, Any | None] | None = None ) -> T_Handler: """构造设置 State 默认值的 handler""" diff --git a/src/params/permission.py b/src/params/permission.py index 69cda57e..f66f1fb8 100644 --- a/src/params/permission.py +++ b/src/params/permission.py @@ -8,24 +8,16 @@ @Software : PyCharm """ -from nonebot.permission import Permission, SUPERUSER - -from nonebot.adapters.onebot.v11.permission import ( - GROUP_ADMIN as ONEBOT_V11_GROUP_ADMIN, - GROUP_OWNER as ONEBOT_V11_GROUP_OWNER, - PRIVATE as ONEBOT_V11_PRIVATE -) -from nonebot.adapters.qq.permission import ( - GUILD_CHANNEL_ADMIN as QQ_GUILD_CHANNEL_ADMIN, - GUILD_ADMIN as QQ_GUILD_ADMIN, - GUILD_OWNER as QQ_GUILD_OWNER -) -from nonebot.adapters.telegram.permission import ( - GROUP_ADMIN as TELEGRAM_GROUP_ADMIN, - GROUP_CREATOR as TELEGRAM_GROUP_CREATOR, - PRIVATE as TELEGRAM_PRIVATE -) - +from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN as ONEBOT_V11_GROUP_ADMIN +from nonebot.adapters.onebot.v11.permission import GROUP_OWNER as ONEBOT_V11_GROUP_OWNER +from nonebot.adapters.onebot.v11.permission import PRIVATE as ONEBOT_V11_PRIVATE +from nonebot.adapters.qq.permission import GUILD_ADMIN as QQ_GUILD_ADMIN +from nonebot.adapters.qq.permission import GUILD_CHANNEL_ADMIN as QQ_GUILD_CHANNEL_ADMIN +from nonebot.adapters.qq.permission import GUILD_OWNER as QQ_GUILD_OWNER +from nonebot.adapters.telegram.permission import GROUP_ADMIN as TELEGRAM_GROUP_ADMIN +from nonebot.adapters.telegram.permission import GROUP_CREATOR as TELEGRAM_GROUP_CREATOR +from nonebot.adapters.telegram.permission import PRIVATE as TELEGRAM_PRIVATE +from nonebot.permission import SUPERUSER, Permission IS_ADMIN: Permission = ( SUPERUSER diff --git a/src/params/rule.py b/src/params/rule.py index d0e5d2a2..c49d27fb 100644 --- a/src/params/rule.py +++ b/src/params/rule.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot.adapters import Bot as BaseBot, Event as BaseEvent +from nonebot.internal.adapter import Bot as BaseBot +from nonebot.internal.adapter import Event as BaseEvent from nonebot.rule import Rule from src.database import begin_db_session diff --git a/src/plugins/bilibili_account_manager/__init__.py b/src/plugins/bilibili_account_manager/__init__.py index 5decef5b..18897ed1 100644 --- a/src/plugins/bilibili_account_manager/__init__.py +++ b/src/plugins/bilibili_account_manager/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='bilibiliAccountManager', description='【B站账户管理插件】\n' @@ -22,6 +21,6 @@ from . import command as command - +from . import scheduled_tasks as tasks __all__ = [] diff --git a/src/plugins/bilibili_account_manager/command.py b/src/plugins/bilibili_account_manager/command.py index d3278813..6edec33e 100644 --- a/src/plugins/bilibili_account_manager/command.py +++ b/src/plugins/bilibili_account_manager/command.py @@ -16,7 +16,8 @@ from nonebot.plugin import on_command from nonebot.rule import to_me -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from src.utils.bilibili_api import BilibiliCredential diff --git a/src/plugins/bilibili_account_manager/scheduled_tasks.py b/src/plugins/bilibili_account_manager/scheduled_tasks.py new file mode 100644 index 00000000..d0efe21e --- /dev/null +++ b/src/plugins/bilibili_account_manager/scheduled_tasks.py @@ -0,0 +1,68 @@ +""" +@Author : Ailitonia +@Date : 2024/11/15 14:42:29 +@FileName : scheduled_tasks.py +@Project : omega-miya +@Description : 账户鉴权信息更新定时任务 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot import get_driver, logger + +from src.service import scheduler +from src.utils.bilibili_api import BilibiliCredential + + +async def _refresh_bilibili_login_status() -> None: + """检查 bilibili 登录状态, 根据需要刷新 Cookies""" + logger.opt(colors=True).debug('Bilibili | 开始检查用户 Cookies 状态') + bc = BilibiliCredential() + + is_valid = await bc.check_valid() + if not is_valid: + logger.opt(colors=True).warning('Bilibili | 用户 Cookies 未配置或验证失败, 部分功能可能不可用') + return + + need_refresh = await bc.check_need_refresh() + if need_refresh: + logger.opt(colors=True).warning('Bilibili | 用户登录凭据需要刷新, 正在尝试刷新中') + refresh_result = await bc.refresh_cookies() + if refresh_result: + logger.opt(colors=True).success('Bilibili | 用户登录凭据刷新成功') + else: + logger.opt(colors=True).error('Bilibili | 用户登录凭据刷新失败') + else: + logger.opt(colors=True).debug('Bilibili | 用户 Cookies 有效, 无需刷新') + await bc.update_buvid_cookies() + await bc.update_ticket_wbi_cookies() + await bc.save_cookies_to_db() + logger.opt(colors=True).success('Bilibili | 用户接口鉴权缓存刷新成功') + + +@get_driver().on_startup +async def _load_and_refresh_bilibili_login_status() -> None: + try: + await BilibiliCredential.load_cookies_from_db() + await _refresh_bilibili_login_status() + except Exception as e: + logger.opt(colors=True).error(f'Bilibili | 用户 Cookies 刷新失败, 请尝试重新登录, {e}') + + +@scheduler.scheduled_job( + 'cron', + hour='*/8', + minute='23', + second='23', + id='bilibili_login_status_refresh_monitor', + coalesce=True, + misfire_grace_time=120 +) +async def _bilibili_login_status_refresh_monitor() -> None: + try: + await _refresh_bilibili_login_status() + except Exception as e: + logger.opt(colors=True).error(f'Bilibili | 用户 Cookies 刷新失败, 请尝试重新登录, {e}') + + +__all__ = [] diff --git a/src/plugins/bilibili_dynamic_monitor/__init__.py b/src/plugins/bilibili_dynamic_monitor/__init__.py index bb8040d3..a6f331b0 100644 --- a/src/plugins/bilibili_dynamic_monitor/__init__.py +++ b/src/plugins/bilibili_dynamic_monitor/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='B站动态订阅', description='【B站动态订阅插件】\n' @@ -26,5 +25,4 @@ from . import command as command from . import monitor as monitor - __all__ = [] diff --git a/src/plugins/bilibili_dynamic_monitor/command.py b/src/plugins/bilibili_dynamic_monitor/command.py index c9bc723c..456ed5a1 100644 --- a/src/plugins/bilibili_dynamic_monitor/command.py +++ b/src/plugins/bilibili_dynamic_monitor/command.py @@ -17,7 +17,8 @@ from src.exception import WebSourceException from src.params.handler import get_command_str_single_arg_parser_handler, get_set_default_state_handler from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from src.utils.bilibili_api import BilibiliUser from .consts import NOTICE_AT_ALL from .helpers import add_dynamic_sub, delete_dynamic_sub, query_entity_subscribed_dynamic_sub_source @@ -55,16 +56,15 @@ async def handle_add_subscription( elif ensure in ['是', '确认', 'Yes', 'yes', 'Y', 'y']: await interface.send_reply('正在更新Bilibili用户动态订阅信息, 请稍候') - user = BilibiliUser(uid=int(uid)) scheduler.pause() # 暂停计划任务避免中途检查更新 try: - await add_dynamic_sub(interface=interface, bili_user=user) + await add_dynamic_sub(interface=interface, user_id=uid) await interface.entity.commit_session() - logger.success(f'{interface.entity}订阅用户{user}动态成功') - msg = f'订阅用户{uid}动态成功' + logger.success(f'{interface.entity}订阅用户{uid!r}动态成功') + msg = f'订阅用户{uid!r}动态成功' except Exception as e: - logger.error(f'{interface.entity}订阅用户{user}动态失败, {e!r}') - msg = f'订阅用户{user}动态失败, 可能是网络异常或发生了意外的错误, 请稍后再试或联系管理员处理' + logger.error(f'{interface.entity}订阅用户{uid!r}动态失败, {e!r}') + msg = '订阅用户动态失败, 可能是网络异常或发生了意外的错误, 请稍后再试或联系管理员处理' scheduler.resume() await interface.finish_reply(msg) else: @@ -78,12 +78,11 @@ async def handle_add_subscription( await interface.finish_reply('非有效的用户UID, 用户UID应当为纯数字, 已取消操作') try: - user = BilibiliUser(uid=int(uid)) - user_data = await user.query_user_data() - if user_data.error or user_data.data is None: - raise WebSourceException(f'query {user} data failed, {user_data.message}') + user_data = await BilibiliUser.query_user_info(mid=uid) + if user_data.error: + raise WebSourceException(404, f'query user({uid}) info failed, {user_data.message}') except Exception as e: - logger.error(f'获取用户{uid}信息失败, {e!r}') + logger.error(f'获取用户{uid!r}信息失败, {e!r}') await interface.finish_reply('获取用户信息失败, 可能是网络原因或没有这个用户, 请稍后再试') ensure_msg = f'即将订阅Bilibili用户【{user_data.data.name}】的动态\n\n确认吗?\n【是/否】' @@ -108,13 +107,13 @@ async def handle_del_subscription( pass elif ensure in ['是', '确认', 'Yes', 'yes', 'Y', 'y']: try: - await delete_dynamic_sub(interface=interface, uid=int(uid)) + await delete_dynamic_sub(interface=interface, user_id=uid) await interface.entity.commit_session() - logger.success(f'{interface.entity}取消订阅用户(uid={uid})动态成功') - msg = f'取消订阅用户{uid}动态成功' + logger.success(f'{interface.entity}取消订阅用户{uid!r}动态成功') + msg = f'取消订阅用户{uid!r}动态成功' except Exception as e: - logger.error(f'{interface.entity}取消订阅用户(uid={uid})动态失败, {e!r}') - msg = f'取消订阅用户{uid}动态失败, 请稍后再试或联系管理员处理' + logger.error(f'{interface.entity}取消订阅用户{uid!r}动态失败, {e!r}') + msg = '取消订阅用户动态失败, 请稍后再试或联系管理员处理' await interface.finish_reply(msg) else: await interface.finish_reply('已取消操作') diff --git a/src/plugins/bilibili_dynamic_monitor/consts.py b/src/plugins/bilibili_dynamic_monitor/consts.py index 876b7021..5a838950 100644 --- a/src/plugins/bilibili_dynamic_monitor/consts.py +++ b/src/plugins/bilibili_dynamic_monitor/consts.py @@ -11,13 +11,30 @@ from typing import Literal from src.database.internal.subscription_source import SubscriptionSourceType +from src.utils.bilibili_api.future.models.dynamic import DynamicType - +# 订阅相关 BILI_DYNAMIC_SUB_TYPE: str = SubscriptionSourceType.bili_dynamic.value """b站动态订阅类型""" NOTICE_AT_ALL: Literal['notice_at_all'] = 'notice_at_all' """允许通知时@所有人的权限节点""" +IGNORED_DYNAMIC_TYPES: list[DynamicType] = [ + DynamicType.live_rcmd, + DynamicType.ad, + DynamicType.applet, +] +"""在通知时忽略的动态类型""" + + +# 计划任务相关 +MONITOR_JOB_ID: Literal['bili_dynamic_update_monitor'] = 'bili_dynamic_update_monitor' +"""动态检查的定时任务 ID""" +AVERAGE_CHECKING_PER_MINUTE: int = 12 +"""每分钟检查动态的用户数(数值大小影响风控概率, 请谨慎调整)""" +CHECKING_DELAY_UNDER_RATE_LIMITING: int = 6 +"""被风控时的延迟间隔""" +# 插件相关 MODULE_NAME = str(__name__).rsplit('.', maxsplit=1)[0] PLUGIN_NAME = MODULE_NAME.rsplit('.', maxsplit=1)[-1] @@ -25,6 +42,10 @@ __all__ = [ 'BILI_DYNAMIC_SUB_TYPE', 'NOTICE_AT_ALL', + 'IGNORED_DYNAMIC_TYPES', + 'MONITOR_JOB_ID', + 'AVERAGE_CHECKING_PER_MINUTE', + 'CHECKING_DELAY_UNDER_RATE_LIMITING', 'MODULE_NAME', 'PLUGIN_NAME', ] diff --git a/src/plugins/bilibili_dynamic_monitor/helpers.py b/src/plugins/bilibili_dynamic_monitor/helpers.py index 7e638f62..72445977 100644 --- a/src/plugins/bilibili_dynamic_monitor/helpers.py +++ b/src/plugins/bilibili_dynamic_monitor/helpers.py @@ -8,93 +8,105 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING from nonebot import logger from nonebot.exception import ActionFailed -from sqlalchemy.exc import NoResultFound -from src.database import BiliDynamicDAL, begin_db_session +from src.database import SocialMediaContentDAL, begin_db_session from src.exception import WebSourceException from src.service import ( - OmegaMatcherInterface as OmMI, - OmegaEntityInterface as OmEI, OmegaEntity, OmegaMessage, OmegaMessageSegment, ) +from src.service import ( + OmegaEntityInterface as OmEI, +) +from src.service import ( + OmegaMatcherInterface as OmMI, +) from src.service.omega_base.internal import OmegaBiliDynamicSubSource +from src.utils import run_async_delay, semaphore_gather from src.utils.bilibili_api import BilibiliDynamic, BilibiliUser -from src.utils.process_utils import run_async_delay, semaphore_gather -from .consts import BILI_DYNAMIC_SUB_TYPE, NOTICE_AT_ALL, MODULE_NAME, PLUGIN_NAME +from .consts import ( + BILI_DYNAMIC_SUB_TYPE, + IGNORED_DYNAMIC_TYPES, + MODULE_NAME, + NOTICE_AT_ALL, + PLUGIN_NAME, +) if TYPE_CHECKING: from src.database.internal.entity import Entity from src.database.internal.subscription_source import SubscriptionSource - from src.utils.bilibili_api.model import BilibiliDynamicCard + from src.utils.bilibili_api.future.models.dynamic import DynItem -async def _query_dynamic_sub_source(uid: int) -> "SubscriptionSource": +async def _query_dynamic_sub_source(user_id: int | str) -> 'SubscriptionSource': """从数据库查询动态订阅源""" async with begin_db_session() as session: - source_res = await OmegaBiliDynamicSubSource(session=session, uid=uid).query_subscription_source() + source_res = await OmegaBiliDynamicSubSource(session=session, uid=user_id).query_subscription_source() return source_res -async def _check_new_dynamic(cards: Sequence["BilibiliDynamicCard"]) -> list["BilibiliDynamicCard"]: +async def _check_new_dynamic(items: Sequence['DynItem']) -> list['DynItem']: """检查新的动态(数据库中没有的)""" async with begin_db_session() as session: - all_ids = [x.desc.dynamic_id for x in cards] - new_ids = await BiliDynamicDAL(session=session).query_not_exists_ids(dynamic_ids=all_ids) - return [x for x in cards if x.desc.dynamic_id in new_ids] + new_ids = await SocialMediaContentDAL(session=session).query_source_not_exists_mids( + source=BILI_DYNAMIC_SUB_TYPE, mids=[x.id_str for x in items] + ) + return [x for x in items if x.id_str in new_ids] -async def _add_upgrade_dynamic_content(card: "BilibiliDynamicCard") -> None: +async def _add_upgrade_dynamic_content(item: 'DynItem') -> None: """在数据库中添加动态信息""" async with begin_db_session() as session: - dal = BiliDynamicDAL(session=session) - try: - dynamic = await dal.query_unique(dynamic_id=card.desc.dynamic_id) - await dal.update(id_=dynamic.id, content=card.card.output_std_model().content) - except NoResultFound: - await dal.add(dynamic_id=card.desc.dynamic_id, dynamic_type=card.desc.type, - uid=card.desc.uid, content=card.card.output_std_model().content) + await SocialMediaContentDAL(session=session).upsert( + source=BILI_DYNAMIC_SUB_TYPE, + m_id=str(item.id_str), + m_type=str(item.type), + m_uid=str(item.modules.module_author.mid), + title=f'{item.modules.module_author.name}的动态', + content=item.dyn_text, + ) -async def _add_user_new_dynamic_content(bili_user: BilibiliUser) -> None: +async def _add_user_new_dynamic_content(user_id: int | str) -> None: """在数据库中更新目标用户的所有动态(仅新增不更新)""" - dynamic_data = await bili_user.query_dynamics() - new_dynamic_card = await _check_new_dynamic(cards=dynamic_data.all_cards) + dynamics = await BilibiliDynamic.query_user_space_dynamics(host_mid=user_id) + new_dynamic_item = await _check_new_dynamic(items=dynamics.data.items) - tasks = [_add_upgrade_dynamic_content(card=card) for card in new_dynamic_card] + tasks = [_add_upgrade_dynamic_content(item=item) for item in new_dynamic_item] await semaphore_gather(tasks=tasks, semaphore_num=10, return_exceptions=False) -async def _add_upgrade_dynamic_sub_source(bili_user: BilibiliUser) -> "SubscriptionSource": +async def _add_upgrade_dynamic_sub_source(user_id: int | str) -> 'SubscriptionSource': """在数据库中新更新动态订阅源""" - user_data = await bili_user.query_user_data() + user_data = await BilibiliUser.query_user_info(mid=user_id) if user_data.error: - raise WebSourceException(f'query {bili_user} data failed, {user_data.message}') + raise WebSourceException(404, f'query user({user_id}) info failed, {user_data.message}') - await _add_user_new_dynamic_content(bili_user=bili_user) + await _add_user_new_dynamic_content(user_id=user_id) async with begin_db_session() as session: - sub_source = OmegaBiliDynamicSubSource(session=session, uid=bili_user.uid) + sub_source = OmegaBiliDynamicSubSource(session=session, uid=user_id) await sub_source.add_upgrade(sub_user_name=user_data.uname, sub_info='Bilibili用户动态订阅') source_res = await sub_source.query_subscription_source() return source_res -async def add_dynamic_sub(interface: OmMI, bili_user: BilibiliUser) -> None: +async def add_dynamic_sub(interface: OmMI, user_id: int | str) -> None: """为目标对象添加 Bilibili 用户动态订阅""" - source_res = await _add_upgrade_dynamic_sub_source(bili_user=bili_user) + source_res = await _add_upgrade_dynamic_sub_source(user_id=user_id) await interface.entity.add_subscription(subscription_source=source_res, - sub_info=f'Bilibili用户动态订阅(uid={bili_user.uid})') + sub_info=f'Bilibili用户动态订阅(uid={user_id})') -async def delete_dynamic_sub(interface: OmMI, uid: int) -> None: +async def delete_dynamic_sub(interface: OmMI, user_id: int | str) -> None: """为目标对象删除 Bilibili 用户动态订阅""" - source_res = await _query_dynamic_sub_source(uid=uid) + source_res = await _query_dynamic_sub_source(user_id=user_id) await interface.entity.delete_subscription(subscription_source=source_res) @@ -116,27 +128,27 @@ async def query_all_subscribed_dynamic_sub_source() -> list[int]: return [int(x.sub_id) for x in source_res] -async def query_subscribed_entity_by_bili_user(uid: int) -> list["Entity"]: +async def query_subscribed_entity_by_bili_user(user_id: int | str) -> list['Entity']: """根据 Bilibili 用户查询已经订阅了这个用户的内部 Entity 对象""" async with begin_db_session() as session: - sub_source = OmegaBiliDynamicSubSource(session=session, uid=uid) + sub_source = OmegaBiliDynamicSubSource(session=session, uid=user_id) subscribed_entity = await sub_source.query_all_entity_subscribed() return subscribed_entity -async def _format_dynamic_update_message(dynamic: "BilibiliDynamicCard") -> str | OmegaMessage: +async def _format_dynamic_update_message(item: 'DynItem') -> str | OmegaMessage: """处理动态为消息""" - send_message = f'【bilibili】{dynamic.output_text}\n' + send_message = f'【bilibili】{item.dyn_text}\n' # 下载动态中包含的图片 - if dynamic.output_img_urls: - img_download_tasks = [BilibiliDynamic.download_resource(url=url) for url in dynamic.output_img_urls] + if item.dyn_image_urls: + img_download_tasks = [BilibiliDynamic.download_resource(url=url) for url in item.dyn_image_urls] img_download_res = await semaphore_gather(tasks=img_download_tasks, semaphore_num=9, filter_exception=True) for img in img_download_res: send_message += OmegaMessageSegment.image(url=img.path) send_message += '\n' - send_message += f'\n动态链接: https://t.bilibili.com/{dynamic.desc.dynamic_id}' + send_message += f'\n动态链接: https://t.bilibili.com/{item.id_str}' return send_message @@ -149,7 +161,7 @@ async def _has_notice_at_all_node(entity: OmegaEntity) -> bool: return False -async def _msg_sender(entity: "Entity", message: str | OmegaMessage) -> None: +async def _msg_sender(entity: 'Entity', message: str | OmegaMessage) -> None: """向 entity 发送动态消息""" try: async with begin_db_session() as session: @@ -166,31 +178,36 @@ async def _msg_sender(entity: "Entity", message: str | OmegaMessage) -> None: logger.error(f'BilibiliDynamicMonitor | Sending message to {entity} failed, {e!r}') -@run_async_delay(delay_time=5) -async def bili_dynamic_monitor_main(uid: int) -> None: +@run_async_delay(delay_time=8, random_sigma=4) +async def bili_dynamic_monitor_main(user_id: int | str) -> None: """向已订阅的用户或群发送 Bilibili 用户动态更新""" - bili_user = BilibiliUser(uid=uid) - logger.debug(f'BilibiliDynamicMonitor | Start checking {bili_user} new dynamics') - dynamic_data = await bili_user.query_dynamics() - - new_dynamic_card = await _check_new_dynamic(cards=dynamic_data.all_cards) - if new_dynamic_card: - logger.info(f'BilibiliDynamicMonitor | Confirmed {bili_user} ' - f'new dynamic: {", ".join(str(x.desc.dynamic_id) for x in new_dynamic_card)}') + logger.debug(f'BilibiliDynamicMonitor | Start checking user({user_id}) new dynamics') + dynamics = await BilibiliDynamic.query_user_space_dynamics(host_mid=user_id) + + new_dynamic_item = await _check_new_dynamic(items=dynamics.data.items) + if new_dynamic_item: + logger.info( + f'BilibiliDynamicMonitor | Confirmed user({user_id}) ' + f'new dynamic: {", ".join(x.id_str for x in new_dynamic_item)}' + ) else: - logger.debug(f'BilibiliDynamicMonitor | {bili_user} has not new dynamics') + logger.debug(f'BilibiliDynamicMonitor | user({user_id}) has not new dynamics') return # 获取动态消息内容 - format_msg_tasks = [_format_dynamic_update_message(dynamic=card) for card in new_dynamic_card] + format_msg_tasks = [ + _format_dynamic_update_message(item=item) + for item in new_dynamic_item + if item.type not in IGNORED_DYNAMIC_TYPES + ] send_messages = await semaphore_gather(tasks=format_msg_tasks, semaphore_num=3, return_exceptions=False) # 数据库中插入新动态信息 - add_artwork_tasks = [_add_upgrade_dynamic_content(card=card) for card in new_dynamic_card] + add_artwork_tasks = [_add_upgrade_dynamic_content(item=item) for item in new_dynamic_item] await semaphore_gather(tasks=add_artwork_tasks, semaphore_num=10, return_exceptions=False) # 向订阅者发送新动态信息 - subscribed_entity = await query_subscribed_entity_by_bili_user(uid=bili_user.uid) + subscribed_entity = await query_subscribed_entity_by_bili_user(user_id=user_id) send_tasks = [ _msg_sender(entity=entity, message=send_msg) for entity in subscribed_entity for send_msg in send_messages diff --git a/src/plugins/bilibili_dynamic_monitor/monitor.py b/src/plugins/bilibili_dynamic_monitor/monitor.py index 257af28e..fb5ac6e7 100644 --- a/src/plugins/bilibili_dynamic_monitor/monitor.py +++ b/src/plugins/bilibili_dynamic_monitor/monitor.py @@ -8,50 +8,71 @@ @Software : PyCharm """ -from typing import Literal +from asyncio import Queue as AsyncQueue +from datetime import datetime, timedelta from nonebot.log import logger -from src.exception import WebSourceException -from src.service import scheduler, reschedule_job -from src.utils.process_utils import semaphore_gather -from .helpers import query_all_subscribed_dynamic_sub_source, bili_dynamic_monitor_main +from src.exception import PluginException, WebSourceException +from src.service import scheduler +from src.utils import semaphore_gather +from .consts import AVERAGE_CHECKING_PER_MINUTE, CHECKING_DELAY_UNDER_RATE_LIMITING, MONITOR_JOB_ID +from .helpers import bili_dynamic_monitor_main, query_all_subscribed_dynamic_sub_source -_MONITOR_JOB_ID: Literal['bili_dynamic_update_monitor'] = 'bili_dynamic_update_monitor' -"""动态检查的定时任务 ID""" -_AVERAGE_CHECKING_PER_MINUTE: float = 7.5 -"""期望平均每分钟检查动态的用户数(数值大小影响风控概率, 请谨慎调整)""" -_CHECKING_DELAY_UNDER_RATE_LIMITING: int = 20 -"""被风控时的延迟间隔""" +_UID_CHECKING_QUEUE: AsyncQueue[int] = AsyncQueue() +"""用于动态更新监控使用的已订阅用户 UID 队列""" -async def bili_dynamic_update_monitor() -> None: - """Bilibili 用户动态订阅 动态更新监控""" - logger.debug('BilibiliDynamicMonitor | Started checking bilibili user dynamics update') +class NullBiliDynamicSubscribedSource(PluginException): + """当前没有任何已订阅的 bilibili 用户动态订阅源""" - # 获取所有已添加的 Bilibili 用户动态订阅源 + +async def _reload_uid_queue() -> None: + """重新填充动态已订阅用户 UID 待检查队列""" subscribed_uid = await query_all_subscribed_dynamic_sub_source() if not subscribed_uid: - logger.debug('BilibiliDynamicMonitor | None of bilibili dynamic subscription, ignored') - return + logger.debug('BilibiliDynamicMonitor | Null of bilibili dynamic subscription source, ignored') + raise NullBiliDynamicSubscribedSource + + for uid in subscribed_uid: + await _UID_CHECKING_QUEUE.put(uid) + + logger.debug( + f'BilibiliDynamicMonitor | Reloaded UID queue with {subscribed_uid}, remaining: {_UID_CHECKING_QUEUE.qsize()}' + ) + - # 避免风控, 根据订阅的用户数动态调整检查时间间隔 - monitor_job = scheduler.get_job(job_id=_MONITOR_JOB_ID) - if monitor_job is not None: - interval_min = int(len(subscribed_uid) // _AVERAGE_CHECKING_PER_MINUTE) - interval_min = interval_min if interval_min > 2 else 2 - reschedule_job(job=monitor_job, trigger_mode='interval', minutes=interval_min) +async def _get_next_check_uid(num: int) -> list[int]: + """从待检查队列中获取接下来检查的用户 UID""" + logger.debug(f'BilibiliDynamicMonitor | UID queue remaining: {_UID_CHECKING_QUEUE.qsize()}') + if _UID_CHECKING_QUEUE.empty(): + await _reload_uid_queue() + + return [await _UID_CHECKING_QUEUE.get() for _ in range(min(num, _UID_CHECKING_QUEUE.qsize()))] + + +async def bili_dynamic_update_monitor() -> None: + """Bilibili 用户动态订阅 动态更新监控""" + logger.debug('BilibiliDynamicMonitor | Started checking bilibili user dynamics update from queue') + + try: + subscribed_uid = await _get_next_check_uid(num=AVERAGE_CHECKING_PER_MINUTE) + [_UID_CHECKING_QUEUE.task_done() for _ in range(len(subscribed_uid))] + except NullBiliDynamicSubscribedSource: + return # 检查新作品并发送消息 - tasks = [bili_dynamic_monitor_main(uid=uid) for uid in subscribed_uid] - sent_result = await semaphore_gather(tasks=tasks, semaphore_num=5, return_exceptions=True, filter_exception=False) + tasks = [bili_dynamic_monitor_main(user_id=uid) for uid in subscribed_uid] + sent_result = await semaphore_gather(tasks=tasks, semaphore_num=AVERAGE_CHECKING_PER_MINUTE) if any(isinstance(e, WebSourceException) for e in sent_result): # 如果 API 异常则大概率被风控, 推迟下一次检查 + monitor_job = scheduler.get_job(job_id=MONITOR_JOB_ID) if monitor_job is not None: - reschedule_job(job=monitor_job, trigger_mode='interval', minutes=_CHECKING_DELAY_UNDER_RATE_LIMITING) - logger.warning('BilibiliDynamicMonitor | Fetch bilibili dynamic data failed, ' - f'maybe under the rate limiting, ' - f'delay the next checking until after {_CHECKING_DELAY_UNDER_RATE_LIMITING} minutes') + monitor_job.modify(next_run_time=(datetime.now() + timedelta(minutes=CHECKING_DELAY_UNDER_RATE_LIMITING))) + logger.warning( + 'BilibiliDynamicMonitor | Fetch bilibili dynamic data failed, maybe under the rate limiting, ' + f'delay the next checking until after {CHECKING_DELAY_UNDER_RATE_LIMITING} minutes, {monitor_job}' + ) logger.debug('BilibiliDynamicMonitor | Bilibili user dynamic update checking completed') @@ -70,7 +91,7 @@ async def bili_dynamic_update_monitor() -> None: # start_date=None, # end_date=None, # timezone=None, - id=_MONITOR_JOB_ID, + id=MONITOR_JOB_ID, coalesce=True, misfire_grace_time=120 ) diff --git a/src/plugins/bilibili_live_monitor/__init__.py b/src/plugins/bilibili_live_monitor/__init__.py index 68ba304a..4e882727 100644 --- a/src/plugins/bilibili_live_monitor/__init__.py +++ b/src/plugins/bilibili_live_monitor/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='B站直播间订阅', description='【B站直播间订阅插件】\n' @@ -27,5 +26,4 @@ from . import command as command from . import monitor as monitor - __all__ = [] diff --git a/src/plugins/bilibili_live_monitor/command.py b/src/plugins/bilibili_live_monitor/command.py index 26082b07..c7e1e9a5 100644 --- a/src/plugins/bilibili_live_monitor/command.py +++ b/src/plugins/bilibili_live_monitor/command.py @@ -14,12 +14,12 @@ from nonebot.params import ArgStr, Depends from nonebot.plugin import CommandGroup -from src.exception import WebSourceException from src.params.handler import get_command_str_single_arg_parser_handler, get_set_default_state_handler from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state -from src.utils.bilibili_api import BilibiliLiveRoom +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from .consts import NOTICE_AT_ALL +from .data_source import query_live_room_status from .helpers import add_live_room_sub, delete_live_room_sub, query_subscribed_live_room_sub_source from .monitor import scheduler @@ -55,16 +55,15 @@ async def handle_add_subscription( elif ensure in ['是', '确认', 'Yes', 'yes', 'Y', 'y']: await interface.send_reply('正在更新Bilibili直播间订阅信息, 请稍候') - room = BilibiliLiveRoom(room_id=int(room_id)) scheduler.pause() # 暂停计划任务避免中途检查更新 try: - await add_live_room_sub(interface=interface, live_room=room) + await add_live_room_sub(interface=interface, room_id=room_id) await interface.entity.commit_session() - logger.success(f'{interface.entity}订阅直播间{room}成功') - msg = f'订阅直播间{room_id}成功' + logger.success(f'{interface.entity}订阅直播间{room_id!r}成功') + msg = f'订阅直播间{room_id!r}成功' except Exception as e: - logger.error(f'{interface.entity}订阅直播间{room}失败, {e!r}') - msg = f'订阅直播间{room_id}失败, 可能是网络异常或发生了意外的错误, 请稍后再试或联系管理员处理' + logger.error(f'{interface.entity}订阅直播间{room_id!r}失败, {e!r}') + msg = '订阅直播间失败, 可能是网络异常或发生了意外的错误, 请稍后再试或联系管理员处理' scheduler.resume() await interface.finish_reply(msg) else: @@ -78,25 +77,16 @@ async def handle_add_subscription( await interface.finish_reply('非有效的直播间房间号, 直播间房间号应当为纯数字, 已取消操作') try: - room = BilibiliLiveRoom(room_id=int(room_id)) - live_room_data = await room.query_live_room_data() - if live_room_data.error or live_room_data.data is None: - raise WebSourceException(f'query {room} data failed, {live_room_data.message}') - - live_room_user_data = await room.query_live_room_user_data() - if live_room_user_data.error or live_room_user_data.data is None: - raise WebSourceException(f'query {room} user data failed, {live_room_user_data.message}') - + room_status = await query_live_room_status(room_id=room_id) # 针对直播间短号进行处理 - if room_id == str(live_room_data.data.short_id) and room_id != str(live_room_data.data.room_id): - logger.debug(f'订阅直播间短号{room_id}, 已转换为直播间房间号{live_room_data.data.room_id}') - interface.matcher.state.update({'room_id': live_room_data.data.room_id}) - + if room_id != room_status.live_room_id: + logger.debug(f'订阅直播间短号{room_id!r}, 已转换为直播间房间号{room_status.live_room_id!r}') + interface.matcher.state.update({'room_id': room_status.live_room_id}) except Exception as e: - logger.error(f'获取直播间{room_id}用户信息失败, {e!r}') + logger.error(f'获取直播间{room_id!r}用户信息失败, {e!r}') await interface.finish_reply('获取直播间用户信息失败, 可能是网络原因或没有这个直播间, 请稍后再试') - ensure_msg = f'即将订阅Bilibili用户【{live_room_user_data.data.name}】的直播间\n\n确认吗?\n【是/否】' + ensure_msg = f'即将订阅Bilibili用户【{room_status.live_user_name}】的直播间\n\n确认吗?\n【是/否】' await interface.reject_arg_reply('ensure', ensure_msg) @@ -118,13 +108,13 @@ async def handle_del_subscription( pass elif ensure in ['是', '确认', 'Yes', 'yes', 'Y', 'y']: try: - await delete_live_room_sub(interface=interface, room_id=int(room_id)) + await delete_live_room_sub(interface=interface, room_id=room_id) await interface.entity.commit_session() - logger.success(f'{interface.entity}取消订阅直播间(rid={room_id})成功') - msg = f'取消订阅直播间{room_id}成功' + logger.success(f'{interface.entity}取消订阅直播间{room_id!r}成功') + msg = f'取消订阅直播间{room_id!r}成功' except Exception as e: - logger.error(f'{interface.entity}取消订阅直播间(rid={room_id})失败, {e!r}') - msg = f'取消订阅直播间{room_id}失败, 请稍后再试或联系管理员处理' + logger.error(f'{interface.entity}取消订阅直播间{room_id!r}失败, {e!r}') + msg = '取消订阅直播间失败, 请稍后再试或联系管理员处理' await interface.finish_reply(msg) else: @@ -144,7 +134,7 @@ async def handle_del_subscription( reject_key = 'ensure' else: exist_text = '\n'.join(f'{sub_id}: {user_nickname}' for sub_id, user_nickname in exist_sub.items()) - ensure_msg = f'未订阅直播间{room_id}, 请确认已订阅的直播间列表:\n\n{exist_text if exist_text else "无"}' + ensure_msg = f'未订阅直播间{room_id!r}, 请确认已订阅的直播间列表:\n\n{exist_text if exist_text else "无"}' reject_key = None except Exception as e: logger.error(f'获取{interface.entity}已订阅直播间失败, {e!r}') diff --git a/src/plugins/bilibili_live_monitor/consts.py b/src/plugins/bilibili_live_monitor/consts.py index 1b32a127..959f20d2 100644 --- a/src/plugins/bilibili_live_monitor/consts.py +++ b/src/plugins/bilibili_live_monitor/consts.py @@ -12,7 +12,6 @@ from src.database.internal.subscription_source import SubscriptionSourceType - BILI_LIVE_SUB_TYPE: str = SubscriptionSourceType.bili_live.value """b站直播间订阅类型""" NOTICE_AT_ALL: Literal['notice_at_all'] = 'notice_at_all' diff --git a/src/plugins/bilibili_live_monitor/data_source.py b/src/plugins/bilibili_live_monitor/data_source.py index 8e6cc668..6e8f11f0 100644 --- a/src/plugins/bilibili_live_monitor/data_source.py +++ b/src/plugins/bilibili_live_monitor/data_source.py @@ -8,89 +8,66 @@ @Software : PyCharm """ -from copy import deepcopy from typing import TYPE_CHECKING from nonebot import get_driver, logger from src.database import begin_db_session -from src.exception import WebSourceException from src.service.omega_base.internal import OmegaBiliLiveSubSource -from src.utils.bilibili_api import BilibiliLiveRoom -from src.utils.process_utils import semaphore_gather +from src.utils.bilibili_api import BilibiliLive from .model import BilibiliLiveRoomStatus, BilibiliLiveRoomStatusUpdate if TYPE_CHECKING: - from src.utils.bilibili_api.model.live_room import BilibiliLiveRoomDataModel + from src.utils.bilibili_api.future.models.live import RoomInfoData -__LIVE_STATUS: dict[int, BilibiliLiveRoomStatus] = {} -"""Bilibili 直播间状态缓存, {用户UID: 直播间状态}""" +__LIVE_ROOM_STATUS: dict[str, BilibiliLiveRoomStatus] = {} +"""Bilibili 直播间状态缓存, {直播间房间号: 直播间状态}""" -def check_and_upgrade_live_status( - live_room_data: "BilibiliLiveRoomDataModel", - *, - live_user_name: str | None = None -) -> BilibiliLiveRoomStatusUpdate | None: - """检查并更新 Bilibili 直播间状态缓存 - - :return: 更新后的直播间状态(如有) - """ - exist_status = __LIVE_STATUS.get(live_room_data.uid, None) - - if exist_status is None and live_user_name is None: - raise ValueError('Add new live room status must provide "live_user_name" parameter') - - if exist_status is None: # make typing checker happy - new_live_user_name = live_user_name if live_user_name is not None else f'bilibili直播间{live_room_data.room_id}' - else: - new_live_user_name = exist_status.live_user_name if live_user_name is None else live_user_name - - new_status = BilibiliLiveRoomStatus.model_validate({ - 'live_room_id': live_room_data.room_id, - 'live_status': live_room_data.live_status, - 'live_title': live_room_data.title, - 'live_user_name': new_live_user_name +def _convert_room_info(room_info: 'RoomInfoData') -> BilibiliLiveRoomStatus: + return BilibiliLiveRoomStatus.model_validate({ + 'live_room_id': room_info.room_id, + 'live_status': room_info.live_status, + 'live_title': room_info.title, + 'live_user_name': room_info.uname }) - __LIVE_STATUS.update({live_room_data.uid: new_status}) - logger.trace(f'Upgrade live room({live_room_data.room_id}) status: {new_status}') - if isinstance(exist_status, BilibiliLiveRoomStatus): - update = new_status - exist_status - else: - update = None - return update +async def get_all_subscribed_live_room_ids() -> list[str]: + """获取所有已订阅的直播间房间号列表""" + async with begin_db_session() as session: + source_result = await OmegaBiliLiveSubSource.query_type_all(session=session) + return [x.sub_id for x in source_result] -def get_all_live_room_status_uid() -> list[int]: - """获取缓存的直播间所有用户 uid 列表""" - return list(set(deepcopy(__LIVE_STATUS).keys())) +def check_and_upgrade_live_status(room_info: 'RoomInfoData') -> BilibiliLiveRoomStatusUpdate: + """检查并更新直播间状态缓存 -def get_live_room_status(room_id: int) -> BilibiliLiveRoomStatus | None: - """获取缓存的直播间信息""" - return {x.live_room_id: x for x in deepcopy(__LIVE_STATUS).values()}.get(room_id, None) + :return: 更新后的直播间状态(如有) + """ + new_status = _convert_room_info(room_info) + exist_status = __LIVE_ROOM_STATUS.setdefault(room_info.room_id, new_status) + __LIVE_ROOM_STATUS.update({room_info.room_id: new_status}) + logger.debug(f'Upgrade live room({room_info.room_id}) status: {new_status}') -def get_user_live_room_status(uid: int) -> BilibiliLiveRoomStatus | None: - """获取缓存的用户直播间信息""" - return deepcopy(__LIVE_STATUS).get(uid, None) + return new_status - exist_status -async def query_and_upgrade_live_room_status(live_room: BilibiliLiveRoom) -> BilibiliLiveRoomStatusUpdate | None: - """查询并更新 Bilibili 直播间状态""" - logger.debug(f'BilibiliLiveRoomMonitor | Updating live room({live_room.room_id}) status') +async def query_live_room_status(room_id: int | str, *, use_cache: bool = True) -> BilibiliLiveRoomStatus: + """查询单个直播间状态""" + if use_cache and (status := __LIVE_ROOM_STATUS.get(str(room_id), None)) is not None: + return status - live_room_data = await live_room.query_live_room_data() - if live_room_data.error or live_room_data.data is None: - raise WebSourceException(f'query {live_room} data failed, {live_room_data.message}') + rooms_info = await BilibiliLive.query_room_info_by_room_id_list(room_id_list=[room_id]) - live_room_user_data = await live_room.query_live_room_user_data() - if live_room_user_data.error or live_room_user_data.data is None: - raise WebSourceException(f'query {live_room} user data failed, {live_room_user_data.message}') + # 针对直播间短号进行处理 + room_info = rooms_info.data.by_room_ids.get(str(room_id), None) + if room_info is None: + room_info = {x.short_id: x for x in rooms_info.data.by_room_ids.values()}[str(room_id)] - return check_and_upgrade_live_status(live_room_data.data, live_user_name=live_room_user_data.data.name) + return _convert_room_info(room_info) @get_driver().on_startup @@ -99,13 +76,16 @@ async def _init_all_live_room_subscription_source_status() -> None: logger.opt(colors=True).info('BilibiliLiveRoomMonitor | Initializing live room status') try: - async with begin_db_session() as session: - source_result = await OmegaBiliLiveSubSource.query_type_all(session=session) - tasks = [ - query_and_upgrade_live_room_status(live_room=BilibiliLiveRoom(room_id=int(source.sub_id))) - for source in source_result - ] - await semaphore_gather(tasks=tasks, semaphore_num=10) + room_id_list = await get_all_subscribed_live_room_ids() + rooms_info = await BilibiliLive.query_room_info_by_room_id_list(room_id_list=room_id_list) + if rooms_info.error: + logger.error(f'BilibiliLiveRoomMonitor | Failed to query live room status, {rooms_info}') + return + + __LIVE_ROOM_STATUS.update({ + room_id: _convert_room_info(room_info=room_info) + for room_id, room_info in rooms_info.data.by_room_ids.items() + }) logger.opt(colors=True).success('BilibiliLiveRoomMonitor | Live room status initializing completed') except Exception as e: logger.error(f'BilibiliLiveRoomMonitor | Live room status initializing failed, {e!r}') @@ -114,8 +94,6 @@ async def _init_all_live_room_subscription_source_status() -> None: __all__ = [ 'check_and_upgrade_live_status', - 'get_all_live_room_status_uid', - 'get_live_room_status', - 'get_user_live_room_status', - 'query_and_upgrade_live_room_status', + 'get_all_subscribed_live_room_ids', + 'query_live_room_status', ] diff --git a/src/plugins/bilibili_live_monitor/helpers.py b/src/plugins/bilibili_live_monitor/helpers.py index 216ac2f2..401ff79f 100644 --- a/src/plugins/bilibili_live_monitor/helpers.py +++ b/src/plugins/bilibili_live_monitor/helpers.py @@ -16,69 +16,65 @@ from src.database import begin_db_session from src.service import ( - OmegaMatcherInterface as OmMI, - OmegaEntityInterface as OmEI, OmegaEntity, OmegaMessage, OmegaMessageSegment, ) +from src.service import ( + OmegaEntityInterface as OmEI, +) +from src.service import ( + OmegaMatcherInterface as OmMI, +) from src.service.omega_base.internal import OmegaBiliLiveSubSource -from src.utils.bilibili_api import BilibiliLiveRoom -from src.utils.process_utils import semaphore_gather -from .consts import BILI_LIVE_SUB_TYPE, NOTICE_AT_ALL, MODULE_NAME, PLUGIN_NAME +from src.utils import semaphore_gather +from src.utils.bilibili_api import BilibiliLive +from .consts import BILI_LIVE_SUB_TYPE, MODULE_NAME, NOTICE_AT_ALL, PLUGIN_NAME from .data_source import ( check_and_upgrade_live_status, - get_all_live_room_status_uid, - get_live_room_status, - get_user_live_room_status, - query_and_upgrade_live_room_status + get_all_subscribed_live_room_ids, + query_live_room_status, ) from .model import ( - BilibiliLiveRoomTitleChange, BilibiliLiveRoomStartLiving, BilibiliLiveRoomStartLivingWithUpdateTitle, + BilibiliLiveRoomStatusUpdate, BilibiliLiveRoomStopLiving, BilibiliLiveRoomStopLivingWithPlaylist, - BilibiliLiveRoomStatusUpdate + BilibiliLiveRoomTitleChange, ) if TYPE_CHECKING: from src.database.internal.entity import Entity from src.database.internal.subscription_source import SubscriptionSource - from src.utils.bilibili_api.model.live_room import BilibiliLiveRoomDataModel + from src.utils.bilibili_api.future.models.live import RoomInfoData -async def _query_room_sub_source(room_id: int) -> "SubscriptionSource": +async def _query_room_sub_source(room_id: int | str) -> 'SubscriptionSource': """从数据库查询直播间订阅源""" async with begin_db_session() as session: source_res = await OmegaBiliLiveSubSource(session=session, live_room_id=room_id).query_subscription_source() return source_res -async def _add_upgrade_room_sub_source(live_room: BilibiliLiveRoom) -> "SubscriptionSource": +async def _add_upgrade_room_sub_source(room_id: int | str) -> 'SubscriptionSource': """在数据库中更新直播间订阅源""" - await query_and_upgrade_live_room_status(live_room=live_room) - live_room_status = get_live_room_status(room_id=live_room.room_id) - if live_room_status is None: - raise RuntimeError(f'Upgrade live room {live_room.room_id} status failed') - - room_username = live_room_status.live_user_name - + live_room_status = await query_live_room_status(room_id=room_id) async with begin_db_session() as session: - sub_source = OmegaBiliLiveSubSource(session=session, live_room_id=live_room.room_id) - await sub_source.add_upgrade(sub_user_name=room_username, sub_info='Bilibili直播间订阅') + sub_source = OmegaBiliLiveSubSource(session=session, live_room_id=room_id) + await sub_source.add_upgrade(sub_user_name=live_room_status.live_user_name, sub_info='Bilibili直播间订阅') source_res = await sub_source.query_subscription_source() return source_res -async def add_live_room_sub(interface: OmMI, live_room: BilibiliLiveRoom) -> None: +async def add_live_room_sub(interface: OmMI, room_id: int | str) -> None: """为目标对象添加 Bilibili 直播间订阅""" - source_res = await _add_upgrade_room_sub_source(live_room=live_room) + source_res = await _add_upgrade_room_sub_source(room_id=room_id) await interface.entity.add_subscription(subscription_source=source_res, - sub_info=f'Bilibili直播间订阅(rid={live_room.rid})') + sub_info=f'Bilibili直播间订阅(rid={room_id})') -async def delete_live_room_sub(interface: OmMI, room_id: int) -> None: +async def delete_live_room_sub(interface: OmMI, room_id: int | str) -> None: """为目标对象删除 Bilibili 直播间订阅""" source_res = await _query_room_sub_source(room_id=room_id) await interface.entity.delete_subscription(subscription_source=source_res) @@ -92,7 +88,7 @@ async def query_subscribed_live_room_sub_source(interface: OmMI) -> dict[str, st return {x.sub_id: x.sub_user_name for x in subscribed_source} -async def query_subscribed_entity_by_live_room(room_id: int) -> list["Entity"]: +async def query_subscribed_entity_by_live_room(room_id: int | str) -> list['Entity']: """根据 Bilibili 直播间房间号查询已经订阅了这个用户的内部 Entity 对象""" async with begin_db_session() as session: sub_source = OmegaBiliLiveSubSource(session=session, live_room_id=room_id) @@ -101,50 +97,45 @@ async def query_subscribed_entity_by_live_room(room_id: int) -> list["Entity"]: async def _format_live_room_update_message( - live_room_data: "BilibiliLiveRoomDataModel", + room_info: 'RoomInfoData', update_data: BilibiliLiveRoomStatusUpdate ) -> str | OmegaMessage | None: """处理直播间更新为消息""" - live_room_status = get_user_live_room_status(uid=live_room_data.uid) - if live_room_status is None: - return None - send_message = '【bilibili直播间】\n' - user_name = live_room_status.live_user_name need_url = False if isinstance(update_data.update, (BilibiliLiveRoomStartLivingWithUpdateTitle, BilibiliLiveRoomStartLiving)): # 开播 - if isinstance(live_room_data.live_time, datetime): - start_time = live_room_data.live_time.strftime('%Y-%m-%d %H:%M:%S') + if isinstance(room_info.live_time, datetime): + start_time = room_info.live_time.strftime('%Y-%m-%d %H:%M:%S') else: - start_time = str(live_room_data.live_time) - send_message += f"{start_time}\n{user_name}开播啦!\n\n【{live_room_data.title}】" + start_time = str(room_info.live_time) + send_message += f'{start_time}\n{room_info.uname}开播啦!\n\n【{room_info.title}】' need_url = True elif isinstance(update_data.update, BilibiliLiveRoomStopLiving): # 下播 - send_message += f'{user_name}下播了' + send_message += f'{room_info.uname}下播了' elif isinstance(update_data.update, BilibiliLiveRoomStopLivingWithPlaylist): # 下播转轮播 - send_message += f'{user_name}下播了(轮播中)' - elif isinstance(update_data.update, BilibiliLiveRoomTitleChange) and live_room_data.live_status == 1: + send_message += f'{room_info.uname}下播了(轮播中)' + elif isinstance(update_data.update, BilibiliLiveRoomTitleChange) and room_info.live_status == 1: # 直播中换标题 - send_message += f"{user_name}的直播间换标题啦!\n\n【{live_room_data.title}】" + send_message += f'{room_info.uname}的直播间换标题啦!\n\n【{room_info.title}】' need_url = True else: return None # 下载直播间封面图 - if live_room_data.cover: + if room_info.cover_url: try: - cover_img = await BilibiliLiveRoom.download_resource(url=live_room_data.cover) + cover_img = await BilibiliLive.download_resource(url=room_info.cover_url) send_message += '\n' send_message += OmegaMessageSegment.image(url=cover_img.path) except Exception as e: logger.warning(f'BilibiliLiveRoomMonitor | Download live room cover failed, {e!r}') if need_url: - send_message += f'\n传送门: https://live.bilibili.com/{live_room_data.room_id}' + send_message += f'\n传送门: https://live.bilibili.com/{room_info.room_id}' return send_message @@ -158,7 +149,7 @@ async def _has_notice_at_all_node(entity: OmegaEntity) -> bool: return False -async def _msg_sender(entity: "Entity", message: str | OmegaMessage) -> None: +async def _msg_sender(entity: 'Entity', message: str | OmegaMessage) -> None: """向 entity 发送直播间通知""" try: async with begin_db_session() as session: @@ -175,20 +166,18 @@ async def _msg_sender(entity: "Entity", message: str | OmegaMessage) -> None: logger.error(f'BilibiliLiveRoomMonitor | Sending message to {entity} failed, {e!r}') -async def _process_bili_live_room_update(live_room_data: "BilibiliLiveRoomDataModel") -> None: - """处理 Bilibili 直播间状态更新""" - logger.debug(f'BilibiliLiveRoomMonitor | Checking bilibili live room({live_room_data.room_id}) status') +async def _process_bili_live_room_update(room_info: 'RoomInfoData') -> None: + """处理直播间状态更新""" + logger.debug(f'BilibiliLiveRoomMonitor | Checking live room({room_info.room_id}) status') - update_data = check_and_upgrade_live_status(live_room_data=live_room_data) - if update_data is None or not update_data.is_update: - logger.debug(f'BilibiliLiveRoomMonitor | Bilibili live room({live_room_data.room_id}) holding') + update_data = check_and_upgrade_live_status(room_info=room_info) + if not update_data.is_update: + logger.debug(f'BilibiliLiveRoomMonitor | Live room({room_info.room_id}) holding') return - else: - logger.info(f'BilibiliLiveRoomMonitor | Bilibili live room({live_room_data.room_id}) ' - f'status update, {update_data.update}') - send_msg = await _format_live_room_update_message(live_room_data=live_room_data, update_data=update_data) - subscribed_entity = await query_subscribed_entity_by_live_room(room_id=live_room_data.room_id) + logger.info(f'BilibiliLiveRoomMonitor | Live room({room_info.room_id}) status update, {update_data.update}') + send_msg = await _format_live_room_update_message(room_info=room_info, update_data=update_data) + subscribed_entity = await query_subscribed_entity_by_live_room(room_id=room_info.room_id) # 向订阅者发送直播间更新信息 send_tasks = [ @@ -201,18 +190,21 @@ async def _process_bili_live_room_update(live_room_data: "BilibiliLiveRoomDataMo async def bili_live_room_monitor_main() -> None: """向已订阅的用户或群发送 Bilibili 直播间状态更新""" - uid_list = get_all_live_room_status_uid() - if not uid_list: + room_ids = await get_all_subscribed_live_room_ids() + if not room_ids: logger.debug('BilibiliLiveRoomMonitor | None of live room subscription, ignored') return - room_status_data = await BilibiliLiveRoom.query_live_room_by_uid_list(uid_list=uid_list) - if room_status_data.error: - logger.error(f'BilibiliLiveRoomMonitor | Failed to checking live room status, {room_status_data}') + rooms_info = await BilibiliLive.query_room_info_by_room_id_list(room_id_list=room_ids) + if rooms_info.error: + logger.error(f'BilibiliLiveRoomMonitor | Failed to checking live room status, {rooms_info}') return # 处理直播间状态更新并向订阅者发送直播间更新信息 - send_tasks = [_process_bili_live_room_update(live_room_data=data) for _, data in room_status_data.data.items()] + send_tasks = [ + _process_bili_live_room_update(room_info=room_info) + for _, room_info in rooms_info.data.by_room_ids.items() + ] await semaphore_gather(tasks=send_tasks, semaphore_num=3) diff --git a/src/plugins/bilibili_live_monitor/model.py b/src/plugins/bilibili_live_monitor/model.py index 6d6f5452..82ecb30f 100644 --- a/src/plugins/bilibili_live_monitor/model.py +++ b/src/plugins/bilibili_live_monitor/model.py @@ -8,14 +8,14 @@ @Software : PyCharm """ -from typing import Literal, Optional +from typing import Literal from pydantic import BaseModel, ConfigDict, model_validator class BilibiliLiveRoomStatus(BaseModel): """Bilibili 直播间状态""" - live_room_id: int + live_room_id: str live_status: int live_title: str live_user_name: str @@ -33,7 +33,7 @@ def __eq__(self, other) -> bool: def __ne__(self, other) -> bool: return not self.__eq__(other) - def __sub__(self, other) -> "BilibiliLiveRoomStatusUpdate": + def __sub__(self, other) -> 'BilibiliLiveRoomStatusUpdate': if isinstance(other, BilibiliLiveRoomStatus): differ = set(self.model_dump().items()) - set(other.model_dump().items()) differ_data = {k: v for k, v in differ} @@ -95,16 +95,20 @@ class BilibiliLiveRoomStopLivingWithPlaylist(BaseModel): model_config = ConfigDict(extra='ignore') +type LiveRoomStatusUpdateType = ( + BilibiliLiveRoomTitleChange + | BilibiliLiveRoomStartLiving + | BilibiliLiveRoomStartLivingWithUpdateTitle + | BilibiliLiveRoomStopLiving + | BilibiliLiveRoomStopLivingWithPlaylist + | None +) + + class BilibiliLiveRoomStatusUpdate(BaseModel): """Bilibili 直播间状态更新""" is_update: bool - update: Optional[ - BilibiliLiveRoomTitleChange | - BilibiliLiveRoomStartLiving | - BilibiliLiveRoomStartLivingWithUpdateTitle | - BilibiliLiveRoomStopLiving | - BilibiliLiveRoomStopLivingWithPlaylist - ] = None + update: LiveRoomStatusUpdateType = None @model_validator(mode='after') @classmethod diff --git a/src/plugins/bot_message_revoker/__init__.py b/src/plugins/bot_message_revoker/__init__.py index 927f416c..144e3b2a 100644 --- a/src/plugins/bot_message_revoker/__init__.py +++ b/src/plugins/bot_message_revoker/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='Bot消息撤回', description='【Bot 消息撤回插件】\n' @@ -25,5 +24,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/bot_message_revoker/command.py b/src/plugins/bot_message_revoker/command.py index 8661731f..353c4bd2 100644 --- a/src/plugins/bot_message_revoker/command.py +++ b/src/plugins/bot_message_revoker/command.py @@ -8,15 +8,11 @@ @Software : PyCharm """ -from nonebot.adapters.onebot.v11 import ( - Bot as OneBotV11Bot, - MessageEvent as OneBotV11MessageEvent -) +from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot +from nonebot.adapters.onebot.v11 import MessageEvent as OneBotV11MessageEvent from nonebot.adapters.telegram import Bot as TelegramBot -from nonebot.adapters.telegram.event import ( - PrivateMessageEvent as TelegramPrivateMessageEvent, - GroupMessageEvent as TelegramGroupMessageEvent -) +from nonebot.adapters.telegram.event import GroupMessageEvent as TelegramGroupMessageEvent +from nonebot.adapters.telegram.event import PrivateMessageEvent as TelegramPrivateMessageEvent from nonebot.log import logger from nonebot.permission import SUPERUSER from nonebot.plugin import on_command diff --git a/src/plugins/choice_helper/__init__.py b/src/plugins/choice_helper/__init__.py index 0f2e09ff..923220c6 100644 --- a/src/plugins/choice_helper/__init__.py +++ b/src/plugins/choice_helper/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='帮我选', description='【选择困难症帮助器插件】\n' @@ -23,5 +22,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/choice_helper/command.py b/src/plugins/choice_helper/command.py index 080d926b..b6790440 100644 --- a/src/plugins/choice_helper/command.py +++ b/src/plugins/choice_helper/command.py @@ -15,7 +15,8 @@ from nonebot.plugin import on_command from src.params.handler import get_command_str_single_arg_parser_handler -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state @on_command( @@ -36,7 +37,7 @@ async def handle_help_choices( await interface.finish_reply('你什么选项都没告诉我, 怎么帮你选OwO') result = random.choice(choice_list) - result_text = f'''帮你从“{'”,“'.join(choice_list)}”中选择了:\n\n“{result}”''' + result_text = f"""帮你从“{'”,“'.join(choice_list)}”中选择了:\n\n“{result}”""" await interface.finish_reply(result_text) diff --git a/src/plugins/http_cat/__init__.py b/src/plugins/http_cat/__init__.py index 5589d2b8..ef7d678b 100644 --- a/src/plugins/http_cat/__init__.py +++ b/src/plugins/http_cat/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='HttpCat', description='【HttpCat插件】\n' @@ -22,5 +21,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/http_cat/command.py b/src/plugins/http_cat/command.py index 1d92ba9c..cd4bb1b6 100644 --- a/src/plugins/http_cat/command.py +++ b/src/plugins/http_cat/command.py @@ -15,7 +15,8 @@ from nonebot.plugin import on_command from src.params.handler import get_command_str_single_arg_parser_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from .data_source import get_http_cat diff --git a/src/plugins/http_cat/data_source.py b/src/plugins/http_cat/data_source.py index 30aeafa0..5a200997 100644 --- a/src/plugins/http_cat/data_source.py +++ b/src/plugins/http_cat/data_source.py @@ -10,8 +10,7 @@ from src.exception import WebSourceException from src.resource import TemporaryResource -from src.service import OmegaRequests - +from src.utils import OmegaRequests _TMP_FOLDER: TemporaryResource = TemporaryResource('http_cat') """缓存图片目录""" diff --git a/src/plugins/image_searcher/__init__.py b/src/plugins/image_searcher/__init__.py index 7a9745a9..c7dd7094 100644 --- a/src/plugins/image_searcher/__init__.py +++ b/src/plugins/image_searcher/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='识图搜番', description='【识图搜番插件】\n' @@ -23,5 +22,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/image_searcher/command.py b/src/plugins/image_searcher/command.py index 45fffdd4..c5a2087a 100644 --- a/src/plugins/image_searcher/command.py +++ b/src/plugins/image_searcher/command.py @@ -8,8 +8,9 @@ @Software : PyCharm """ +from collections.abc import Sequence from datetime import datetime -from typing import TYPE_CHECKING, Annotated, Sequence +from typing import TYPE_CHECKING, Annotated from nonebot import get_driver from nonebot.log import logger @@ -18,12 +19,13 @@ from nonebot.typing import T_State from src.params.handler import get_command_str_single_arg_parser_handler -from src.resource import TemporaryResource, StaticResource -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, OmegaRequests, enable_processor_state +from src.resource import StaticResource, TemporaryResource +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state +from src.utils import OmegaRequests, semaphore_gather from src.utils.image_searcher import ComplexImageSearcher, TraceMoe from src.utils.image_utils import ImageUtils from src.utils.image_utils.template import PreviewImageModel, PreviewImageThumbs, generate_thumbs_preview_image -from src.utils.process_utils import semaphore_gather if TYPE_CHECKING: from src.utils.image_searcher.model import ImageSearchingResult @@ -96,7 +98,7 @@ async def handle_search_image( await interface.finish_reply('获取识别结果失败了, 发生了意外的错误, 请稍后再试') -async def _fetch_result_as_preview_body(result: "ImageSearchingResult") -> PreviewImageThumbs: +async def _fetch_result_as_preview_body(result: 'ImageSearchingResult') -> PreviewImageThumbs: requests = OmegaRequests( timeout=15, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @@ -107,14 +109,14 @@ async def _fetch_result_as_preview_body(result: "ImageSearchingResult") -> Previ return PreviewImageThumbs(desc_text=desc_text, preview_thumb=requests.parse_content_as_bytes(thumbnail_response)) -async def _emit_preview_model_from_searching_result(results: Sequence["ImageSearchingResult"]) -> PreviewImageModel: +async def _emit_preview_model_from_searching_result(results: Sequence['ImageSearchingResult']) -> PreviewImageModel: tasks = [_fetch_result_as_preview_body(result=result) for result in results] preview_data = list(await semaphore_gather(tasks=tasks, semaphore_num=6, filter_exception=True)) count = len(preview_data) return PreviewImageModel(preview_name='ImageSearcherResults', count=count, previews=preview_data) -async def _generate_result_preview_image(results: Sequence["ImageSearchingResult"]) -> TemporaryResource: +async def _generate_result_preview_image(results: Sequence['ImageSearchingResult']) -> TemporaryResource: """识别图片并将结果转换为消息""" preview_model = await _emit_preview_model_from_searching_result(results=results) preview_img_file = await generate_thumbs_preview_image( @@ -129,7 +131,7 @@ async def _generate_result_preview_image(results: Sequence["ImageSearchingResult return preview_img_file -async def _generate_result_desc_image(results: Sequence["ImageSearchingResult"]) -> TemporaryResource: +async def _generate_result_desc_image(results: Sequence['ImageSearchingResult']) -> TemporaryResource: preview_txt = '\n\n'.join( f'来源: {result.source}\n相似度: {result.similarity if result.similarity else "未知"}\n来源地址:\n{url}' for result in results diff --git a/src/plugins/jm/__init__.py b/src/plugins/jm/__init__.py index c8908042..117a712e 100644 --- a/src/plugins/jm/__init__.py +++ b/src/plugins/jm/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='jm', description='【JM】\n' @@ -24,5 +23,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/jm/command.py b/src/plugins/jm/command.py index b6f87d3d..57b7d6b2 100644 --- a/src/plugins/jm/command.py +++ b/src/plugins/jm/command.py @@ -16,9 +16,10 @@ from nonebot.rule import Namespace from src.params.handler import get_command_str_single_arg_parser_handler, get_shell_command_parse_failed_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from src.utils.comic18 import Comic18 -from .helper import get_searching_argument_parser, parse_from_searching_parser, format_album_desc_msg +from .helper import format_album_desc_msg, get_searching_argument_parser, parse_from_searching_parser jm = CommandGroup( 'jm', diff --git a/src/plugins/jm/helper.py b/src/plugins/jm/helper.py index 6896407b..0029fb16 100644 --- a/src/plugins/jm/helper.py +++ b/src/plugins/jm/helper.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from typing import Literal, Optional +from typing import Literal from nonebot.rule import ArgumentParser, Namespace from pydantic import BaseModel, ConfigDict @@ -32,11 +32,11 @@ def get_searching_argument_parser() -> ArgumentParser: class SearchingArguments(BaseModel): """搜索命令 argument parser 的解析结果 Model""" - page: Optional[int] - type: Optional[Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single']] - time: Optional[Literal['a', 't', 'w', 'm']] - order: Optional[Literal['mr', 'mv', 'mp', 'md', 'tr', 'tf']] - tag: Optional[Literal['0', '1', '2', '3', '4']] + page: int | None + type: Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single'] | None + time: Literal['a', 't', 'w', 'm'] | None + order: Literal['mr', 'mv', 'mp', 'md', 'tr', 'tf'] | None + tag: Literal['0', '1', '2', '3', '4'] | None keyword: list[str] model_config = ConfigDict(extra='ignore', from_attributes=True) diff --git a/src/plugins/maybe/__init__.py b/src/plugins/maybe/__init__.py index 9eede196..f35170cb 100644 --- a/src/plugins/maybe/__init__.py +++ b/src/plugins/maybe/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='求签', description='【求签插件】\n' @@ -24,5 +23,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/maybe/command.py b/src/plugins/maybe/command.py index 8c381c04..44638da0 100644 --- a/src/plugins/maybe/command.py +++ b/src/plugins/maybe/command.py @@ -15,7 +15,8 @@ from nonebot.plugin import on_command from src.params.handler import get_command_str_single_arg_parser_handler -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from .helpers import query_divination diff --git a/src/plugins/maybe/helpers.py b/src/plugins/maybe/helpers.py index 55663882..b799ddcb 100644 --- a/src/plugins/maybe/helpers.py +++ b/src/plugins/maybe/helpers.py @@ -21,35 +21,51 @@ def query_divination(divination_text: str, user_id: str | int) -> str: md5.update(random_seed_str.encode('utf-8')) random_seed = md5.hexdigest() random.seed(random_seed) - # 生成求签随机数, 9分一级 - divination_result = random.randint(1, 109) + # 生成求签随机数 + divination_result = random.randint(1, 108) # 大吉・中吉・小吉・吉・半吉・末吉・末小吉・凶・小凶・半凶・末凶・大凶 - if divination_result < 9: + if divination_result < 4: + result_star = 0 result_text = '大凶' - elif divination_result < 18: + elif divination_result < 9: + result_star = 1 result_text = '末凶' - elif divination_result < 27: + elif divination_result < 16: + result_star = 2 result_text = '半凶' - elif divination_result < 36: + elif divination_result < 25: + result_star = 3 result_text = '小凶' - elif divination_result < 45: + elif divination_result < 36: + result_star = 4 result_text = '凶' - elif divination_result < 54: + elif divination_result < 48: + result_star = 5 result_text = '末小吉' - elif divination_result < 63: + elif divination_result < 60: + result_star = 6 result_text = '末吉' elif divination_result < 72: + result_star = 7 result_text = '半吉' - elif divination_result < 81: + elif divination_result < 84: + result_star = 8 result_text = '吉' - elif divination_result < 90: + elif divination_result < 96: + result_star = 9 result_text = '小吉' - elif divination_result < 99: + elif divination_result < 102: + result_star = 10 result_text = '中吉' else: + result_star = 11 result_text = '大吉' - msg = f'所求之事: “{divination_text}”\n\n结果: 【{result_text}】' + msg = ( + f'所求之事: “{divination_text}”\n\n' + f'结果: 【{result_text}】\n' + f'{"★" * result_star}{"☆" * (11 - result_star)}' + ) # 重置随机种子 random.seed() diff --git a/src/plugins/mirage_tank/__init__.py b/src/plugins/mirage_tank/__init__.py index 467d0de8..3f84a9d5 100644 --- a/src/plugins/mirage_tank/__init__.py +++ b/src/plugins/mirage_tank/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='幻影坦克', description='【幻影坦克图片生成工具】\n' @@ -23,5 +22,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/mirage_tank/command.py b/src/plugins/mirage_tank/command.py index 44bd0878..631db73a 100644 --- a/src/plugins/mirage_tank/command.py +++ b/src/plugins/mirage_tank/command.py @@ -16,9 +16,17 @@ from nonebot.typing import T_State from src.params.handler import get_command_str_single_arg_parser_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state -from .utils import (simple_white, simple_black, simple_noise, color_noise, - complex_gray, complex_color, complex_difference) +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state +from .utils import ( + color_noise, + complex_color, + complex_difference, + complex_gray, + simple_black, + simple_noise, + simple_white, +) @on_command( diff --git a/src/plugins/mirage_tank/utils.py b/src/plugins/mirage_tank/utils.py index a51713e0..784b09ca 100644 --- a/src/plugins/mirage_tank/utils.py +++ b/src/plugins/mirage_tank/utils.py @@ -8,14 +8,14 @@ @Software : PyCharm """ +from collections.abc import Callable from datetime import datetime -from typing import Callable, Optional -from PIL import Image, ImageEnhance, ImageOps, ImageMath +from PIL import Image, ImageEnhance, ImageMath, ImageOps from nonebot.utils import run_sync from src.resource import TemporaryResource -from src.service import OmegaRequests +from src.utils import OmegaRequests from src.utils.image_utils import ImageUtils _TMP_FOLDER: TemporaryResource = TemporaryResource('mirage_tank') @@ -39,7 +39,7 @@ async def _load_image(image_content: bytes) -> ImageUtils: async def generate_mirage_tank( factory: MIRAGE_FACTORY, base_image_url: str, - addition_image_url: Optional[str] = None + addition_image_url: str | None = None ) -> TemporaryResource: """生成幻影坦克图片""" base_image_content = await _fetch_image(image_url=base_image_url) diff --git a/src/plugins/moe/__init__.py b/src/plugins/moe/__init__.py index f9c2c93c..3d193385 100644 --- a/src/plugins/moe/__init__.py +++ b/src/plugins/moe/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='来点萌图', description='【图库插件】\n' @@ -24,5 +23,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/moe/command.py b/src/plugins/moe/command.py index d7ac3bbe..11243ecd 100644 --- a/src/plugins/moe/command.py +++ b/src/plugins/moe/command.py @@ -18,13 +18,14 @@ from nonebot.rule import Namespace from src.params.handler import get_shell_command_parse_failed_handler -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state -from src.utils.process_utils import semaphore_gather +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state +from src.utils import semaphore_gather from .config import moe_plugin_config from .consts import ALLOW_R18_NODE from .helpers import ( - has_allow_r18_node, get_query_argument_parser, + has_allow_r18_node, parse_from_query_parser, prepare_send_image, query_artworks_from_database, @@ -76,13 +77,15 @@ async def handle_setu( origin=parsed_args.origin, all_origin=parsed_args.all_origin, allow_rating_range=allow_rating_range, + latest=parsed_args.latest, + ratio=parsed_args.ratio, num=parsed_args.num, ) if not artworks: await interface.finish_reply('找不到涩图QAQ') - await interface.send_reply_auto_revoke('稍等, 正在下载图片~', 30) + await interface.send_reply('稍等, 正在下载图片~') send_messages = await semaphore_gather( tasks=[prepare_send_image(x) for x in artworks], @@ -142,13 +145,15 @@ async def handle_moe( origin=parsed_args.origin, all_origin=parsed_args.all_origin, allow_rating_range=(0, 0), + latest=parsed_args.latest, + ratio=parsed_args.ratio, num=parsed_args.num, ) if not artworks: await interface.finish_reply('找不到萌图QAQ') - await interface.send_reply_auto_revoke('稍等, 正在下载图片~', 30) + await interface.send_reply('稍等, 正在下载图片~') send_messages = await semaphore_gather( tasks=[prepare_send_image(x) for x in artworks], diff --git a/src/plugins/moe/config.py b/src/plugins/moe/config.py index d7340d8f..928bf62f 100644 --- a/src/plugins/moe/config.py +++ b/src/plugins/moe/config.py @@ -11,9 +11,13 @@ from nonebot import get_plugin_config, logger from pydantic import BaseModel, ConfigDict, ValidationError +from .consts import ALLOW_MOE_PLUGIN_ARTWORK_ORIGIN + class MoePluginConfig(BaseModel): """Moe 插件配置""" + # 默认查询的作品来源 + moe_plugin_default_origin: ALLOW_MOE_PLUGIN_ARTWORK_ORIGIN = 'pixiv' # 默认每次查询的图片数量 moe_plugin_query_image_num: int = 3 # 允许用户通过参数调整的每次查询的图片数量上限 diff --git a/src/plugins/moe/helpers.py b/src/plugins/moe/helpers.py index e158d8cc..3762e8e3 100644 --- a/src/plugins/moe/helpers.py +++ b/src/plugins/moe/helpers.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Optional, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Literal from nonebot.log import logger from nonebot.rule import ArgumentParser, Namespace @@ -25,7 +26,7 @@ from src.service.artwork_collection.typing import CollectedArtwork -async def _has_allow_r18_node(interface: "OmegaMatcherInterface") -> bool: +async def _has_allow_r18_node(interface: 'OmegaMatcherInterface') -> bool: """判断当前 entity 主体是否具有允许预览 r18 作品的权限""" if interface.matcher.plugin is None: return False @@ -40,7 +41,7 @@ async def _has_allow_r18_node(interface: "OmegaMatcherInterface") -> bool: ) -async def has_allow_r18_node(interface: "OmegaMatcherInterface") -> bool: +async def has_allow_r18_node(interface: 'OmegaMatcherInterface') -> bool: """判断当前 entity 主体是否具有允许预览 r18 作品的权限""" try: allow_r18 = await _has_allow_r18_node(interface=interface) @@ -56,6 +57,8 @@ def get_query_argument_parser() -> ArgumentParser: parser.add_argument('-o', '--origin', type=str, default=None) parser.add_argument('-a', '--all-origin', action='store_true') parser.add_argument('-r', '--r18', action='store_true') + parser.add_argument('-l', '--latest', action='store_true') + parser.add_argument('-m', '--ratio', type=int, default=None) parser.add_argument('-n', '--num', type=int, default=0) parser.add_argument('keywords', nargs='*') return parser @@ -63,9 +66,11 @@ def get_query_argument_parser() -> ArgumentParser: class QueryArguments(BaseModel): """查询图库命令 argument parser 的解析结果 Model""" - origin: Optional[ALLOW_MOE_PLUGIN_ARTWORK_ORIGIN] + origin: ALLOW_MOE_PLUGIN_ARTWORK_ORIGIN | None all_origin: bool r18: bool + latest: bool + ratio: int | None num: int keywords: list[str] @@ -79,14 +84,18 @@ def parse_from_query_parser(args: Namespace) -> QueryArguments: async def query_artworks_from_database( keywords: Sequence[str], - origin: Optional[ALLOW_MOE_PLUGIN_ARTWORK_ORIGIN] = None, + origin: ALLOW_MOE_PLUGIN_ARTWORK_ORIGIN | None = None, all_origin: bool = False, allow_rating_range: tuple[int, int] = (0, 0), + latest: bool = False, + ratio: int | None = None, num: int = 0, -) -> list["CollectedArtwork"]: +) -> list['CollectedArtwork']: """从数据库查询收藏作品, 特别的: 当参数 `origin` 值为 `none` 时代表从所有的来源随机获取""" if all_origin: query_origin = ALL_MOE_PLUGIN_ARTWORK_ORIGIN + elif origin is None: + query_origin = moe_plugin_config.moe_plugin_default_origin else: query_origin = origin @@ -95,15 +104,17 @@ async def query_artworks_from_database( moe_plugin_config.moe_plugin_query_image_limit ) + order_mode: Literal['latest', 'random'] = 'latest' if latest else 'random' + random_artworks = await get_artwork_collection_type().query_any_origin_by_condition( keywords=keywords, origin=query_origin, num=query_num, - allow_classification_range=(2, 3), allow_rating_range=allow_rating_range, + allow_classification_range=(2, 3), allow_rating_range=allow_rating_range, ratio=ratio, order_mode=order_mode, ) return [get_artwork_collection(artwork=artwork) for artwork in random_artworks] -async def prepare_send_image(collected_artwork: "CollectedArtwork") -> OmegaMessageSegment: +async def prepare_send_image(collected_artwork: 'CollectedArtwork') -> OmegaMessageSegment: """预处理待发送图片""" if not isinstance(collected_artwork.artwork_proxy, ImageOpsMixin): raise RuntimeError(f'{collected_artwork} is not compatible with the image processing method') diff --git a/src/plugins/nbnhhsh/__init__.py b/src/plugins/nbnhhsh/__init__.py index 0dad0edf..28bfb054 100644 --- a/src/plugins/nbnhhsh/__init__.py +++ b/src/plugins/nbnhhsh/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='好好说话', description='【能不能好好说话?】\n' @@ -22,5 +21,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/nbnhhsh/command.py b/src/plugins/nbnhhsh/command.py index 4fc1ca86..7513edd2 100644 --- a/src/plugins/nbnhhsh/command.py +++ b/src/plugins/nbnhhsh/command.py @@ -15,7 +15,8 @@ from nonebot.plugin import on_command from src.params.handler import get_command_str_single_arg_parser_handler -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from .data_source import query_guess diff --git a/src/plugins/nbnhhsh/data_source.py b/src/plugins/nbnhhsh/data_source.py index a7b479e5..51d7457c 100644 --- a/src/plugins/nbnhhsh/data_source.py +++ b/src/plugins/nbnhhsh/data_source.py @@ -8,18 +8,17 @@ @Software : PyCharm """ -from typing import Optional from pydantic import BaseModel from src.compat import parse_obj_as -from src.service import OmegaRequests +from src.utils import OmegaRequests class GuessResult(BaseModel): name: str - trans: Optional[list[str]] = None - inputting: Optional[list[str]] = None + trans: list[str] | None = None + inputting: list[str] | None = None @property def guess_result(self) -> list[str]: diff --git a/src/plugins/nhentai/__init__.py b/src/plugins/nhentai/__init__.py index 1696640f..7c050986 100644 --- a/src/plugins/nhentai/__init__.py +++ b/src/plugins/nhentai/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='nhentai', description='【nhentai】\n' @@ -24,5 +23,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/nhentai/command.py b/src/plugins/nhentai/command.py index 9bf7f259..19165e87 100644 --- a/src/plugins/nhentai/command.py +++ b/src/plugins/nhentai/command.py @@ -16,9 +16,10 @@ from nonebot.rule import Namespace from src.params.handler import get_command_str_single_arg_parser_handler, get_shell_command_parse_failed_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from src.utils.nhentai import NhentaiGallery -from .helper import get_searching_argument_parser, parse_from_searching_parser, format_gallery_desc_msg +from .helper import format_gallery_desc_msg, get_searching_argument_parser, parse_from_searching_parser nhentai = CommandGroup( 'nhentai', diff --git a/src/plugins/nhentai/helper.py b/src/plugins/nhentai/helper.py index 08be0f3b..8d5c4d3c 100644 --- a/src/plugins/nhentai/helper.py +++ b/src/plugins/nhentai/helper.py @@ -42,7 +42,7 @@ def parse_from_searching_parser(args: Namespace) -> SearchingArguments: return SearchingArguments.model_validate(args) -async def format_gallery_desc_msg(gallery: "NhentaiGallery") -> OmegaMessage: +async def format_gallery_desc_msg(gallery: 'NhentaiGallery') -> OmegaMessage: """获取格式化作品描述文本""" gallery_data = await gallery.query_gallery() folder_name = f'gallery_{gallery_data.id}' diff --git a/src/plugins/omega_announcement/__init__.py b/src/plugins/omega_announcement/__init__.py index a4b9873b..d17de442 100644 --- a/src/plugins/omega_announcement/__init__.py +++ b/src/plugins/omega_announcement/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='公告', description='【公告插件】\n' @@ -22,5 +21,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/omega_announcement/command.py b/src/plugins/omega_announcement/command.py index eb2ca11a..ca3fe876 100644 --- a/src/plugins/omega_announcement/command.py +++ b/src/plugins/omega_announcement/command.py @@ -10,7 +10,7 @@ from typing import Annotated -from nonebot.adapters import Message +from nonebot.internal.adapter import Message as BaseMessage from nonebot.log import logger from nonebot.params import Arg, Depends from nonebot.permission import SUPERUSER @@ -19,13 +19,10 @@ from src.database import EntityDAL from src.params.handler import get_command_message_arg_parser_handler -from src.service import ( - OmegaMatcherInterface as OmMI, - OmegaEntityInterface as OmEI, - OmegaEntity, - enable_processor_state -) -from src.utils.process_utils import semaphore_gather +from src.service import OmegaEntity, enable_processor_state +from src.service import OmegaEntityInterface as OmEI +from src.service import OmegaMatcherInterface as OmMI +from src.utils import semaphore_gather @on_command( @@ -41,7 +38,7 @@ async def handle_announce( interface: Annotated[OmMI, Depends(OmMI.depend())], entity_dal: Annotated[EntityDAL, Depends(EntityDAL.dal_dependence)], - announcement_content: Annotated[Message, Arg('announcement_content')] + announcement_content: Annotated[BaseMessage, Arg('announcement_content')] ) -> None: announce_message = interface.get_message_extractor()(message=announcement_content).message diff --git a/src/plugins/omega_any_artworks/command.py b/src/plugins/omega_any_artworks/command.py index 07344a72..4b7e70ec 100644 --- a/src/plugins/omega_any_artworks/command.py +++ b/src/plugins/omega_any_artworks/command.py @@ -12,9 +12,10 @@ DanbooruArtworkProxy, GelbooruArtworkProxy, KonachanSafeArtworkProxy, - YandereArtworkProxy, PixivArtworkProxy, + YandereArtworkProxy, ) + from .handlers import ArtworkHandlerManager __ARTWORK_PROXY_LIST = [ diff --git a/src/plugins/omega_any_artworks/handlers.py b/src/plugins/omega_any_artworks/handlers.py index f714ae9b..b9b2ae60 100644 --- a/src/plugins/omega_any_artworks/handlers.py +++ b/src/plugins/omega_any_artworks/handlers.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Annotated, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Annotated from nonebot.log import logger from nonebot.params import Depends, ShellCommandArgs @@ -17,12 +18,14 @@ from pydantic import BaseModel, ConfigDict from src.params.handler import get_shell_command_parse_failed_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessage, OmegaMessageSegment, enable_processor_state -from src.utils.process_utils import semaphore_gather +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessage, OmegaMessageSegment, enable_processor_state +from src.utils import semaphore_gather from .consts import ALLOW_R18_NODE if TYPE_CHECKING: from nonebot.typing import T_Handler + from src.service.artwork_proxy.add_ons.image_ops import ImageOpsMixin @@ -36,7 +39,7 @@ class ArtworkHandlerQueryArguments(BaseModel): model_config = ConfigDict(extra='ignore', coerce_numbers_to_str=True, from_attributes=True) -class ArtworkHandlerManager[T: "ImageOpsMixin"]: +class ArtworkHandlerManager[T: 'ImageOpsMixin']: """图站作品搜索预览等命令整合""" def __init__(self, artwork_class: type[T]): @@ -44,7 +47,7 @@ def __init__(self, artwork_class: type[T]): self._command_name = artwork_class.get_base_origin_name().lower() @staticmethod - async def _allow_r18_node_checker(interface: "OmMI") -> bool: + async def _allow_r18_node_checker(interface: 'OmMI') -> bool: """判断当前 entity 主体是否具有允许预览 r18 作品的权限""" if interface.matcher.plugin is None: return False @@ -59,7 +62,7 @@ async def _allow_r18_node_checker(interface: "OmMI") -> bool: ) @classmethod - async def _has_allow_r18_node(cls, interface: "OmMI") -> bool: + async def _has_allow_r18_node(cls, interface: 'OmMI') -> bool: """判断当前 entity 主体是否具有允许预览 r18 作品的权限""" try: allow_r18 = await cls._allow_r18_node_checker(interface=interface) @@ -71,7 +74,7 @@ async def _has_allow_r18_node(cls, interface: "OmMI") -> bool: @staticmethod def _get_query_argument_parser() -> ArgumentParser: """命令的参数解析器""" - parser = ArgumentParser(prog=f'作品查询命令参数解析', description='Parse artwork query arguments') + parser = ArgumentParser(prog='作品查询命令参数解析', description='Parse artwork query arguments') parser.add_argument('-r', '--random', action='store_true') parser.add_argument('-s', '--search', action='store_true') parser.add_argument('-p', '--page', type=int, default=1) @@ -142,7 +145,7 @@ async def _send_artworks_preview_message( else: await interface.send_reply(send_msg) - def generate_shell_handler(self) -> "T_Handler": + def generate_shell_handler(self) -> 'T_Handler': """生成插件命令流程函数以供注册""" async def _handler( @@ -160,7 +163,7 @@ async def _handler( allow_r18 = await self._has_allow_r18_node(interface=interface) no_blur_rating = 3 if allow_r18 else 1 - await interface.send_reply_auto_revoke('稍等, 正在获取作品信息~', 30) + await interface.send_reply('稍等, 正在获取作品信息~') try: if parsed_args.random: @@ -193,7 +196,7 @@ async def _handler( return _handler - def register_handler(self) -> "T_Handler": + def register_handler(self) -> 'T_Handler': """注册插件命令""" return on_shell_command( cmd=self._command_name, @@ -213,5 +216,5 @@ def register_handler(self) -> "T_Handler": __all__ = [ - "ArtworkHandlerManager", + 'ArtworkHandlerManager', ] diff --git a/src/plugins/omega_artwork_collection_updater/__init__.py b/src/plugins/omega_artwork_collection_updater/__init__.py index 5d99d951..ea54d981 100644 --- a/src/plugins/omega_artwork_collection_updater/__init__.py +++ b/src/plugins/omega_artwork_collection_updater/__init__.py @@ -20,6 +20,6 @@ ) from . import manual_update_tools as manual_update_tools -from . import scheduler as scheduler +from . import scheduled_tasks as tasks __all__ = [] diff --git a/src/plugins/omega_artwork_collection_updater/manual_update_tools.py b/src/plugins/omega_artwork_collection_updater/manual_update_tools.py index b4a8d7e8..67ec3614 100644 --- a/src/plugins/omega_artwork_collection_updater/manual_update_tools.py +++ b/src/plugins/omega_artwork_collection_updater/manual_update_tools.py @@ -26,14 +26,14 @@ from src.resource import TemporaryResource from src.service import enable_processor_state from src.service.artwork_collection import ALLOW_ARTWORK_ORIGIN, get_artwork_collection_type -from src.utils.process_utils import semaphore_gather +from src.utils import semaphore_gather class CustomImportArtwork(BaseModel): """手动导入/更新作品信息""" origin: ALLOW_ARTWORK_ORIGIN aid: str - classification: int = 3 + classification: int rating: int model_config = ConfigDict(extra='ignore', frozen=True, coerce_numbers_to_str=True) @@ -46,20 +46,33 @@ async def _get_custom_import_artworks_data_from_file() -> list[CustomImportArtwo return parse_json_as(list[CustomImportArtwork], data) -async def _import_artwork_into_database(artwork_data: CustomImportArtwork, log_index: int = -1) -> None: - collected_artwork = get_artwork_collection_type(origin=artwork_data.origin)(artwork_id=artwork_data.aid) +async def _import_artwork_into_database(import_data: CustomImportArtwork, log_index: int = -1) -> None: + collected_artwork = get_artwork_collection_type(origin=import_data.origin)(artwork_id=import_data.aid) try: + artwork_data = await collected_artwork.artwork_proxy.query() + classification = 1 if artwork_data.classification.value == 1 else import_data.classification + rating = 3 if artwork_data.rating.value == 3 else import_data.rating + await collected_artwork.add_and_upgrade_artwork_into_database( - classification=artwork_data.classification, rating=artwork_data.rating, force_update_mark=True + classification=classification, rating=rating, force_update_mark=True ) except WebSourceException as e: - # 网络问题有可能是风控/限流, 小概率是作品已经被删除, 反正这里 sleep 一下后再试试 + # 网络问题有可能是风控/限流, 小概率是作品已经被删除 + if e.status_code == 404: + raise e + logger.warning( - f'ImportCustomCollectedArtworks | 获取作品 {collected_artwork} 信息时发生异常, {e!r}, 60秒后重试') + f'ImportCustomCollectedArtworks | 获取作品 {collected_artwork} 信息时发生异常, {e!r}, 60秒后重试' + ) await async_sleep(60) + + artwork_data = await collected_artwork.artwork_proxy.query() + classification = 1 if artwork_data.classification.value == 1 else import_data.classification + rating = 3 if artwork_data.rating.value == 3 else import_data.rating + await collected_artwork.add_and_upgrade_artwork_into_database( - classification=artwork_data.classification, rating=artwork_data.rating, force_update_mark=True + classification=classification, rating=rating, force_update_mark=True ) if log_index % 10 == 0: @@ -87,7 +100,7 @@ async def handle_import_collected_artworks(matcher: Matcher) -> None: await matcher.finish('解析导入数据失败, 或导入文件不存在, 已取消操作, 详情请查看日志') import_tasks = [ - _import_artwork_into_database(artwork_data=x, log_index=index) + _import_artwork_into_database(import_data=x, log_index=index) for index, x in enumerate(artworks_data) ] await semaphore_gather(tasks=import_tasks, semaphore_num=8, return_exceptions=True) @@ -114,10 +127,10 @@ async def handle_artwork_collection_statistics( if origins is None: query_origin = None query_keywords = None - elif len(split_origins := origins.strip().split(maxsplit=1)) == 1: + elif len(split_origins := origins.strip().split()) == 1: query_origin = split_origins[0] query_keywords = None - elif len(split_origins := origins.strip().split(maxsplit=1)) > 1: + elif len(split_origins := origins.strip().split()) > 1: query_origin = split_origins[0] query_keywords = split_origins[1:] else: @@ -134,7 +147,7 @@ async def handle_artwork_collection_statistics( prefix_text = f'本地数据库{origins if origins is not None else "全量"!r}统计信息:' classification_text = ( f'-- Classification --\n' - f'Unknown: {classification_statistic.unknown}\n' + f'Unused: {classification_statistic.unused}\n' f'Unclassified: {classification_statistic.unclassified}\n' f'AIGenerated: {classification_statistic.ai_generated}\n' f'Automatic: {classification_statistic.automatic}\n' diff --git a/src/plugins/omega_artwork_collection_updater/scheduler.py b/src/plugins/omega_artwork_collection_updater/scheduled_tasks.py similarity index 98% rename from src/plugins/omega_artwork_collection_updater/scheduler.py rename to src/plugins/omega_artwork_collection_updater/scheduled_tasks.py index dc13902e..1bd50636 100644 --- a/src/plugins/omega_artwork_collection_updater/scheduler.py +++ b/src/plugins/omega_artwork_collection_updater/scheduled_tasks.py @@ -1,7 +1,7 @@ """ @Author : Ailitonia @Date : 2024/8/17 下午7:02 -@FileName : scheduler +@FileName : scheduled_tasks @Project : nonebot2_miya @Description : 更新任务管理 @GitHub : https://github.com/Ailitonia diff --git a/src/plugins/omega_artwork_collection_updater/sites/booru_artwork.py b/src/plugins/omega_artwork_collection_updater/sites/booru_artwork.py index 65619f62..a31c4ba6 100644 --- a/src/plugins/omega_artwork_collection_updater/sites/booru_artwork.py +++ b/src/plugins/omega_artwork_collection_updater/sites/booru_artwork.py @@ -9,7 +9,8 @@ """ import random -from typing import TYPE_CHECKING, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING from src.service.artwork_collection import ( DanbooruArtworkCollection, @@ -21,34 +22,34 @@ KonachanSafeArtworkProxy, YandereArtworkProxy, ) -from src.utils.process_utils import semaphore_gather +from src.utils import semaphore_gather if TYPE_CHECKING: from src.service.artwork_collection.typing import ArtworkCollectionType from src.service.artwork_proxy.typing import ProxiedArtwork -class BooruArtworksUpdater(object): +class BooruArtworksUpdater: """自动更新较高评价的 booru 系图站作品 Tips: danbooru 图比较杂, 使用 score:>600 筛选还算可以的作品 - gelbooru 平均水平惨不忍睹, 且限制搜索条件, 略 + gelbooru 平均水平惨不忍睹, 没有通用的搜索条件, 略 konachan 和 yandere 的图整体较好, 但请求限制相对较严 """ @staticmethod async def _add_artwork_into_database( - ac_t: "ArtworkCollectionType", - artworks: Sequence["ProxiedArtwork"], - semaphore_num: int = 10, + ac_t: 'ArtworkCollectionType', + artworks: Sequence['ProxiedArtwork'], + semaphore_num: int = 4, ) -> None: tasks = [ac_t(x.s_aid).add_artwork_into_database_ignore_exists() for x in artworks] await semaphore_gather(tasks=tasks, semaphore_num=semaphore_num, return_exceptions=False) @classmethod async def update_danbooru_high_score_sfw_artworks(cls) -> None: - random_result = await DanbooruArtworkProxy.search('status:active is:sfw score:>600 random:30 limit:20') + random_result = await DanbooruArtworkProxy.search('status:active is:sfw score:>600 order:random limit:20') top_result = await DanbooruArtworkProxy.search('status:active is:sfw score:>500 limit:20', page=random.randint(1, 10)) await cls._add_artwork_into_database(DanbooruArtworkCollection, random_result + top_result) diff --git a/src/plugins/omega_artwork_collection_updater/sites/local_collected_artwork.py b/src/plugins/omega_artwork_collection_updater/sites/local_collected_artwork.py index d56c91b1..7646fbf0 100644 --- a/src/plugins/omega_artwork_collection_updater/sites/local_collected_artwork.py +++ b/src/plugins/omega_artwork_collection_updater/sites/local_collected_artwork.py @@ -10,7 +10,7 @@ from src.service.artwork_collection import LocalCollectedArtworkCollection from src.service.artwork_proxy import LocalCollectedArtworkProxy -from src.utils.process_utils import semaphore_gather +from src.utils import semaphore_gather class LocalCollectedArtworkUpdater: diff --git a/src/plugins/omega_artwork_collection_updater/sites/lolicon_api.py b/src/plugins/omega_artwork_collection_updater/sites/lolicon_api.py index 5e372b04..4ba20a62 100644 --- a/src/plugins/omega_artwork_collection_updater/sites/lolicon_api.py +++ b/src/plugins/omega_artwork_collection_updater/sites/lolicon_api.py @@ -8,13 +8,12 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Literal, Optional +from typing import TYPE_CHECKING, Literal from pydantic import BaseModel, ConfigDict from src.service.artwork_collection import PixivArtworkCollection -from src.utils.common_api import BaseCommonAPI -from src.utils.process_utils import semaphore_gather +from src.utils import BaseCommonAPI, semaphore_gather if TYPE_CHECKING: from nonebot.internal.driver import CookieTypes, HeaderTypes @@ -41,7 +40,7 @@ class LoliconSetu(BaseLoliconModel): class LoliconAPIReturn(BaseLoliconModel): - error: Optional[str] = None + error: str | None = None data: list[LoliconSetu] @@ -61,11 +60,11 @@ def _load_cloudflare_clearance(cls) -> bool: return False @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None @classmethod @@ -92,7 +91,7 @@ async def update_lolicon_setu(cls) -> None: """从 lolicon API 获取涩图数据并导入数据库""" setu_data = await cls._query_setu(r18=2, num=20) tasks = [cls._add_lolicon_setu_into_database(PixivArtworkCollection(artwork_id=x.pid)) for x in setu_data.data] - await semaphore_gather(tasks=tasks, semaphore_num=10, return_exceptions=False) + await semaphore_gather(tasks=tasks, semaphore_num=8, return_exceptions=False) __all__ = [ diff --git a/src/plugins/omega_artwork_collection_updater/sites/pixiv.py b/src/plugins/omega_artwork_collection_updater/sites/pixiv.py index 27a94498..818c8507 100644 --- a/src/plugins/omega_artwork_collection_updater/sites/pixiv.py +++ b/src/plugins/omega_artwork_collection_updater/sites/pixiv.py @@ -9,18 +9,18 @@ """ from asyncio import sleep as async_sleep -from typing import Sequence +from collections.abc import Sequence from src.service.artwork_collection import PixivArtworkCollection +from src.utils import semaphore_gather from src.utils.pixiv_api import PixivArtwork -from src.utils.process_utils import semaphore_gather -class PixivArtworkUpdater(object): +class PixivArtworkUpdater: """自动从 Pixiv 发现/推荐更新作品""" @staticmethod - async def _add_artwork_into_database(pids: Sequence[int], semaphore_num: int = 10) -> None: + async def _add_artwork_into_database(pids: Sequence[int], semaphore_num: int = 8) -> None: tasks = [PixivArtworkCollection(x).add_artwork_into_database_ignore_exists() for x in pids] await semaphore_gather(tasks=tasks, semaphore_num=semaphore_num, return_exceptions=False) diff --git a/src/plugins/omega_core_manager/__init__.py b/src/plugins/omega_core_manager/__init__.py index 6b50f5f0..0e9d33ad 100644 --- a/src/plugins/omega_core_manager/__init__.py +++ b/src/plugins/omega_core_manager/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='OmegaCoreManager', description='【Omega 机器人核心管理插件】\n' @@ -37,5 +36,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/omega_core_manager/command.py b/src/plugins/omega_core_manager/command.py index 1d91fe70..609d1e73 100644 --- a/src/plugins/omega_core_manager/command.py +++ b/src/plugins/omega_core_manager/command.py @@ -11,18 +11,19 @@ from datetime import timedelta from typing import Annotated -from nonebot.adapters import Bot, Event, Message +from nonebot.internal.adapter import Bot, Event, Message from nonebot.log import logger from nonebot.matcher import Matcher from nonebot.params import ArgStr, CommandArg, Depends from nonebot.permission import SUPERUSER -from nonebot.plugin import CommandGroup, get_plugin, get_loaded_plugins +from nonebot.plugin import CommandGroup, get_loaded_plugins, get_plugin from nonebot.typing import T_State from src.database import PluginDAL from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state -from .helpers import get_all_plugins_desc, get_plugin_desc, get_plugin_auth_node, list_command_by_priority +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state +from .helpers import get_all_plugins_desc, get_plugin_auth_node, get_plugin_desc, list_command_by_priority from .status import get_status DEFAULT_PERMISSION_LEVEL: int = 30 @@ -192,8 +193,12 @@ async def handle_enable_plugin( await matcher.finish(f'插件{plugin_name!r}未加载, 操作已取消') try: - plugin = await plugin_dal.query_unique(plugin_name=plugin_name, module_name=imported_plugin.module_name) - await plugin_dal.update(id_=plugin.id, enabled=1, info='Enabled by OPM') + await plugin_dal.upsert( + plugin_name=plugin_name, + module_name=imported_plugin.module_name, + enabled=1, + info='Enabled by OPM', + ) await plugin_dal.commit_session() logger.success(f'Omega 启用插件{plugin_name!r}成功') @@ -219,8 +224,12 @@ async def handle_disable_plugin( await matcher.finish(f'插件{plugin_name!r}未加载, 操作已取消') try: - plugin = await plugin_dal.query_unique(plugin_name=plugin_name, module_name=imported_plugin.module_name) - await plugin_dal.update(id_=plugin.id, enabled=0, info='Disabled by OPM') + await plugin_dal.upsert( + plugin_name=plugin_name, + module_name=imported_plugin.module_name, + enabled=0, + info='Disabled by OPM', + ) await plugin_dal.commit_session() logger.success(f'Omega 禁用插件{plugin_name!r}成功') diff --git a/src/plugins/omega_core_manager/helpers.py b/src/plugins/omega_core_manager/helpers.py index db4b733c..516760eb 100644 --- a/src/plugins/omega_core_manager/helpers.py +++ b/src/plugins/omega_core_manager/helpers.py @@ -65,9 +65,9 @@ def get_plugin_auth_node(plugin_name: str) -> list[str]: ) if s.auth_node is not None] # 如果有 extra_auth_node 也加入到可配置的权限节点中 - nodes.extend((extra_node for s in ( + nodes.extend(extra_node for s in ( parse_processor_state(m._default_state) for m in plugin.matcher - ) if s.extra_auth_node for extra_node in s.extra_auth_node)) + ) if s.extra_auth_node for extra_node in s.extra_auth_node) # 如果有冷却配置就把跳过冷却的权限加入到可配置的权限节点中 if any(s.cooldown for s in (parse_processor_state(m._default_state) for m in plugin.matcher)): @@ -103,7 +103,7 @@ def list_command_by_priority() -> str: priority_info: str = '' for priority in sorted(priority_map): - matcher_info = "\n".join(sorted(priority_map[priority].copy())) + matcher_info = '\n'.join(sorted(priority_map[priority].copy())) priority_info += f'[Priority - {priority}]\n{matcher_info}\n\n' return priority_info.strip() diff --git a/src/plugins/omega_core_manager/status.py b/src/plugins/omega_core_manager/status.py index e24ba825..d0eaf139 100644 --- a/src/plugins/omega_core_manager/status.py +++ b/src/plugins/omega_core_manager/status.py @@ -12,7 +12,7 @@ import asyncio from datetime import datetime -from typing import TYPE_CHECKING, Dict, Optional +from typing import TYPE_CHECKING, Optional import psutil from nonebot import get_driver @@ -27,7 +27,7 @@ # bot status _nonebot_run_time: datetime -_bot_connect_time: Dict[str, datetime] = {} +_bot_connect_time: dict[str, datetime] = {} @driver.on_startup @@ -41,7 +41,7 @@ def get_nonebot_run_time() -> datetime: try: return _nonebot_run_time except NameError: - raise RuntimeError("NoneBot not running!") from None + raise RuntimeError('NoneBot not running!') from None async def get_cpu_status() -> float: @@ -61,14 +61,14 @@ def get_swap_status(): return psutil.swap_memory() -def _get_disk_usage(path: str) -> Optional["sdiskusage"]: +def _get_disk_usage(path: str) -> Optional['sdiskusage']: try: return psutil.disk_usage(path) except Exception as e: - logger.warning(f"Could not get disk usage for {path}: {e!r}") + logger.warning(f'Could not get disk usage for {path}: {e!r}') -def get_disk_usage() -> Dict[str, "sdiskusage"]: +def get_disk_usage() -> dict[str, 'sdiskusage']: """Get the disk usage status.""" disk_parts = psutil.disk_partitions() return { diff --git a/src/plugins/omega_email/__init__.py b/src/plugins/omega_email/__init__.py index 4b45011b..29e4124f 100644 --- a/src/plugins/omega_email/__init__.py +++ b/src/plugins/omega_email/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='收邮件', description='【OmegaEmail 邮箱插件】\n' @@ -26,5 +25,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/omega_email/command.py b/src/plugins/omega_email/command.py index f5937327..5d8d1536 100644 --- a/src/plugins/omega_email/command.py +++ b/src/plugins/omega_email/command.py @@ -17,12 +17,22 @@ from nonebot.permission import SUPERUSER from nonebot.plugin import CommandGroup from nonebot.rule import to_me -from sqlalchemy.exc import NoResultFound -from src.database import EmailBoxDAL -from src.params.handler import get_command_str_single_arg_parser_handler, get_command_str_multi_args_parser_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state -from .helpers import check_mailbox, get_unseen_mail_data, encrypt_password, decrypt_password, generate_mail_snapshot +from src.params.handler import get_command_str_multi_args_parser_handler, get_command_str_single_arg_parser_handler +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state +from .helpers import ( + bind_entity_mailbox, + check_mailbox, + decrypt_password, + encrypt_password, + generate_mail_snapshot, + get_entity_bound_mailbox, + get_unseen_mail_data, + query_available_mailbox, + save_mailbox, + unbind_entity_mailbox, +) mailbox_manager = CommandGroup( 'mailbox-manager', @@ -45,7 +55,6 @@ @add_mail_box.got('add_mailbox_arg_2', prompt='请输入邮箱密码:') async def handle_add_mailbox( matcher: Matcher, - email_dal: Annotated[EmailBoxDAL, Depends(EmailBoxDAL.dal_dependence)], server_host: Annotated[str, ArgStr('add_mailbox_arg_0')], address: Annotated[str, ArgStr('add_mailbox_arg_1')], password: Annotated[str, ArgStr('add_mailbox_arg_2')], @@ -60,12 +69,7 @@ async def handle_add_mailbox( try: password = await encrypt_password(plaintext=password) - try: - mailbox = await email_dal.query_unique(address=address) - await email_dal.update(id_=mailbox.id, server_host=server_host, password=password) - except NoResultFound: - await email_dal.add(address=address, server_host=server_host, password=password, protocol='imap', port=993) - await email_dal.commit_session() + await save_mailbox(address=address, server_host=server_host, password=password, protocol='imap', port=993) logger.success(f'EmailBoxManager | 添加邮箱: {address} 成功') await matcher.send(f'添加邮箱 {address} 成功') except Exception as e: @@ -80,11 +84,10 @@ async def handle_add_mailbox( ).got('mailbox_address', prompt='请输入需要绑定的邮箱地址:') async def handle_bind_mailbox( interface: Annotated[OmMI, Depends(OmMI.depend())], - email_dal: Annotated[EmailBoxDAL, Depends(EmailBoxDAL.dal_dependence)], mailbox_address: Annotated[str | None, ArgStr('mailbox_address')], ) -> None: try: - available_mailbox = await email_dal.query_all() + available_mailbox = await query_available_mailbox() except Exception as e: logger.error(f'EmailBoxManager | 查询可用邮箱失败, {e}') await interface.finish_reply('查询可用邮箱失败, 详情请查看日志') @@ -102,10 +105,7 @@ async def handle_bind_mailbox( await interface.finish_reply(f'{mailbox_address} 不是可用的邮箱地址, 请确认后重试或请管理员添加该邮箱') try: - await interface.entity.bind_email_box( - email_box=available_mailbox_map.get(mailbox_address), # type: ignore - bind_info=f'{interface.entity.entity_name}-{mailbox_address}' - ) + await bind_entity_mailbox(interface=interface, mailbox_account=available_mailbox_map[mailbox_address]) await interface.entity.commit_session() logger.success(f'EmailBoxManager | 绑定邮箱: {mailbox_address} 成功') await interface.send_reply(f'绑定邮箱 {mailbox_address} 成功') @@ -124,7 +124,7 @@ async def handle_unbind_mailbox( mailbox_address: Annotated[str | None, ArgStr('mailbox_address')], ) -> None: try: - bound_mailbox = await interface.entity.query_bound_email_box() + bound_mailbox = await get_entity_bound_mailbox(interface=interface) except Exception as e: logger.error(f'EmailBoxManager | 查询已绑定邮箱失败, {e}') await interface.finish_reply('查询已绑定邮箱失败, 详情请查看日志') @@ -142,7 +142,7 @@ async def handle_unbind_mailbox( await interface.finish_reply(f'{mailbox_address} 不是已绑定的邮箱地址, 请确认后重试') try: - await interface.entity.unbind_email_box(email_box=bound_mailbox_map.get(mailbox_address)) # type: ignore + await unbind_entity_mailbox(interface=interface, mailbox_address=mailbox_address) await interface.entity.commit_session() logger.success(f'EmailBoxManager | 解绑邮箱: {mailbox_address} 成功') await interface.send_reply(f'解绑邮箱 {mailbox_address} 成功') @@ -160,13 +160,13 @@ async def handle_unbind_mailbox( ).handle() async def handle_receive_email(interface: Annotated[OmMI, Depends(OmMI.depend())]) -> None: try: - bound_mailbox = await interface.entity.query_bound_email_box() + bound_mailbox = await get_entity_bound_mailbox(interface=interface) except Exception as e: logger.error(f'ReceiveEmail | 查询已绑定邮箱失败, {e}') await interface.finish_reply('查询已绑定邮箱失败, 请稍后重试或联系管理员处理') if not bound_mailbox: - logger.warning('ReceiveEmail | 收邮件失败, 没有绑定的邮箱') + logger.warning('ReceiveEmail | 收邮件结束, 没有绑定的邮箱') await interface.finish_reply('没有绑定的邮箱, 请先联系管理员绑定邮箱后再收件') bound_mailbox_msg = '\n'.join(x.address for x in bound_mailbox) @@ -175,7 +175,7 @@ async def handle_receive_email(interface: Annotated[OmMI, Depends(OmMI.depend()) for mailbox in bound_mailbox: # 解密密码 try: - password = await decrypt_password(ciphertext=mailbox.password) + password = await decrypt_password(ciphertext=mailbox.server.password) except Exception as e: logger.error(f'ReceiveEmail | 邮箱 {mailbox.address} 密码验证失败, {e}') await interface.send_reply(f'邮箱: {mailbox.address}\n密码验证失败, 请联系管理员处理') @@ -184,7 +184,7 @@ async def handle_receive_email(interface: Annotated[OmMI, Depends(OmMI.depend()) # 接收邮件内容 try: unseen_mail = await get_unseen_mail_data( - address=mailbox.address, server_host=mailbox.server_host, password=password + address=mailbox.address, server_host=mailbox.server.server_host, password=password ) except Exception as e: logger.error(f'ReceiveEmail | 邮箱 {mailbox.address} 收件失败, {e}') diff --git a/src/plugins/omega_email/consts.py b/src/plugins/omega_email/consts.py new file mode 100644 index 00000000..01a31ac4 --- /dev/null +++ b/src/plugins/omega_email/consts.py @@ -0,0 +1,29 @@ +""" +@Author : Ailitonia +@Date : 2024/12/30 16:54:04 +@FileName : consts.py +@Project : omega-miya +@Description : 邮件插件常量 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Literal + +from src.resource import TemporaryResource + +DB_MAILBOX_ACCOUNT_SETTING_NAME: Literal['omega_email_mailbox'] = 'omega_email_mailbox' +"""数据库存放邮箱账号系统配置项名称""" +DB_ENTITY_SETTING_MODULE_NAME: Literal['omega_email'] = 'omega_email' +DB_ENTITY_SETTING_PLUGIN_NAME: Literal['omega_email_mailbox_bind'] = 'omega_email_mailbox_bind' +"""实体对象配置项名称""" +TMP_FOLDER: TemporaryResource = TemporaryResource('receive_email') +"""已收邮件图片缓存路径""" + + +__all__ = [ + 'DB_MAILBOX_ACCOUNT_SETTING_NAME', + 'DB_ENTITY_SETTING_MODULE_NAME', + 'DB_ENTITY_SETTING_PLUGIN_NAME', + 'TMP_FOLDER', +] diff --git a/src/plugins/omega_email/helpers.py b/src/plugins/omega_email/helpers.py index b715fbd2..f1815ada 100644 --- a/src/plugins/omega_email/helpers.py +++ b/src/plugins/omega_email/helpers.py @@ -9,21 +9,127 @@ """ from datetime import datetime +from typing import TYPE_CHECKING from nonebot.log import logger from nonebot.utils import run_sync +from pydantic import BaseModel, ConfigDict -from src.resource import TemporaryResource -from src.utils.encrypt import AESEncrypter +from src.database import SystemSettingDAL, begin_db_session +from src.utils.crypto import AESEncryptor from src.utils.image_utils import ImageUtils -from .imap import Email, ImapMailbox - -_TMP_FOLDER: TemporaryResource = TemporaryResource('receive_email') -"""已收邮件图片缓存路径""" +from .consts import ( + DB_ENTITY_SETTING_MODULE_NAME, + DB_ENTITY_SETTING_PLUGIN_NAME, + DB_MAILBOX_ACCOUNT_SETTING_NAME, + TMP_FOLDER, +) +from .mailbox import Email, ImapMailbox + +if TYPE_CHECKING: + from src.resource import TemporaryResource + from src.service import OmegaMatcherInterface as OmMI + + +class BaseMailboxModel(BaseModel): + """邮箱数据基类""" + model_config = ConfigDict(extra='ignore', coerce_numbers_to_str=True, from_attributes=True, frozen=True) + + +class MailboxServer(BaseMailboxModel): + """邮箱账号数据""" + server_host: str + password: str + protocol: str + port: int + + +class MailboxAccount(BaseMailboxModel): + """数据库存储的邮箱账号数据""" + address: str + server: MailboxServer + + +async def save_mailbox(address: str, server_host: str, password: str, protocol: str, port: int) -> None: + """向数据库写入邮箱账号数据""" + mail_server = MailboxServer(server_host=server_host, password=password, protocol=protocol, port=port) + async with begin_db_session() as session: + await SystemSettingDAL(session=session).upsert( + setting_name=DB_MAILBOX_ACCOUNT_SETTING_NAME, + setting_key=address, + setting_value=mail_server.model_dump_json(), + ) + + +async def load_mailbox(mailbox_address: str) -> MailboxAccount: + """从数据库读取邮箱账号数据""" + async with begin_db_session() as session: + mailbox_server = await SystemSettingDAL(session=session).query_unique( + setting_name=DB_MAILBOX_ACCOUNT_SETTING_NAME, + setting_key=mailbox_address, + ) + return MailboxAccount.model_validate({ + 'address': mailbox_server.setting_key, + 'server': MailboxServer.model_validate_json(mailbox_server.setting_value), + }) + + +async def query_available_mailbox() -> list[MailboxAccount]: + """从数据库读取可用邮箱账号列表""" + async with begin_db_session() as session: + available_mailbox = await SystemSettingDAL(session=session).query_series( + setting_name=DB_MAILBOX_ACCOUNT_SETTING_NAME, + ) + return [ + MailboxAccount.model_validate({ + 'address': x.setting_key, + 'server': MailboxServer.model_validate_json(x.setting_value), + }) + for x in available_mailbox + ] + + +async def bind_entity_mailbox(interface: 'OmMI', mailbox_account: MailboxAccount) -> None: + """为实体对象绑定邮箱""" + return await interface.entity.set_auth_setting( + module=DB_ENTITY_SETTING_MODULE_NAME, + plugin=DB_ENTITY_SETTING_PLUGIN_NAME, + node=mailbox_account.address, + available=1, + value=mailbox_account.server.model_dump_json(), + ) + + +async def unbind_entity_mailbox(interface: 'OmMI', mailbox_address: str) -> None: + """为实体对象解绑邮箱""" + return await interface.entity.set_auth_setting( + module=DB_ENTITY_SETTING_MODULE_NAME, + plugin=DB_ENTITY_SETTING_PLUGIN_NAME, + node=mailbox_address, + available=0, + value='', + ) + + +async def get_entity_bound_mailbox(interface: 'OmMI') -> list[MailboxAccount]: + """为实体对象解绑邮箱""" + bound_mailbox = await interface.entity.query_plugin_all_auth_setting( + module=DB_ENTITY_SETTING_MODULE_NAME, + plugin=DB_ENTITY_SETTING_PLUGIN_NAME, + ) + return [ + MailboxAccount.model_validate({ + 'address': x.node, + 'server': MailboxServer.model_validate_json(x.value), + }) + for x in bound_mailbox + if (x.available == 1) and x.value + ] @run_sync def check_mailbox(address: str, server_host: str, password: str) -> bool: + """检查邮箱状态""" try: ImapMailbox(host=server_host, address=address, password=password).check() return True @@ -34,6 +140,7 @@ def check_mailbox(address: str, server_host: str, password: str) -> bool: @run_sync def get_unseen_mail_data(address: str, server_host: str, password: str) -> list[Email]: + """获取未读邮件列表""" mail = ImapMailbox(host=server_host, address=address, password=password) unseen_mails = mail.get_mail_list(None, 'UNSEEN') result = [x for x in unseen_mails] @@ -42,12 +149,12 @@ def get_unseen_mail_data(address: str, server_host: str, password: str) -> list[ @run_sync def encrypt_password(plaintext: str) -> str: - return AESEncrypter().ecb_encrypt(plaintext) + return AESEncryptor().ecb_encrypt(plaintext) @run_sync def decrypt_password(ciphertext: str) -> str: - return AESEncrypter().ecb_decrypt(ciphertext) + return AESEncryptor().ecb_decrypt(ciphertext) @run_sync @@ -55,12 +162,19 @@ def _generate_mail_snapshot(mail_content: str) -> ImageUtils: return ImageUtils.init_from_text(text=mail_content) -async def generate_mail_snapshot(mail_content: str) -> TemporaryResource: +async def generate_mail_snapshot(mail_content: str) -> 'TemporaryResource': + """生成邮件快照图片""" image = await _generate_mail_snapshot(mail_content=mail_content) - return await image.save(_TMP_FOLDER(f'mail_{datetime.now().strftime("%Y%m%d%H%M%S")}_{hash(mail_content)}.jpg')) + return await image.save(TMP_FOLDER(f'mail_{datetime.now().strftime("%Y%m%d%H%M%S")}_{hash(mail_content)}.jpg')) __all__ = [ + 'save_mailbox', + 'load_mailbox', + 'query_available_mailbox', + 'bind_entity_mailbox', + 'unbind_entity_mailbox', + 'get_entity_bound_mailbox', 'check_mailbox', 'get_unseen_mail_data', 'encrypt_password', diff --git a/src/plugins/omega_email/imap.py b/src/plugins/omega_email/mailbox.py similarity index 95% rename from src/plugins/omega_email/imap.py rename to src/plugins/omega_email/mailbox.py index 884962ba..a3badc4e 100644 --- a/src/plugins/omega_email/imap.py +++ b/src/plugins/omega_email/mailbox.py @@ -1,7 +1,7 @@ """ @Author : Ailitonia @Date : 2022/04/28 20:26 -@FileName : imap.py +@FileName : mailbox.py @Project : nonebot2_miya @Description : imap 协议处理模块 @GitHub : https://github.com/Ailitonia @@ -83,7 +83,7 @@ def get_mail_list(self, charset, *criteria) -> list[Email]: if self.__address.endswith('@163.com'): # 添加163邮箱 IMAP ID 验证 imaplib.Commands['ID'] = ('AUTH',) - args = ("name", "omega", "contact", "omega_miya@163.com", "version", "1.0.2", "vendor", "pyimaplibclient") + args = ('name', 'omega', 'contact', 'omega_miya@163.com', 'version', '1.0.2', 'vendor', 'pyimaplibclient') typ, dat = self.__mail._simple_command('ID', '("' + '" "'.join(args) + '")') self.__mail._untagged_response(typ, dat, 'ID') @@ -131,10 +131,10 @@ def get_mail_list(self, charset, *criteria) -> list[Email]: content_text = str(part_content) # 根据内容形式进一步解析处理 - if part.get_content_type() == "text/plain": + if part.get_content_type() == 'text/plain': content_text = content_text.replace(r' ', '\n') body_list.append(content_text) - elif part.get_content_type() == "text/html": + elif part.get_content_type() == 'text/html': content_text = re.sub(re.compile(r'', re.IGNORECASE), '\n', content_text) html_ = etree.HTML(content_text) html_list.append(''.join(text for text in html_.itertext())) diff --git a/src/plugins/omega_scheduled_message/__init__.py b/src/plugins/omega_scheduled_message/__init__.py index 5a7ba192..fee4258f 100644 --- a/src/plugins/omega_scheduled_message/__init__.py +++ b/src/plugins/omega_scheduled_message/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='定时消息', description='【定时消息插件】\n' @@ -27,5 +26,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/omega_scheduled_message/command.py b/src/plugins/omega_scheduled_message/command.py index 846f4898..d8ae4aab 100644 --- a/src/plugins/omega_scheduled_message/command.py +++ b/src/plugins/omega_scheduled_message/command.py @@ -10,20 +10,21 @@ from typing import Annotated -from nonebot.adapters import Message +from nonebot.internal.adapter import Message as BaseMessage from nonebot.log import logger from nonebot.params import Arg, ArgStr, Depends from nonebot.plugin import CommandGroup from src.params.handler import get_command_message_arg_parser_handler from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageTransfer, enable_processor_state from .helpers import ( add_schedule_job, generate_schedule_job_data, get_schedule_message_job_list, + remove_schedule_message_job, set_schedule_message_job, - remove_schedule_message_job ) schedule_message = CommandGroup( @@ -49,14 +50,14 @@ async def handle_set_schedule_message( interface: Annotated[OmMI, Depends(OmMI.depend())], job_name: Annotated[str, ArgStr('job_name')], crontab: Annotated[str, ArgStr('crontab')], - message: Annotated[Message, Arg('message')], + message: Annotated[BaseMessage, Arg('message')], ) -> None: job_name = job_name.strip() if len(job_name) > 50: await interface.reject_arg_reply('job_name', '设置的定时消息任务名称过长(超过50字), 请重新输入:') try: - parsed_message = interface.get_message_extractor()(message=message).message + parsed_message = await OmegaMessageTransfer(interface=interface, origin_message=message).dumps() job_data = await generate_schedule_job_data( interface=interface, job_name=job_name, crontab=crontab, message=parsed_message ) diff --git a/src/plugins/omega_scheduled_message/helpers.py b/src/plugins/omega_scheduled_message/helpers.py index 90063c12..22ef90e2 100644 --- a/src/plugins/omega_scheduled_message/helpers.py +++ b/src/plugins/omega_scheduled_message/helpers.py @@ -16,12 +16,8 @@ from src.compat import parse_json_as from src.database import AuthSettingDAL, begin_db_session -from src.service import ( - OmegaEntityInterface as OmEI, - OmegaEntity, - OmegaMessage, - scheduler -) +from src.service import OmegaEntity, OmegaMessage, scheduler +from src.service import OmegaEntityInterface as OmEI from .model import SCHEDULE_MESSAGE_CUSTOM_MODULE_NAME, SCHEDULE_MESSAGE_CUSTOM_PLUGIN_NAME, ScheduleMessageJob if TYPE_CHECKING: @@ -80,7 +76,7 @@ async def _init_schedule_message_job() -> None: async def generate_schedule_job_data( - interface: "OmegaMatcherInterface", + interface: 'OmegaMatcherInterface', job_name: str, crontab: str, message: OmegaMessage @@ -97,7 +93,7 @@ async def generate_schedule_job_data( return ScheduleMessageJob.model_validate(job_data) -async def get_schedule_message_job_list(interface: "OmegaMatcherInterface") -> list[str]: +async def get_schedule_message_job_list(interface: 'OmegaMatcherInterface') -> list[str]: """获取数据库中 Event 对应 Entity 的全部定时任务名称""" all_jobs = await interface.entity.query_plugin_all_auth_setting( module=SCHEDULE_MESSAGE_CUSTOM_MODULE_NAME, plugin=SCHEDULE_MESSAGE_CUSTOM_PLUGIN_NAME @@ -110,7 +106,7 @@ async def get_schedule_message_job_list(interface: "OmegaMatcherInterface") -> l return job_list -async def set_schedule_message_job(interface: "OmegaMatcherInterface", job_data: ScheduleMessageJob) -> None: +async def set_schedule_message_job(interface: 'OmegaMatcherInterface', job_data: ScheduleMessageJob) -> None: """在数据库中新增或更新 Event 对应 Entity 的定时任务信息""" await interface.entity.set_auth_setting( module=SCHEDULE_MESSAGE_CUSTOM_MODULE_NAME, @@ -121,7 +117,7 @@ async def set_schedule_message_job(interface: "OmegaMatcherInterface", job_data: ) -async def remove_schedule_message_job(interface: "OmegaMatcherInterface", job_name: str) -> None: +async def remove_schedule_message_job(interface: 'OmegaMatcherInterface', job_name: str) -> None: """在数据库中停用 Event 对应 Entity 的定时任务信息""" job_setting = await interface.entity.query_auth_setting( module=SCHEDULE_MESSAGE_CUSTOM_MODULE_NAME, diff --git a/src/plugins/omega_sign_in/__init__.py b/src/plugins/omega_sign_in/__init__.py index a084d237..bafaf10a 100644 --- a/src/plugins/omega_sign_in/__init__.py +++ b/src/plugins/omega_sign_in/__init__.py @@ -15,7 +15,7 @@ __plugin_meta__ = PluginMetadata( name='签到', description='【OmegaSignIn 签到插件】\n' - "轻量化签到插件, 好感度系统基础", + '轻量化签到插件, 好感度系统基础', usage='/签到\n' '/老黄历|好感度|一言\n' '/补签\n\n' @@ -27,5 +27,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/omega_sign_in/command.py b/src/plugins/omega_sign_in/command.py index 79e0fddd..015762c8 100644 --- a/src/plugins/omega_sign_in/command.py +++ b/src/plugins/omega_sign_in/command.py @@ -10,11 +10,9 @@ from nonebot import get_driver from nonebot.adapters.onebot.v11.bot import Bot as OneBotV11Bot -from nonebot.adapters.onebot.v11.event import ( - PokeNotifyEvent as OneBotV11PokeNotifyEvent, - GroupMessageEvent as OneBotV11GroupMessageEvent, - PrivateMessageEvent as OneBotV11PrivateMessageEvent -) +from nonebot.adapters.onebot.v11.event import GroupMessageEvent as OneBotV11GroupMessageEvent +from nonebot.adapters.onebot.v11.event import PokeNotifyEvent as OneBotV11PokeNotifyEvent +from nonebot.adapters.onebot.v11.event import PrivateMessageEvent as OneBotV11PrivateMessageEvent from nonebot.adapters.onebot.v11.message import Message as OneBotV11Message from nonebot.log import logger from nonebot.message import handle_event @@ -25,7 +23,7 @@ from src.params.rule import event_has_permission_level from src.service import enable_processor_state from .config import sign_in_config -from .handlers import handle_generate_fortune_card, handle_generate_sign_in_card, handle_fix_sign_in +from .handlers import handle_fix_sign_in, handle_generate_fortune_card, handle_generate_sign_in_card _COMMAND_START: set[str] = get_driver().config.command_start _DEFAULT_COMMAND_START: str = list(_COMMAND_START)[0] if _COMMAND_START else '' @@ -56,7 +54,7 @@ # 针对 OneBot V11 的戳一戳事件进行特殊处理 @on_notice( rule=to_me() & event_has_permission_level(level=20), - state=enable_processor_state(name='OmegaPokeSignIn', echo_processor_result=False), + state=enable_processor_state(name='OmegaPokeSignIn', enable_processor=False, echo_processor_result=False), priority=11, block=False ).handle() @@ -71,10 +69,8 @@ async def handle_poke_sign_in(bot: OneBotV11Bot, event: OneBotV11PokeNotifyEvent 'user_id': event.user_id, 'nickname': sender_data.get('nickname'), 'sex': sender_data.get('sex'), - 'age': sender_data.get('age'), 'card': sender_data.get('card'), 'area': sender_data.get('area'), - 'level': sender_data.get('level'), 'role': sender_data.get('role'), 'title': sender_data.get('title') } diff --git a/src/plugins/omega_sign_in/config.py b/src/plugins/omega_sign_in/config.py index 68cea88f..68f8d6e6 100644 --- a/src/plugins/omega_sign_in/config.py +++ b/src/plugins/omega_sign_in/config.py @@ -9,7 +9,6 @@ """ from dataclasses import dataclass -from typing import Optional from nonebot import get_plugin_config, logger from pydantic import BaseModel, ConfigDict, ValidationError @@ -28,7 +27,7 @@ class SignInConfig(BaseModel): # 签到头图图库来源, 可配置: pixiv, danbooru, gelbooru, konachan, yandere, local # 特别的: 当配置为 `None` 时, 代表从所有的来源随机获取 # 配置后需要数据库里面有图才能正常获取到 - signin_plugin_top_image_origin: Optional[ALLOW_ARTWORK_ORIGIN] = 'pixiv' + signin_plugin_top_image_origin: ALLOW_ARTWORK_ORIGIN | None = 'pixiv' # 相关数值显示命令 signin_plugin_friendship_alias: str = '好感度' diff --git a/src/plugins/omega_sign_in/handlers.py b/src/plugins/omega_sign_in/handlers.py index 1f70077f..80d98a86 100644 --- a/src/plugins/omega_sign_in/handlers.py +++ b/src/plugins/omega_sign_in/handlers.py @@ -16,10 +16,11 @@ from nonebot.params import ArgStr, Depends from nonebot.typing import T_State -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment from .config import sign_in_config from .exception import DuplicateException, FailedException -from .helpers import generate_signin_card, get_signin_top_image, get_hitokoto, get_profile_image +from .helpers import generate_signin_card, get_hitokoto, get_profile_image, get_signin_top_image async def handle_generate_sign_in_card( diff --git a/src/plugins/omega_sign_in/helpers.py b/src/plugins/omega_sign_in/helpers.py index fd3c50b4..f2fe274a 100644 --- a/src/plugins/omega_sign_in/helpers.py +++ b/src/plugins/omega_sign_in/helpers.py @@ -20,8 +20,8 @@ from pydantic import BaseModel from src.compat import parse_json_as, parse_obj_as -from src.service import OmegaRequests from src.service.artwork_collection import get_artwork_collection, get_artwork_collection_type +from src.utils import OmegaRequests from src.utils.image_utils import ImageUtils from .config import sign_in_config, sign_local_resource_config @@ -30,8 +30,7 @@ from src.service import OmegaMatcherInterface from src.service.artwork_collection.typing import CollectedArtwork - -__FORTUNE_EVENT: list["FortuneEvent"] = [] +__FORTUNE_EVENT: list['FortuneEvent'] = [] """缓存求签事件""" @@ -61,7 +60,7 @@ def __hash__(self) -> int: return hash(self.name + self.good + self.bad) -def _load_fortune_event(file: Union["StaticResource", "TemporaryResource"]) -> list[FortuneEvent]: +def _load_fortune_event(file: Union['StaticResource', 'TemporaryResource']) -> list[FortuneEvent]: """从文件读取求签事件""" if file.is_file: logger.debug(f'loading fortune event form {file}') @@ -100,7 +99,7 @@ def random_fortune_event(num: int = 4) -> list[FortuneEvent]: return random.sample(get_fortune_event(), k=num) -def get_fortune(user_id: str, *, date: Optional[datetime] = None) -> Fortune: +def get_fortune(user_id: str, *, date: datetime | None = None) -> Fortune: """根据 user_id 和当天日期生成老黄历""" if date is None: date_str = str(datetime.now().date()) @@ -159,10 +158,10 @@ def get_fortune(user_id: str, *, date: Optional[datetime] = None) -> Fortune: result = { 'star': fortune_star, 'text': fortune_text, - 'good_do_st': f"{do_and_not[0].name} —— {do_and_not[0].good}", - 'good_do_nd': f"{do_and_not[2].name} —— {do_and_not[2].good}", - 'bad_do_st': f"{do_and_not[1].name} —— {do_and_not[1].bad}", - 'bad_do_nd': f"{do_and_not[3].name} —— {do_and_not[3].bad}" + 'good_do_st': f'{do_and_not[0].name} —— {do_and_not[0].good}', + 'good_do_nd': f'{do_and_not[2].name} —— {do_and_not[2].good}', + 'bad_do_st': f'{do_and_not[1].name} —— {do_and_not[1].bad}', + 'bad_do_nd': f'{do_and_not[3].name} —— {do_and_not[3].bad}' } # 重置随机种子 @@ -171,7 +170,7 @@ def get_fortune(user_id: str, *, date: Optional[datetime] = None) -> Fortune: return Fortune.model_validate(result) -async def get_signin_top_image() -> "CollectedArtwork": +async def get_signin_top_image() -> 'CollectedArtwork': """从数据库获取一张生成签到卡片用的头图""" random_artworks = await get_artwork_collection_type().query_any_origin_by_condition( keywords=None, origin=sign_in_config.signin_plugin_top_image_origin, num=5, @@ -191,7 +190,7 @@ async def get_signin_top_image() -> "CollectedArtwork": raise RuntimeError('all attempts to fetch artwork resources have failed') -async def get_profile_image(interface: "OmegaMatcherInterface") -> "TemporaryResource": +async def get_profile_image(interface: 'OmegaMatcherInterface') -> 'TemporaryResource': """获取用户头像""" url = await interface.get_entity_interface().get_entity_profile_image_url() image_name = OmegaRequests.hash_url_file_name('signin-head-image', url=url) @@ -256,7 +255,7 @@ def _get_level_color( return level_color.get(level, default_color) -async def get_hitokoto(*, c: Optional[str] = None) -> str: +async def get_hitokoto(*, c: str | None = None) -> str: """获取一言""" url = 'https://v1.hitokoto.cn' params = { @@ -273,7 +272,7 @@ async def get_hitokoto(*, c: Optional[str] = None) -> str: hitokoto_data = OmegaRequests.parse_content_as_json(response=hitokoto_response) text = f'{hitokoto_data.get("hitokoto")}\n——《{hitokoto_data.get("from")}》' - if hitokoto_data.get("from_who"): + if hitokoto_data.get('from_who'): text += f' {hitokoto_data.get("from_who")}' return text @@ -282,11 +281,11 @@ async def generate_signin_card( user_id: str, user_text: str, friendship: float, - top_img: "CollectedArtwork", + top_img: 'CollectedArtwork', *, width: int = 1024, draw_fortune: bool = True, - head_img: Optional["TemporaryResource"] = None) -> "TemporaryResource": + head_img: Optional['TemporaryResource'] = None) -> 'TemporaryResource': """生成签到卡片 :param user_id: 用户id @@ -386,7 +385,7 @@ def _handle_signin_card() -> bytes: # 生成背景 background = Image.new( - mode="RGB", + mode='RGB', size=(width, height), color=(255, 255, 255)) diff --git a/src/plugins/omega_statistic/__init__.py b/src/plugins/omega_statistic/__init__.py index 9d4b51bd..c3df2fa0 100644 --- a/src/plugins/omega_statistic/__init__.py +++ b/src/plugins/omega_statistic/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='统计信息', description='【OmegaStatistic 插件使用统计】\n' @@ -25,5 +24,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/omega_statistic/command.py b/src/plugins/omega_statistic/command.py index fe35c211..9a350fc0 100644 --- a/src/plugins/omega_statistic/command.py +++ b/src/plugins/omega_statistic/command.py @@ -16,7 +16,8 @@ from nonebot.plugin import CommandGroup from src.database import StatisticDAL -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from .helpers import draw_statistics # 注册事件响应器 diff --git a/src/plugins/omega_statistic/helpers.py b/src/plugins/omega_statistic/helpers.py index 62886bfd..d6456865 100644 --- a/src/plugins/omega_statistic/helpers.py +++ b/src/plugins/omega_statistic/helpers.py @@ -8,8 +8,9 @@ @Software : PyCharm """ +from collections.abc import Sequence from datetime import datetime -from typing import TYPE_CHECKING, Sequence +from typing import TYPE_CHECKING import matplotlib.cm as cm from matplotlib.colors import Normalize @@ -23,9 +24,9 @@ async def draw_statistics( - statistics_data: Sequence["CountStatisticModel"], + statistics_data: Sequence['CountStatisticModel'], title: str = '插件使用情况统计', -) -> "TemporaryResource": +) -> 'TemporaryResource': """绘制插件使用统计图 :param statistics_data: 统计信息 @@ -33,7 +34,7 @@ async def draw_statistics( """ @run_sync - def _handle(_statistics_data: Sequence["CountStatisticModel"]) -> "TemporaryResource": + def _handle(_statistics_data: Sequence['CountStatisticModel']) -> 'TemporaryResource': y_name = [x.custom_name for x in _statistics_data] x_value = [x.call_count for x in _statistics_data] @@ -57,7 +58,7 @@ def _handle(_statistics_data: Sequence["CountStatisticModel"]) -> "TemporaryReso file_name = f"statistic_{title}_{datetime.now().strftime('%Y%m%d-%H%M%S')}.jpg" return output_figure(fig, file_name) - return await _handle(_statistics_data=statistics_data) + return await _handle(_statistics_data=sorted(statistics_data, key=lambda x: x.call_count)) __all__ = [ diff --git a/src/plugins/onebot_v11_anti_recall/__init__.py b/src/plugins/onebot_v11_anti_recall/__init__.py index 5a56bc33..3265e8bd 100644 --- a/src/plugins/onebot_v11_anti_recall/__init__.py +++ b/src/plugins/onebot_v11_anti_recall/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='反撤回', description='【OneBot V11 AntiRecall 反撤回插件】\n' @@ -24,5 +23,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/onebot_v11_anti_recall/command.py b/src/plugins/onebot_v11_anti_recall/command.py index b086f29a..c8ef9ccf 100644 --- a/src/plugins/onebot_v11_anti_recall/command.py +++ b/src/plugins/onebot_v11_anti_recall/command.py @@ -8,16 +8,13 @@ @Software : PyCharm """ -from datetime import datetime from typing import Annotated, Literal -from nonebot.adapters.onebot.v11 import ( - Bot as OneBotV11Bot, - Message as OneBotV11Message, - MessageSegment as OneBotV11MessageSegment, - GroupMessageEvent as OneBotV11GroupMessageEvent, - GroupRecallNoticeEvent as OneBotV11GroupRecallNoticeEvent -) +from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot +from nonebot.adapters.onebot.v11 import GroupMessageEvent as OneBotV11GroupMessageEvent +from nonebot.adapters.onebot.v11 import GroupRecallNoticeEvent as OneBotV11GroupRecallNoticeEvent +from nonebot.adapters.onebot.v11 import Message as OneBotV11Message +from nonebot.adapters.onebot.v11 import MessageSegment as OneBotV11MessageSegment from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER from nonebot.log import logger from nonebot.matcher import Matcher @@ -26,9 +23,11 @@ from nonebot.plugin import on_command, on_notice from nonebot.typing import T_State -from src.compat import parse_obj_as from src.params.rule import event_has_permission_node -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state +from .config import onebot_v11_anti_recall_config +from .helpers import query_message_from_adapter, query_message_from_database _ANTI_RECALL_CUSTOM_MODULE_NAME: Literal['Omega.AntiRecall'] = 'Omega.AntiRecall' """固定写入数据库的 module name 参数""" @@ -70,6 +69,7 @@ async def handle_set_anti_recall( node=_ENABLE_ANTI_RECALL_NODE, available=1 ) + await interface.entity.commit_session() case 'off': await interface.entity.set_auth_setting( module=_ANTI_RECALL_CUSTOM_MODULE_NAME, @@ -77,6 +77,7 @@ async def handle_set_anti_recall( node=_ENABLE_ANTI_RECALL_NODE, available=0 ) + await interface.entity.commit_session() case _: await interface.send_reply(f'无效输入{switch!r}, 操作已取消') return @@ -104,22 +105,21 @@ async def check_recall_notice(bot: OneBotV11Bot, event: OneBotV11GroupRecallNoti if user_id == event.self_id or event.operator_id == event.self_id: return - message_id = event.message_id - try: - message_result = await bot.get_msg(message_id=message_id) + if onebot_v11_anti_recall_config.onebot_v11_anti_recall_plugin_enable_internal_database: + sent_time, message = await query_message_from_database(bot=bot, event=event, message_id=event.message_id) + else: + sent_time, message = await query_message_from_adapter(bot=bot, message_id=event.message_id) except Exception as e: - logger.error(f'AntiRecall 查询历史消息失败, message_id: {message_id}, {e!r}') + logger.error(f'AntiRecall 查询历史消息失败, message_id: {event.message_id}, {e!r}') return - message = parse_obj_as(OneBotV11Message, message_result['message']).include('image', 'text') - - sent_msg = f'已检测到撤回消息:\n{datetime.fromtimestamp(message_result["time"]).strftime("%Y/%m/%d %H:%M:%S")} ' + sent_msg = f'已检测到撤回消息:\n{sent_time.strftime("%Y-%m-%d %H:%M:%S")} ' sent_msg += OneBotV11MessageSegment.at(user_id=user_id) sent_msg += '\n----消息内容----\n' sent_msg += message - logger.success(f'AntiRecall 已捕获并处理撤回消息, message_id: {message_id}') + logger.success(f'AntiRecall 已捕获并处理撤回消息, message_id: {event.message_id}') await matcher.finish(sent_msg) diff --git a/src/plugins/onebot_v11_anti_recall/config.py b/src/plugins/onebot_v11_anti_recall/config.py new file mode 100644 index 00000000..f02161a9 --- /dev/null +++ b/src/plugins/onebot_v11_anti_recall/config.py @@ -0,0 +1,34 @@ +""" +@Author : Ailitonia +@Date : 2024/10/23 17:04:59 +@FileName : config.py +@Project : omega-miya +@Description : OneBot V11 反撤回插件配置 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot import get_plugin_config, logger +from pydantic import BaseModel, ConfigDict, ValidationError + + +class OneBotV11AntiRecallConfig(BaseModel): + """OneBot V11 反撤回插件配置""" + + # 是否使用内部数据库的消息记录作为查询已撤回消息的来源 + onebot_v11_anti_recall_plugin_enable_internal_database: bool = False + + model_config = ConfigDict(extra='ignore') + + +try: + onebot_v11_anti_recall_config = get_plugin_config(OneBotV11AntiRecallConfig) +except ValidationError as e: + import sys + + logger.opt(colors=True).critical(f'OneBot V11 反撤回插件配置格式验证失败, 错误信息:\n{e}') + sys.exit(f'OneBot V11 反撤回插件配置格式验证失败, {e}') + +__all__ = [ + 'onebot_v11_anti_recall_config', +] diff --git a/src/plugins/onebot_v11_anti_recall/helpers.py b/src/plugins/onebot_v11_anti_recall/helpers.py new file mode 100644 index 00000000..159e1ac9 --- /dev/null +++ b/src/plugins/onebot_v11_anti_recall/helpers.py @@ -0,0 +1,58 @@ +""" +@Author : Ailitonia +@Date : 2024/10/23 17:11:12 +@FileName : helpers.py +@Project : omega-miya +@Description : OneBot V11 反撤回插件工具函数 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from datetime import datetime + +from nonebot.adapters.onebot.v11 import ( + Bot as OneBotV11Bot, +) +from nonebot.adapters.onebot.v11 import ( + Event as OneBotV11Event, +) +from nonebot.adapters.onebot.v11 import ( + Message as OneBotV11Message, +) + +from src.compat import parse_json_as, parse_obj_as +from src.database import HistoryDAL, begin_db_session +from src.service import OmegaMatcherInterface + +type MessageHistory = tuple[datetime, OneBotV11Message] +"""查询到的消息记录: 消息发送时间, 消息内容""" + + +async def query_message_from_adapter(bot: OneBotV11Bot, message_id: int) -> MessageHistory: + """从协议端查询用户消息""" + message_result = await bot.get_msg(message_id=message_id) + message = parse_obj_as(OneBotV11Message, message_result['message']).include('image', 'text') + sent_time = datetime.fromtimestamp(message_result['time']) + return sent_time, message + + +async def query_message_from_database(bot: OneBotV11Bot, event: OneBotV11Event, message_id: int) -> MessageHistory: + """从数据库查询用户消息""" + async with begin_db_session() as session: + event_entity = OmegaMatcherInterface.get_entity(bot, event, session, acquire_type='event') + user_entity = OmegaMatcherInterface.get_entity(bot, event, session, acquire_type='user') + message_recording = await HistoryDAL(session=session).query_unique( + message_id=message_id, + bot_self_id=bot.self_id, + event_entity_id=event_entity.entity_id, + user_entity_id=user_entity.entity_id, + ) + message = parse_json_as(OneBotV11Message, message_recording.message_raw).include('image', 'text') + sent_time = datetime.fromtimestamp(message_recording.received_time) + return sent_time, message + + +__all__ = [ + 'query_message_from_adapter', + 'query_message_from_database', +] diff --git a/src/plugins/onebot_v11_auto_group_sign/__init__.py b/src/plugins/onebot_v11_auto_group_sign/__init__.py index ebbb9b99..12ef5e6e 100644 --- a/src/plugins/onebot_v11_auto_group_sign/__init__.py +++ b/src/plugins/onebot_v11_auto_group_sign/__init__.py @@ -12,7 +12,6 @@ from .config import auto_group_sign_config - __plugin_meta__ = PluginMetadata( name='自动群打卡', description='【QQ 自动群打卡插件】\n' @@ -24,8 +23,6 @@ extra={'author': 'Ailitonia'}, ) - -from . import scheduler as scheduler - +from . import scheduled_tasks as tasks __all__ = [] diff --git a/src/plugins/onebot_v11_auto_group_sign/scheduler.py b/src/plugins/onebot_v11_auto_group_sign/scheduled_tasks.py similarity index 95% rename from src/plugins/onebot_v11_auto_group_sign/scheduler.py rename to src/plugins/onebot_v11_auto_group_sign/scheduled_tasks.py index 46455bf7..d94fc814 100644 --- a/src/plugins/onebot_v11_auto_group_sign/scheduler.py +++ b/src/plugins/onebot_v11_auto_group_sign/scheduled_tasks.py @@ -1,7 +1,7 @@ """ @Author : Ailitonia @Date : 2022/06/27 20:48 -@FileName : auto_group_sign.py +@FileName : scheduled_tasks @Project : nonebot2_miya @Description : 自动群打卡定时任务 @GitHub : https://github.com/Ailitonia @@ -13,7 +13,7 @@ from src.service import scheduler from src.service.omega_multibot_support import get_online_bots -from src.utils.process_utils import semaphore_gather +from src.utils import semaphore_gather from .config import auto_group_sign_config diff --git a/src/plugins/onebot_v11_group_repeater/__init__.py b/src/plugins/onebot_v11_group_repeater/__init__.py index 48413e10..5a8bb3f5 100644 --- a/src/plugins/onebot_v11_group_repeater/__init__.py +++ b/src/plugins/onebot_v11_group_repeater/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='复读姬', description='【QQ 群复读姬插件】\n' @@ -23,5 +22,4 @@ from . import common as common - __all__ = [] diff --git a/src/plugins/onebot_v11_group_repeater/common.py b/src/plugins/onebot_v11_group_repeater/common.py index 270456e7..d8d36677 100644 --- a/src/plugins/onebot_v11_group_repeater/common.py +++ b/src/plugins/onebot_v11_group_repeater/common.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot, GroupMessageEvent as OneBotV11GroupMessageEvent +from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot +from nonebot.adapters.onebot.v11 import GroupMessageEvent as OneBotV11GroupMessageEvent from nonebot.adapters.onebot.v11.permission import GROUP from nonebot.exception import FinishedException from nonebot.matcher import Matcher diff --git a/src/plugins/onebot_v11_group_welcome_message/__init__.py b/src/plugins/onebot_v11_group_welcome_message/__init__.py index 07ed71a7..84c61e86 100644 --- a/src/plugins/onebot_v11_group_welcome_message/__init__.py +++ b/src/plugins/onebot_v11_group_welcome_message/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='群欢迎消息', description='【QQ 群自定义欢迎消息插件】\n' @@ -24,5 +23,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/onebot_v11_group_welcome_message/command.py b/src/plugins/onebot_v11_group_welcome_message/command.py index 01ba0c40..07ac4f86 100644 --- a/src/plugins/onebot_v11_group_welcome_message/command.py +++ b/src/plugins/onebot_v11_group_welcome_message/command.py @@ -12,10 +12,16 @@ from nonebot.adapters.onebot.v11 import ( Bot as OneBotV11Bot, - Message as OneBotV11Message, - GroupMessageEvent as OneBotV11GroupMessageEvent, +) +from nonebot.adapters.onebot.v11 import ( GroupIncreaseNoticeEvent as OneBotV11GroupIncreaseNoticeEvent, ) +from nonebot.adapters.onebot.v11 import ( + GroupMessageEvent as OneBotV11GroupMessageEvent, +) +from nonebot.adapters.onebot.v11 import ( + Message as OneBotV11Message, +) from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER from nonebot.log import logger from nonebot.params import Arg, Depends @@ -24,7 +30,8 @@ from src.params.handler import get_command_message_arg_parser_handler from src.params.rule import event_has_permission_level -from src.service import OmegaMatcherInterface as OmMI, OmegaMessage, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessage, OmegaMessageTransfer, enable_processor_state _SETTING_NAME: Literal['group_welcome_message'] = 'group_welcome_message' """数据库配置节点名称""" @@ -56,7 +63,7 @@ async def handle_set_welcome_message( module_name = interface.matcher.plugin.module_name try: - parsed_message = interface.get_message_extractor()(message=message).message + parsed_message = await OmegaMessageTransfer(interface=interface, origin_message=message).dumps() await interface.entity.set_auth_setting( module=module_name, plugin=plugin_name, node=_SETTING_NAME, available=1, value=parsed_message.dumps() ) diff --git a/src/plugins/onebot_v11_invite_request_manager/__init__.py b/src/plugins/onebot_v11_invite_request_manager/__init__.py index b98e167a..719e6e45 100644 --- a/src/plugins/onebot_v11_invite_request_manager/__init__.py +++ b/src/plugins/onebot_v11_invite_request_manager/__init__.py @@ -10,11 +10,10 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='好友和群组请求管理', description='【QQ 好友和群组请求管理插件】\n' - "处理加好友请求和加群、退群请求", + '处理加好友请求和加群、退群请求', usage='/好友验证码 [用户qq] \n\n' '说明:\n' '以上命令只允许管理员使用\n' @@ -27,5 +26,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/onebot_v11_invite_request_manager/command.py b/src/plugins/onebot_v11_invite_request_manager/command.py index 64a1a5f6..e641a649 100644 --- a/src/plugins/onebot_v11_invite_request_manager/command.py +++ b/src/plugins/onebot_v11_invite_request_manager/command.py @@ -14,10 +14,16 @@ from nonebot.adapters.onebot.v11 import ( Bot as OneBotV11Bot, - Message as OneBotV11Message, +) +from nonebot.adapters.onebot.v11 import ( FriendRequestEvent as OneBotV11FriendRequestEvent, +) +from nonebot.adapters.onebot.v11 import ( GroupRequestEvent as OneBotV11GroupRequestEvent, ) +from nonebot.adapters.onebot.v11 import ( + Message as OneBotV11Message, +) from nonebot.log import logger from nonebot.matcher import Matcher from nonebot.params import ArgStr, CommandArg diff --git a/src/plugins/onebot_v11_scheduled_mute/__init__.py b/src/plugins/onebot_v11_scheduled_mute/__init__.py new file mode 100644 index 00000000..3a86b549 --- /dev/null +++ b/src/plugins/onebot_v11_scheduled_mute/__init__.py @@ -0,0 +1,27 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 19:20 +@FileName : onebot_v11_scheduled_mute +@Project : omega-miya +@Description : 群定时全体禁言 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.plugin import PluginMetadata + +__plugin_meta__ = PluginMetadata( + name='定时群禁言', + description='【OneBot V11 定时群禁言插件】\n' + '设置定时群禁言', + usage='/设置定时群禁言\n' + '/删除定时群禁言\n\n' + 'Crontab格式说明:\n' + '*/1 * * * *\n' + '分|时|日|月|星期', + extra={'author': 'Ailitonia'}, +) + +from . import command as command + +__all__ = [] diff --git a/src/plugins/onebot_v11_scheduled_mute/command.py b/src/plugins/onebot_v11_scheduled_mute/command.py new file mode 100644 index 00000000..3b748670 --- /dev/null +++ b/src/plugins/onebot_v11_scheduled_mute/command.py @@ -0,0 +1,117 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 19:55 +@FileName : command +@Project : omega-miya +@Description : 群定时禁言插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Annotated + +from nonebot.adapters.onebot.v11 import ( + Bot as OneBotV11Bot, +) +from nonebot.adapters.onebot.v11 import ( + GroupMessageEvent as OneBotV11GroupMessageEvent, +) +from nonebot.log import logger +from nonebot.params import ArgStr, Depends +from nonebot.plugin import CommandGroup + +from src.params.permission import IS_ADMIN +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state +from .helpers import ( + add_schedule_job, + generate_schedule_job_data, + remove_schedule_group_mute_job, + set_schedule_group_mute_job, +) + + +async def _check_bot_role( + bot: OneBotV11Bot, + event: OneBotV11GroupMessageEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + try: + bot_role = await bot.get_group_member_info(group_id=event.group_id, user_id=int(bot.self_id)) + except Exception as e: + logger.error(f'SetScheduleMute | 添加前检查管理员身份失败, {e!r}') + await interface.finish_reply('设置定时群禁言任务失败, 请稍后再试或联系管理员处理') + + if bot_role.get('role') not in ['owner', 'admin']: + await interface.finish_reply('Bot非群管理员, 无法执行禁言操作') + + +schedule_group_mute = CommandGroup( + 'schedule-group-mute', + permission=IS_ADMIN, + priority=20, + block=True, + state=enable_processor_state(name='OneBotV11ScheduleMute', level=10), +) + +set_ = schedule_group_mute.command( + 'set', + aliases={'设置定时群禁言', '新增定时群禁言'}, + handlers=[_check_bot_role], +) + + +@set_.got('crontab_enable', prompt='请发送开始禁言时间的crontab表达式:') +@set_.got('crontab_disable', prompt='请发送结束禁言时间的crontab表达式:') +async def handle_set_schedule_group_mute( + _bot: OneBotV11Bot, + _event: OneBotV11GroupMessageEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], + crontab_enable: Annotated[str, ArgStr('crontab_enable')], + crontab_disable: Annotated[str, ArgStr('crontab_disable')], +) -> None: + try: + enable_job_data = await generate_schedule_job_data( + interface=interface, crontab=crontab_enable.strip(), enable_mute=True + ) + disable_job_data = await generate_schedule_job_data( + interface=interface, crontab=crontab_disable.strip(), enable_mute=False + ) + + add_schedule_job(job_data=enable_job_data) + add_schedule_job(job_data=disable_job_data) + except Exception as e: + logger.error(f'SetScheduleMute | 为 {interface} 添加定时群禁言任务到 schedule 失败, {e!r}') + await interface.finish_reply('设置定时群禁言任务失败, 请稍后再试或联系管理员处理') + + try: + await set_schedule_group_mute_job(interface=interface, job_data=enable_job_data) + await set_schedule_group_mute_job(interface=interface, job_data=disable_job_data) + except Exception as e: + logger.error(f'SetScheduleMute | 将 {interface} 定时群禁言任务写入数据库失败, {e!r}') + await interface.finish_reply('保存定时群禁言任务失败, 请稍后再试或联系管理员处理') + + await interface.entity.commit_session() + await interface.finish_reply('添加定时群禁言任务成功!') + + +@schedule_group_mute.command( + 'remove', + aliases={'删除定时群禁言', '移除定时群禁言'}, +).handle() +async def handle_remove_schedule_group_mute( + _bot: OneBotV11Bot, + _event: OneBotV11GroupMessageEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + try: + await remove_schedule_group_mute_job(interface=interface) + except Exception as e: + logger.error(f'RemoveScheduleMute | 移除 {interface} 定时群禁言任务失败, {e!r}') + await interface.finish_reply('移除定时群禁言任务失败, 可能是还尚未配置群禁言任务, 请稍后再试或联系管理员处理') + + await interface.entity.commit_session() + await interface.finish_reply('移除定时群禁言任务成功!') + + +__all__ = [] diff --git a/src/plugins/onebot_v11_scheduled_mute/helpers.py b/src/plugins/onebot_v11_scheduled_mute/helpers.py new file mode 100644 index 00000000..daa3c695 --- /dev/null +++ b/src/plugins/onebot_v11_scheduled_mute/helpers.py @@ -0,0 +1,136 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 19:20 +@FileName : helpers +@Project : omega-miya +@Description : 群定时禁言工具 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import TYPE_CHECKING + +from apscheduler.triggers.cron import CronTrigger +from nonebot import get_driver +from nonebot.log import logger + +from src.compat import parse_json_as +from src.database import AuthSettingDAL, begin_db_session +from src.service import OmegaEntity, scheduler +from src.service import OmegaEntityInterface as OmEI +from .model import SCHEDULE_MUTE_CUSTOM_MODULE_NAME, SCHEDULE_MUTE_CUSTOM_PLUGIN_NAME, ScheduleMuteJob + +if TYPE_CHECKING: + from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot + + from src.service import OmegaMatcherInterface + + +def add_schedule_job(job_data: ScheduleMuteJob) -> None: + """添加执行群禁言的计划任务""" + + async def _handle_group_mute(): + """执行群禁言的内部函数""" + try: + async with begin_db_session() as session: + entity = await OmegaEntity.init_from_entity_index_id(session=session, index_id=job_data.entity_index_id) + bot: OneBotV11Bot = await OmEI(entity=entity).get_bot() # type: ignore + await bot.set_group_whole_ban(group_id=int(entity.entity_id), enable=job_data.enable_mute) + except Exception as e: + logger.error(f'ScheduleMuteJob | Handling group mute job({job_data.job_name}) failed, {e!r}') + + trigger = CronTrigger.from_crontab(job_data.crontab) + # 检查有没有同名计划任务 + exist_job = scheduler.get_job(job_id=job_data.job_name) + if exist_job is None: + scheduler.add_job( + _handle_group_mute, + trigger=trigger, + id=job_data.job_name, + coalesce=True, + misfire_grace_time=10 + ) + logger.success(f'ScheduleMuteJob | Add group mute job({job_data.job_name}) successful') + else: + exist_job.reschedule(trigger=trigger) + logger.success(f'ScheduleMuteJob | Reschedule group mute job({job_data.job_name}) successful') + + +def remove_schedule_job(job_data: ScheduleMuteJob) -> None: + """移除群禁言的计划任务""" + scheduler.remove_job(job_id=job_data.job_name) + logger.success(f'ScheduleMuteJob | Remove group mute job({job_data.job_name}) successful') + + +@get_driver().on_startup +async def _init_schedule_group_mute_job() -> None: + """启动时读取并配置所有定时群禁言任务""" + async with begin_db_session() as session: + all_jobs = await AuthSettingDAL(session=session).query_module_plugin_all( + module=SCHEDULE_MUTE_CUSTOM_MODULE_NAME, plugin=SCHEDULE_MUTE_CUSTOM_PLUGIN_NAME + ) + for job in all_jobs: + if job.available == 1 and job.value is not None: + try: + add_schedule_job(job_data=parse_json_as(ScheduleMuteJob, job.value)) + except Exception as e: + logger.error(f'ScheduleMuteJob | Add group mute job({job}) failed when init in startup, {e!r}') + + +async def generate_schedule_job_data( + interface: 'OmegaMatcherInterface', + crontab: str, + enable_mute: bool, +) -> ScheduleMuteJob: + """生成定时群禁言的计划任务""" + entity_data = await interface.entity.query_entity_self() + job_data = { + 'entity_index_id': entity_data.id, + 'crontab': crontab, + 'enable_mute': enable_mute, + 'mode': 'cron', + } + return ScheduleMuteJob.model_validate(job_data) + + +async def set_schedule_group_mute_job(interface: 'OmegaMatcherInterface', job_data: ScheduleMuteJob) -> None: + """在数据库中新增或更新 Event 对应 Entity 的定时任务信息""" + await interface.entity.set_auth_setting( + module=SCHEDULE_MUTE_CUSTOM_MODULE_NAME, + plugin=SCHEDULE_MUTE_CUSTOM_PLUGIN_NAME, + node='enable' if job_data.enable_mute else 'disable', + available=1, + value=job_data.model_dump_json() + ) + + +async def remove_schedule_group_mute_job(interface: 'OmegaMatcherInterface') -> None: + """在数据库中停用 Event 对应 Entity 的定时任务信息""" + jobs_setting = await interface.entity.query_plugin_all_auth_setting( + module=SCHEDULE_MUTE_CUSTOM_MODULE_NAME, + plugin=SCHEDULE_MUTE_CUSTOM_PLUGIN_NAME, + ) + if not jobs_setting: + raise ValueError(f'{interface.entity} group mute job not confined') + + for job_setting in jobs_setting: + if job_setting.value is None: + raise ValueError(f'{interface.entity} group mute job({job_setting.node}) not confined') + + job_data = parse_json_as(ScheduleMuteJob, job_setting.value) + + await interface.entity.set_auth_setting( + module=SCHEDULE_MUTE_CUSTOM_MODULE_NAME, + plugin=SCHEDULE_MUTE_CUSTOM_PLUGIN_NAME, + node=job_setting.node, + available=0 + ) + remove_schedule_job(job_data=job_data) + + +__all__ = [ + 'add_schedule_job', + 'generate_schedule_job_data', + 'set_schedule_group_mute_job', + 'remove_schedule_group_mute_job', +] diff --git a/src/plugins/onebot_v11_scheduled_mute/model.py b/src/plugins/onebot_v11_scheduled_mute/model.py new file mode 100644 index 00000000..bd8052c5 --- /dev/null +++ b/src/plugins/onebot_v11_scheduled_mute/model.py @@ -0,0 +1,36 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 19:21 +@FileName : model +@Project : omega-miya +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Literal + +from pydantic import BaseModel + +SCHEDULE_MUTE_CUSTOM_MODULE_NAME: Literal['OneBotV11.ScheduleMute'] = 'OneBotV11.ScheduleMute' +"""固定写入数据库的 module name 参数""" +SCHEDULE_MUTE_CUSTOM_PLUGIN_NAME: Literal['ScheduleMute'] = 'ScheduleMute' +"""固定写入数据库的 plugin name 参数""" + + +class ScheduleMuteJob(BaseModel): + entity_index_id: int + crontab: str + enable_mute: bool + mode: Literal['cron'] = 'cron' + + @property + def job_name(self) -> str: + return f'entity_index-{self.entity_index_id}_ScheduleMute_{"enable" if self.enable_mute else "disable"}' + + +__all__ = [ + 'SCHEDULE_MUTE_CUSTOM_MODULE_NAME', + 'SCHEDULE_MUTE_CUSTOM_PLUGIN_NAME', + 'ScheduleMuteJob', +] diff --git a/src/plugins/onebot_v11_self_mute/__init__.py b/src/plugins/onebot_v11_self_mute/__init__.py index fb1b98d0..5162a859 100644 --- a/src/plugins/onebot_v11_self_mute/__init__.py +++ b/src/plugins/onebot_v11_self_mute/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='随机口球', description='【QQ 群随机口球插件】\n' @@ -23,5 +22,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/onebot_v11_self_mute/command.py b/src/plugins/onebot_v11_self_mute/command.py index 4e1f98f7..59cdac9b 100644 --- a/src/plugins/onebot_v11_self_mute/command.py +++ b/src/plugins/onebot_v11_self_mute/command.py @@ -15,9 +15,13 @@ from nonebot.adapters.onebot.v11 import ( Bot as OneBotV11Bot, - Message as OneBotV11Message, +) +from nonebot.adapters.onebot.v11 import ( GroupMessageEvent as OneBotV11GroupMessageEvent, ) +from nonebot.adapters.onebot.v11 import ( + Message as OneBotV11Message, +) from nonebot.adapters.onebot.v11.permission import GROUP from nonebot.log import logger from nonebot.matcher import Matcher diff --git a/src/plugins/onebot_v11_zhoushen_hime/__init__.py b/src/plugins/onebot_v11_zhoushen_hime/__init__.py index 34bd56d3..be208f16 100644 --- a/src/plugins/onebot_v11_zhoushen_hime/__init__.py +++ b/src/plugins/onebot_v11_zhoushen_hime/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='自动审轴姬', description='【自动审轴姬插件】\n' @@ -25,5 +24,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/onebot_v11_zhoushen_hime/command.py b/src/plugins/onebot_v11_zhoushen_hime/command.py index 362fe9fc..9c09a1e7 100644 --- a/src/plugins/onebot_v11_zhoushen_hime/command.py +++ b/src/plugins/onebot_v11_zhoushen_hime/command.py @@ -14,7 +14,11 @@ from nonebot.adapters.onebot.v11 import ( Bot as OneBotV11Bot, +) +from nonebot.adapters.onebot.v11 import ( GroupMessageEvent as OneBotV11GroupMessageEvent, +) +from nonebot.adapters.onebot.v11 import ( GroupUploadNoticeEvent as OneBotV11GroupUploadNoticeEvent, ) from nonebot.adapters.onebot.v11.permission import GROUP_ADMIN, GROUP_OWNER @@ -26,7 +30,8 @@ from src.params.handler import get_command_str_single_arg_parser_handler from src.params.rule import event_has_permission_node -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from .helpers import ZhouChecker, download_file, upload_result_file _ZHOUSHEN_HIME_CUSTOM_MODULE_NAME: Literal['Omega.ZhoushenHime'] = 'Omega.ZhoushenHime' @@ -70,10 +75,10 @@ async def handle_zhoushen_hime_manager( try: await switch_coro - logger.success(f"ZhoushenHimeManager | {interface.entity} 设置审轴姬功能开关为 {switch} 成功") + logger.success(f'ZhoushenHimeManager | {interface.entity} 设置审轴姬功能开关为 {switch} 成功') await interface.send_reply(f'已设置审轴姬功能开关为 {switch}!') except Exception as e: - logger.error(f"ZhoushenHimeManager | {interface.entity} 设置审轴姬功能开关为 {switch} 失败, {e}") + logger.error(f'ZhoushenHimeManager | {interface.entity} 设置审轴姬功能开关为 {switch} 失败, {e}') await interface.send_reply('设置审轴姬功能开关失败, 请稍后重试或联系管理员处理') diff --git a/src/plugins/onebot_v11_zhoushen_hime/helpers.py b/src/plugins/onebot_v11_zhoushen_hime/helpers.py index d808d3f3..bb726e94 100644 --- a/src/plugins/onebot_v11_zhoushen_hime/helpers.py +++ b/src/plugins/onebot_v11_zhoushen_hime/helpers.py @@ -10,7 +10,6 @@ import datetime from dataclasses import dataclass -from typing import Optional from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot from nonebot.utils import run_sync @@ -18,7 +17,7 @@ from src.exception import PluginException from src.resource import TemporaryResource -from src.service import OmegaRequests +from src.utils import OmegaRequests _TMP_FOLDER: TemporaryResource = TemporaryResource('zhoushen_hime') """缓存文件夹""" @@ -28,7 +27,7 @@ class AssScriptException(PluginException): """字幕处理异常""" -class AssScriptLine(object): +class AssScriptLine: """ass字幕行类""" # 标记属性 __STYLE: str = 'Style' @@ -219,8 +218,8 @@ def generate(self) -> str: if end_time[0] == '0': end_time = end_time[1:] - return f"{self.type}: 0,{start_time},{end_time},{self.style},{self.actor}," \ - f"{self.left_margin},{self.right_margin},{self.vertical_margin},{self.effect},{self.text}" + return f'{self.type}: 0,{start_time},{end_time},{self.style},{self.actor},' \ + f'{self.left_margin},{self.right_margin},{self.vertical_margin},{self.effect},{self.text}' def check_flash(self, threshold_time: int) -> tuple[int, datetime.timedelta]: """判断该行单行是否是闪轴 @@ -272,7 +271,7 @@ def __repr__(self) -> str: f'effect={self.__effect}, text={self.__text})' -class AssScriptLineTool(object): +class AssScriptLineTool: """ass字幕event行工具类""" # 标记属性 __STYLE: str = 'Style' @@ -604,15 +603,15 @@ def _handle(self) -> HandleResult: # 处理叠轴 if overlap == 1: overlap_count += 1 - out_log += f"第{start_line.event_line_num}行轴和第{end_line.event_line_num}行可能是叠轴, 请检查一下\n" + out_log += f'第{start_line.event_line_num}行轴和第{end_line.event_line_num}行可能是叠轴, 请检查一下\n' # 处理闪轴 # 是单行闪轴还和后面连轴了 if single_flash == 1 and continuous == 1: flash_count += 1 - out_log += f"第{start_line.event_line_num}行轴是闪轴" \ - f"({start_line.line_duration.microseconds / 1000}ms), " \ - f"但是它和{end_line.event_line_num}行轴是连轴, 所以看着改吧\n" + out_log += f'第{start_line.event_line_num}行轴是闪轴' \ + f'({start_line.line_duration.microseconds / 1000}ms), ' \ + f'但是它和{end_line.event_line_num}行轴是连轴, 所以看着改吧\n' break # 是单行闪轴而且补也补不够的神轴 elif single_flash == 1 and continuous != 1 and start_line.end_time < end_line.start_time \ @@ -622,15 +621,15 @@ def _handle(self) -> HandleResult: before_duration = start_line.line_duration start_line.change_end_time(delta=multi_flash_lines_duration) after_change_time = start_line.end_time - out_log += f"第{start_line.event_line_num}行轴({before_duration.microseconds / 1000}ms)" \ - f"要是不闪就和第{end_line.event_line_num}行轴之间是叠轴了, " \ - f"不过我姑且给你连上了({after_change_time})\n" + out_log += f'第{start_line.event_line_num}行轴({before_duration.microseconds / 1000}ms)' \ + f'要是不闪就和第{end_line.event_line_num}行轴之间是叠轴了, ' \ + f'不过我姑且给你连上了({after_change_time})\n' break else: flash_count += 1 - out_log += f"第{start_line.event_line_num}行轴({start_line.line_duration.microseconds / 1000}ms)" \ - f"要是不闪就和第{end_line.event_line_num}行轴之间是叠轴了, " \ - f"这啥神轴啊, 你自己看着改吧\n" + out_log += f'第{start_line.event_line_num}行轴({start_line.line_duration.microseconds / 1000}ms)' \ + f'要是不闪就和第{end_line.event_line_num}行轴之间是叠轴了, ' \ + f'这啥神轴啊, 你自己看着改吧\n' break # 是单行闪轴而且补上后就会和后面的轴变成闪轴的神轴 elif single_flash == 1 and continuous != 1 and start_line.end_time < end_line.start_time \ @@ -642,15 +641,15 @@ def _handle(self) -> HandleResult: before_duration = start_line.line_duration start_line.change_end_time(delta=multi_flash_lines_duration) after_change_time = start_line.end_time - out_log += f"第{start_line.event_line_num}行轴({before_duration.microseconds / 1000}ms)" \ - f"要是不闪就和第{end_line.event_line_num}行轴之间是闪轴了, " \ - f"不过我姑且给你连上了({after_change_time})\n" + out_log += f'第{start_line.event_line_num}行轴({before_duration.microseconds / 1000}ms)' \ + f'要是不闪就和第{end_line.event_line_num}行轴之间是闪轴了, ' \ + f'不过我姑且给你连上了({after_change_time})\n' break else: flash_count += 1 - out_log += f"第{start_line.event_line_num}行轴({start_line.line_duration.microseconds / 1000}ms)" \ - f"要是不闪就和第{end_line.event_line_num}行轴之间是闪轴了, " \ - f"这啥神轴啊, 你自己看着改吧\n" + out_log += f'第{start_line.event_line_num}行轴({start_line.line_duration.microseconds / 1000}ms)' \ + f'要是不闪就和第{end_line.event_line_num}行轴之间是闪轴了, ' \ + f'这啥神轴啊, 你自己看着改吧\n' break # 上面的都没有发生而且已经连轴了就跳过 elif continuous == 1: @@ -662,16 +661,16 @@ def _handle(self) -> HandleResult: before_change_time = start_line.end_time start_line.change_end_time(delta=single_flash_lines_duration) after_change_time = start_line.end_time - out_log += f"第{start_line.event_line_num}行轴是闪轴({before_duration.microseconds / 1000}ms), " \ - f"但是我给你改好了, 从原来的{before_change_time}改成了{after_change_time}\n" + out_log += f'第{start_line.event_line_num}行轴是闪轴({before_duration.microseconds / 1000}ms), ' \ + f'但是我给你改好了, 从原来的{before_change_time}改成了{after_change_time}\n' break # 处理轴间闪轴 elif multi_flash == 1 and continuous == 0: flash_count += 1 start_line.change_end_time(delta=multi_flash_lines_duration) after_change_time = start_line.end_time - out_log += f"第{start_line.event_line_num}行轴和第{end_line.event_line_num}行轴之间是闪轴" \ - f"({multi_flash_lines_duration.microseconds / 1000}ms), 不过我给你连上了({after_change_time})\n" + out_log += f'第{start_line.event_line_num}行轴和第{end_line.event_line_num}行轴之间是闪轴' \ + f'({multi_flash_lines_duration.microseconds / 1000}ms), 不过我给你连上了({after_change_time})\n' break out_log += '--- 锤轴部分结束 ---\n\n' \ '--- 审轴信息 ---\n' \ @@ -699,8 +698,8 @@ async def handle(self, auto_style: bool = False) -> OutputHandleResult: # 输出文件 time_text = datetime.datetime.now().strftime('%Y%m%d-%H%M%S') - output_txt_file = _TMP_FOLDER(f"{self.__file.path.name}_{time_text}_锤.txt") - output_ass_file = _TMP_FOLDER(f"{self.__file.path.name}_{time_text}_改.ass") + output_txt_file = _TMP_FOLDER(f'{self.__file.path.name}_{time_text}_锤.txt') + output_ass_file = _TMP_FOLDER(f'{self.__file.path.name}_{time_text}_改.ass') async with output_txt_file.async_open('w', encoding='utf-8') as af: await af.write(handle_result.output_txt) @@ -783,7 +782,7 @@ class OneBotV11GroupRootFiles(BaseOneBotV11Model): - folders: 文件夹列表 """ files: list[OneBotV11GroupFile] - folders: Optional[list[OneBotV11GroupFolder]] = None + folders: list[OneBotV11GroupFolder] | None = None async def download_file(url: str, file_name: str) -> TemporaryResource: diff --git a/src/plugins/pixiv_artist_monitor/__init__.py b/src/plugins/pixiv_artist_monitor/__init__.py index 9ce4357d..bda31452 100644 --- a/src/plugins/pixiv_artist_monitor/__init__.py +++ b/src/plugins/pixiv_artist_monitor/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='Pixiv用户作品助手', description='【Pixiv用户作品助手插件】\n' @@ -33,5 +32,4 @@ from . import command as command from . import monitor as monitor - __all__ = [] diff --git a/src/plugins/pixiv_artist_monitor/command.py b/src/plugins/pixiv_artist_monitor/command.py index c580e205..61a04e17 100644 --- a/src/plugins/pixiv_artist_monitor/command.py +++ b/src/plugins/pixiv_artist_monitor/command.py @@ -16,20 +16,21 @@ from nonebot.typing import T_State from src.params.handler import ( - get_command_str_single_arg_parser_handler, get_command_str_multi_args_parser_handler, + get_command_str_single_arg_parser_handler, get_set_default_state_handler, ) from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from src.utils.pixiv_api import PixivUser from .helpers import ( add_pixiv_user_sub, delete_pixiv_user_sub, - query_entity_subscribed_pixiv_user_sub_source, generate_artworks_preview, get_ranking_preview_factory, handle_ranking_preview, + query_entity_subscribed_pixiv_user_sub_source, ) from .monitor import scheduler @@ -231,10 +232,10 @@ async def handle_add_subscription( try: await add_pixiv_user_sub(interface=interface, pixiv_user=user) await interface.entity.commit_session() - logger.success(f"PixivAddUserSubscription | {interface.entity}订阅用户(uid={user_id})成功") + logger.success(f'PixivAddUserSubscription | {interface.entity}订阅用户(uid={user_id})成功') msg = f'订阅Pixiv用户{user_id}成功' except Exception as e: - logger.error(f"PixivAddUserSubscription | {interface.entity}订阅用户(uid={user_id})失败, {e!r}") + logger.error(f'PixivAddUserSubscription | {interface.entity}订阅用户(uid={user_id})失败, {e!r}') msg = f'订阅Pixiv用户{user_id}失败, 可能是网络异常或发生了意外的错误, 请稍后重试或联系管理员处理' scheduler.resume() await interface.finish_reply(msg) diff --git a/src/plugins/pixiv_artist_monitor/helpers.py b/src/plugins/pixiv_artist_monitor/helpers.py index 9dbe2d80..b59b5559 100644 --- a/src/plugins/pixiv_artist_monitor/helpers.py +++ b/src/plugins/pixiv_artist_monitor/helpers.py @@ -9,25 +9,30 @@ """ import asyncio +from collections.abc import Callable, Coroutine, Sequence from datetime import datetime -from typing import TYPE_CHECKING, Any, Callable, Coroutine, Literal, Optional, Sequence +from typing import TYPE_CHECKING, Any, Literal from nonebot.exception import ActionFailed from nonebot.log import logger from src.database import begin_db_session from src.service import ( - OmegaMatcherInterface as OmMI, - OmegaEntityInterface as OmEI, OmegaEntity, OmegaMessage, OmegaMessageSegment, ) +from src.service import ( + OmegaEntityInterface as OmEI, +) +from src.service import ( + OmegaMatcherInterface as OmMI, +) from src.service.artwork_collection import PixivArtworkCollection from src.service.artwork_proxy import PixivArtworkProxy from src.service.omega_base.internal import OmegaPixivUserSubSource +from src.utils import semaphore_gather from src.utils.pixiv_api import PixivUser -from src.utils.process_utils import semaphore_gather from .consts import PIXIV_USER_SUB_TYPE if TYPE_CHECKING: @@ -37,20 +42,20 @@ from src.utils.pixiv_api.model.ranking import PixivRankingModel -async def _query_pixiv_user_sub_source(uid: int) -> "SubscriptionSource": +async def _query_pixiv_user_sub_source(uid: int) -> 'SubscriptionSource': """从数据库查询 Pixiv 用户订阅源""" async with begin_db_session() as session: source_res = await OmegaPixivUserSubSource(session=session, uid=uid).query_subscription_source() return source_res -async def _check_pixiv_user_new_artworks(pixiv_user: "PixivUser") -> list[str]: +async def _check_pixiv_user_new_artworks(pixiv_user: 'PixivUser') -> list[str]: """检查 Pixiv 用户的新作品(数据库中没有的)""" user_data = await pixiv_user.query_user_data() return await PixivArtworkCollection.query_not_exists_aids(aids=[str(pid) for pid in user_data.manga_illusts]) -async def _add_pixiv_user_new_artworks(pixiv_user: "PixivUser") -> None: +async def _add_pixiv_user_new_artworks(pixiv_user: 'PixivUser') -> None: """在数据库中新增目标用户的全部作品(仅新增不更新)""" user_new_pids = await _check_pixiv_user_new_artworks(pixiv_user=pixiv_user) @@ -74,7 +79,7 @@ async def _add_pixiv_user_new_artworks(pixiv_user: "PixivUser") -> None: logger.info(f'PixivUserAdder | Adding user({pixiv_user.uid}) artworks completed, failed: {fail_count}') -async def _add_upgrade_pixiv_user_sub_source(pixiv_user: "PixivUser") -> "SubscriptionSource": +async def _add_upgrade_pixiv_user_sub_source(pixiv_user: 'PixivUser') -> 'SubscriptionSource': """在数据库中更新 Pixiv 用户订阅源""" user_data = await pixiv_user.query_user_data() @@ -87,7 +92,7 @@ async def _add_upgrade_pixiv_user_sub_source(pixiv_user: "PixivUser") -> "Subscr return source_res -async def add_pixiv_user_sub(interface: OmMI, pixiv_user: "PixivUser") -> None: +async def add_pixiv_user_sub(interface: OmMI, pixiv_user: 'PixivUser') -> None: """为目标对象添加 Pixiv 用户订阅""" source_res = await _add_upgrade_pixiv_user_sub_source(pixiv_user=pixiv_user) await interface.entity.add_subscription(subscription_source=source_res, @@ -118,7 +123,7 @@ async def query_all_subscribed_pixiv_user_sub_source() -> list[int]: return [int(x.sub_id) for x in source_res] -async def query_subscribed_entity_by_pixiv_user(pixiv_user: "PixivUser") -> list["Entity"]: +async def query_subscribed_entity_by_pixiv_user(pixiv_user: 'PixivUser') -> list['Entity']: """根据 Pixiv 用户查询已经订阅了这个用户的内部 Entity 对象""" async with begin_db_session() as session: sub_source = OmegaPixivUserSubSource(session=session, uid=pixiv_user.uid) @@ -129,7 +134,7 @@ async def query_subscribed_entity_by_pixiv_user(pixiv_user: "PixivUser") -> list async def _format_pixiv_user_new_artwork_message( pid: str, *, - message_prefix: Optional[str] = None, + message_prefix: str | None = None, show_page_limiting: int = 10, ) -> OmegaMessage: """预处理用户作品预览消息""" @@ -159,7 +164,7 @@ async def _format_pixiv_user_new_artwork_message( return send_msg -async def _msg_sender(entity: "Entity", message: OmegaMessage) -> None: +async def _msg_sender(entity: 'Entity', message: OmegaMessage) -> None: """向 entity 发送消息""" try: async with begin_db_session() as session: @@ -172,7 +177,7 @@ async def _msg_sender(entity: "Entity", message: OmegaMessage) -> None: logger.error(f'PixivUserSubscriptionMonitor | Sending message to {entity} failed, {e!r}') -async def pixiv_user_new_artworks_monitor_main(pixiv_user: "PixivUser") -> None: +async def pixiv_user_new_artworks_monitor_main(pixiv_user: 'PixivUser') -> None: """向已订阅的用户或群发送 Pixiv 用户更新的作品""" logger.debug(f'PixivUserSubscriptionMonitor | Start checking pixiv {pixiv_user} new artworks') user_data = await pixiv_user.query_user_data() @@ -210,7 +215,7 @@ async def pixiv_user_new_artworks_monitor_main(pixiv_user: "PixivUser") -> None: """作品预览图生成工具""" -async def generate_artworks_preview(title: str, pids: Sequence[int], *, no_blur_rating: int = 1) -> "TemporaryResource": +async def generate_artworks_preview(title: str, pids: Sequence[int], *, no_blur_rating: int = 1) -> 'TemporaryResource': """生成多个作品的预览图""" return await PixivArtworkProxy.generate_artworks_preview( preview_name=title, @@ -221,7 +226,7 @@ async def generate_artworks_preview(title: str, pids: Sequence[int], *, no_blur_ ) -async def _generate_ranking_preview(title: str, ranking_data: "PixivRankingModel") -> "TemporaryResource": +async def _generate_ranking_preview(title: str, ranking_data: 'PixivRankingModel') -> 'TemporaryResource': """根据榜单数据生成预览图""" return await PixivArtworkProxy.generate_artworks_preview( preview_name=title, @@ -233,10 +238,10 @@ async def _generate_ranking_preview(title: str, ranking_data: "PixivRankingModel def get_ranking_preview_factory( mode: Literal['daily', 'weekly', 'monthly'], -) -> Callable[[int], Coroutine[Any, Any, "TemporaryResource"]]: +) -> Callable[[int], Coroutine[Any, Any, 'TemporaryResource']]: """获取榜单预览图生成器""" - async def _factor(page: int) -> "TemporaryResource": + async def _factor(page: int) -> 'TemporaryResource': ranking_data = await PixivUser.query_ranking(mode=mode, page=page, content='illust') title = f'Pixiv {mode.title()} Ranking {datetime.now().strftime("%Y-%m-%d")}' @@ -246,9 +251,9 @@ async def _factor(page: int) -> "TemporaryResource": async def handle_ranking_preview( - interface: "OmMI", + interface: 'OmMI', page: str, - ranking_preview_factory: Callable[[int], Coroutine[Any, Any, "TemporaryResource"]] + ranking_preview_factory: Callable[[int], Coroutine[Any, Any, 'TemporaryResource']] ) -> None: """生成并发送榜单预览图""" page = page.strip() diff --git a/src/plugins/pixiv_artist_monitor/monitor.py b/src/plugins/pixiv_artist_monitor/monitor.py index ad202908..bf8dff94 100644 --- a/src/plugins/pixiv_artist_monitor/monitor.py +++ b/src/plugins/pixiv_artist_monitor/monitor.py @@ -11,9 +11,9 @@ from nonebot.log import logger from src.service import scheduler +from src.utils import semaphore_gather from src.utils.pixiv_api import PixivUser -from src.utils.process_utils import semaphore_gather -from .helpers import query_all_subscribed_pixiv_user_sub_source, pixiv_user_new_artworks_monitor_main +from .helpers import pixiv_user_new_artworks_monitor_main, query_all_subscribed_pixiv_user_sub_source async def pixiv_user_new_artworks_monitor() -> None: diff --git a/src/plugins/pixiv_pixivision_monitor/__init__.py b/src/plugins/pixiv_pixivision_monitor/__init__.py index 7748d5bc..a7de3a84 100644 --- a/src/plugins/pixiv_pixivision_monitor/__init__.py +++ b/src/plugins/pixiv_pixivision_monitor/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='Pixivision', description='【Pixivision 特辑助手】\n' @@ -28,5 +27,4 @@ from . import command as command from . import monitor as monitor - __all__ = [] diff --git a/src/plugins/pixiv_pixivision_monitor/command.py b/src/plugins/pixiv_pixivision_monitor/command.py index 24418c95..52fb518e 100644 --- a/src/plugins/pixiv_pixivision_monitor/command.py +++ b/src/plugins/pixiv_pixivision_monitor/command.py @@ -16,7 +16,8 @@ from src.params.handler import get_command_str_single_arg_parser_handler from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from src.utils.pixiv_api import Pixivision from .helpers import ( add_pixivision_sub, diff --git a/src/plugins/pixiv_pixivision_monitor/helpers.py b/src/plugins/pixiv_pixivision_monitor/helpers.py index 586c75e9..f0052df8 100644 --- a/src/plugins/pixiv_pixivision_monitor/helpers.py +++ b/src/plugins/pixiv_pixivision_monitor/helpers.py @@ -8,76 +8,81 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING from nonebot import logger from nonebot.exception import ActionFailed -from sqlalchemy.exc import NoResultFound -from src.database import PixivisionArticleDAL, begin_db_session +from src.database import SocialMediaContentDAL, begin_db_session +from src.database.internal.subscription_source import SubscriptionSource, SubscriptionSourceType from src.resource import TemporaryResource from src.service import ( - OmegaMatcherInterface as OmMI, - OmegaEntityInterface as OmEI, OmegaEntity, OmegaMessage, OmegaMessageSegment, ) +from src.service import ( + OmegaEntityInterface as OmEI, +) +from src.service import ( + OmegaMatcherInterface as OmMI, +) from src.service.artwork_collection import PixivArtworkCollection from src.service.artwork_proxy import PixivArtworkProxy from src.service.omega_base.internal import OmegaPixivisionSubSource +from src.utils import semaphore_gather from src.utils.pixiv_api import Pixivision -from src.utils.process_utils import semaphore_gather if TYPE_CHECKING: from src.database.internal.entity import Entity - from src.database.internal.subscription_source import SubscriptionSource from src.utils.pixiv_api.model.pixivision import PixivisionArticle, PixivisionIllustration +_PIXIVISION_SUB_TYPE: str = SubscriptionSourceType.pixivision.value +"""微博用户订阅类型""" _TMP_FOLDER: TemporaryResource = TemporaryResource('pixivision') """图片缓存文件夹""" -async def _query_pixivision_sub_source() -> "SubscriptionSource": +async def _query_pixivision_sub_source() -> 'SubscriptionSource': """从数据库查询 Pixivision 订阅源""" async with begin_db_session() as session: source_res = await OmegaPixivisionSubSource(session=session).query_subscription_source() return source_res -async def _check_new_article(articles: Sequence["PixivisionIllustration"]) -> list["PixivisionIllustration"]: +async def _check_new_article(articles: Sequence['PixivisionIllustration']) -> list['PixivisionIllustration']: """检查新的 pixivision 特辑文章(数据库中没有的)""" async with begin_db_session() as session: - new_aids = await PixivisionArticleDAL(session=session).query_not_exists_ids(aids=[x.aid for x in articles]) - return [x for x in articles if x.aid in new_aids] + new_aids = await SocialMediaContentDAL(session=session).query_source_not_exists_mids( + source=_PIXIVISION_SUB_TYPE, mids=[str(x.aid) for x in articles] + ) + return [x for x in articles if str(x.aid) in new_aids] async def _add_upgrade_article_content(article: Pixivision) -> None: - """在数据库中添加特辑文章信息(仅新增不更新)""" + """在数据库中添加特辑文章信息""" article_data = await article.query_article() async with begin_db_session() as session: - dal = PixivisionArticleDAL(session=session) - try: - await dal.query_unique(aid=article.aid) - except NoResultFound: - await dal.add( - aid=article.aid, - title=article_data.title_without_mark, - description=article_data.description, - tags=','.join(x.tag_name for x in article_data.tags_list), - artworks_id=','.join(str(x.artwork_id) for x in article_data.artwork_list), - url=article.url - ) - - -async def _add_new_pixivision_article(articles: Sequence["PixivisionIllustration"]) -> None: + await SocialMediaContentDAL(session=session).upsert( + source=_PIXIVISION_SUB_TYPE, + m_id=str(article.aid), + m_type='pixivision_article', + m_uid='-1', + title=article_data.title_without_mark, + content=f'{article_data.description}\n{",".join(x.tag_name for x in article_data.tags_list)}', + ref_content=','.join(str(x.artwork_id) for x in article_data.artwork_list), + ) + + +async def _add_new_pixivision_article(articles: Sequence['PixivisionIllustration']) -> None: """向数据库中写入 Pixivision 的特辑文章(仅新增不更新)""" tasks = [_add_upgrade_article_content(article=Pixivision(aid=article.aid)) for article in articles] await semaphore_gather(tasks=tasks, semaphore_num=10, return_exceptions=False) -async def _add_pixivision_article_artworks_into_database(article_data: "PixivisionArticle") -> None: +async def _add_pixivision_article_artworks_into_database(article_data: 'PixivisionArticle') -> None: """向数据库中写入 Pixivision 特辑文章中的作品""" add_artwork_tasks = [ PixivArtworkCollection(x.artwork_id).add_artwork_into_database_ignore_exists() @@ -86,7 +91,7 @@ async def _add_pixivision_article_artworks_into_database(article_data: "Pixivisi await semaphore_gather(tasks=add_artwork_tasks, semaphore_num=8, return_exceptions=False) -async def _add_pixivision_sub_source() -> "SubscriptionSource": +async def _add_pixivision_sub_source() -> 'SubscriptionSource': """在数据库中新增 Pixivision 订阅源""" async with begin_db_session() as session: sub_source = OmegaPixivisionSubSource(session=session) @@ -107,7 +112,7 @@ async def delete_pixivision_sub(interface: OmMI) -> None: await interface.entity.delete_subscription(subscription_source=source_res) -async def _query_subscribed_entity() -> list["Entity"]: +async def _query_subscribed_entity() -> list['Entity']: """查询已经订阅了 Pixivision 的内部 Entity 对象""" async with begin_db_session() as session: sub_source = OmegaPixivisionSubSource(session=session) @@ -115,7 +120,7 @@ async def _query_subscribed_entity() -> list["Entity"]: return subscribed_entity -async def generate_pixivision_illustration_list_preview(page: int = 1) -> "TemporaryResource": +async def generate_pixivision_illustration_list_preview(page: int = 1) -> 'TemporaryResource': """根据 Pixivision Illustration 导览页面内容生成预览图""" illustration_result = await Pixivision.query_illustration_list(page=page) title = f'Pixivision Illustration - Page {page}' @@ -132,7 +137,7 @@ async def generate_pixivision_illustration_list_preview(page: int = 1) -> "Tempo ) -async def _generate_pixivision_article_preview(title: str, article_data: "PixivisionArticle") -> "TemporaryResource": +async def _generate_pixivision_article_preview(title: str, article_data: 'PixivisionArticle') -> 'TemporaryResource': """根据 Pixivision 特辑内容生成预览图""" return await PixivArtworkProxy.generate_artworks_preview( preview_name=title, @@ -179,7 +184,7 @@ async def format_pixivision_article_message(article: Pixivision, msg_prefix: str return send_message -async def _msg_sender(entity: "Entity", message: str | OmegaMessage) -> None: +async def _msg_sender(entity: 'Entity', message: str | OmegaMessage) -> None: """向 entity 发送消息""" try: async with begin_db_session() as session: diff --git a/src/plugins/roll/__init__.py b/src/plugins/roll/__init__.py index 0c363f32..a7f7907d 100644 --- a/src/plugins/roll/__init__.py +++ b/src/plugins/roll/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='Roll', description='【骰子插件】\n' @@ -37,5 +36,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/roll/command.py b/src/plugins/roll/command.py index b439bf65..7b063bd5 100644 --- a/src/plugins/roll/command.py +++ b/src/plugins/roll/command.py @@ -19,8 +19,9 @@ from src.database import AuthSettingDAL from src.params.handler import get_command_str_single_arg_parser_handler, get_set_default_state_handler -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state -from .consts import MODULE_NAME, PLUGIN_NAME, ATTR_PREFIX +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state +from .consts import ATTR_PREFIX, MODULE_NAME, PLUGIN_NAME from .model import RandomDice roll = CommandGroup( @@ -77,6 +78,7 @@ async def handle_roll( @roll.command( 'rd', + aliases={'rrd', '掷骰'}, handlers=[get_command_str_single_arg_parser_handler('expression')], ).got('expression', prompt='请掷骰子: AdB(kq)C(pb)DaE') async def handle_roll_dice( @@ -99,6 +101,7 @@ async def handle_roll_dice( @roll.command( 'ra', + aliases={'rra', '检定'}, handlers=[get_command_str_single_arg_parser_handler('attr')], ).got('attr', prompt='请输入需要鉴定的属性/技能名') async def handle_roll_attr( @@ -115,7 +118,7 @@ async def handle_roll_attr( attr_value = int(user_attr.value) except Exception as e: logger.warning(f'Roll | 查询 {interface.entity} 属性 {attr!r} 失败, {e}') - await interface.finish_reply(f'你还没有配置{attr!r}属性/技能, 或属性值异常, 请使用"/roll.rs {attr}"配置后再试') + await interface.finish_reply(f'你还没有{attr!r}属性/技能, 或属性值异常, 请使用"/rrs {attr}"获取属性/技能后再试') roll_result = await RandomDice.simple_roll(1, 100) if roll_result.result_int is None or roll_result.error_message is not None: @@ -124,7 +127,7 @@ async def handle_roll_attr( result_msg = '失败~' if roll_result.result_int > 96: - result_msg = "大失败~" + result_msg = '大失败~' if roll_result.result_int < attr_value: result_msg = '成功!' if roll_result.result_int < attr_value * 0.5: @@ -135,11 +138,13 @@ async def handle_roll_attr( result_msg = '大成功!!' await interface.finish_reply( - f'你对【{attr}({attr_value})】\n进行了检定, 1D100=>{roll_result.result_int}\n{result_msg}') + f'你进行了【{attr}({attr_value})】检定,\n1D100=>{roll_result.result_int}\n{result_msg}' + ) @roll.command( 'rs', + aliases={'rrs'}, handlers=[get_command_str_single_arg_parser_handler('attr')], ).got('attr', prompt='请输入需要随机的属性/技能名') async def handle_roll_set_attr( @@ -174,6 +179,7 @@ async def handle_roll_set_attr( @roll.command( 'rc', + aliases={'rrc'}, handlers=[get_command_str_single_arg_parser_handler('attr')], ).got('attr', prompt='请输入需要移除的属性/技能名') async def handle_roll_clear_attr( @@ -197,6 +203,7 @@ async def handle_roll_clear_attr( @roll.command( 'rca', + aliases={'rrca'}, handlers=[get_set_default_state_handler('ensure', value=None)], ).got('ensure') async def handle_roll_clear_all_attr( @@ -220,7 +227,7 @@ async def handle_roll_clear_all_attr( await interface.finish_reply('已取消操作') -@roll.command('show').handle() +@roll.command('show', aliases={'rlsa'}).handle() async def handle_show_attr(interface: Annotated[OmMI, Depends(OmMI.depend('user'))]) -> None: try: attrs = await interface.entity.query_plugin_all_auth_setting(module=MODULE_NAME, plugin=PLUGIN_NAME) diff --git a/src/plugins/roll/model.py b/src/plugins/roll/model.py index de4a9e6d..3411384a 100644 --- a/src/plugins/roll/model.py +++ b/src/plugins/roll/model.py @@ -8,7 +8,6 @@ @Software : PyCharm """ -from typing import Optional from nonebot.utils import run_sync from onedice import RD @@ -25,14 +24,14 @@ class DiceResult(BaseDice): """骰子结果""" origin_data_raw: str = Field(alias='originDataRaw', description='原始输入的掷骰表达式') origin_data: str = Field(alias='originData', description='转小写的掷骰表达式') - result_int: Optional[int] = Field(alias='resInt', description='掷骰结果') - result_min: Optional[int] = Field(alias='resIntMin', description='掷骰理论最小值') - result_max: Optional[int] = Field(alias='resIntMax', description='掷骰理论最大值') - result_detail: Optional[str] = Field(alias='resDetail', description='掷骰结果表达式') - result_error: Optional[int] = Field(alias='resError', description='错误代码') - custom_default: Optional[dict] = Field(alias='customDefault', description='自定义类型') - value_map: Optional[dict] = Field(alias='valueTable', description='预设参数表') - rule_mode: Optional[str] = Field(alias='ruleMode', description='规则模式') + result_int: int | None = Field(alias='resInt', description='掷骰结果') + result_min: int | None = Field(alias='resIntMin', description='掷骰理论最小值') + result_max: int | None = Field(alias='resIntMax', description='掷骰理论最大值') + result_detail: str | None = Field(alias='resDetail', description='掷骰结果表达式') + result_error: int | None = Field(alias='resError', description='错误代码') + custom_default: dict | None = Field(alias='customDefault', description='自定义类型') + value_map: dict | None = Field(alias='valueTable', description='预设参数表') + rule_mode: str | None = Field(alias='ruleMode', description='规则模式') @property def error_message(self) -> str | None: @@ -67,9 +66,10 @@ def error_message(self) -> str | None: return 'UNKNOWN_FATAL: 未知异常' -class RandomDice(object): +class RandomDice: """骰子""" - def __init__(self, expression: str, value_map: Optional[dict[str, int]] = None): + + def __init__(self, expression: str, value_map: dict[str, int] | None = None): """ :param expression: 掷骰表达式 :param value_map: 预设属性/参数表 @@ -77,7 +77,7 @@ def __init__(self, expression: str, value_map: Optional[dict[str, int]] = None): self._expression = expression self._value_map = value_map self._dice = RD(self._expression, valueTable=self._value_map) - self.last_result: Optional[DiceResult] = None + self.last_result: DiceResult | None = None @run_sync def roll(self) -> DiceResult: diff --git a/src/plugins/shindan_maker/__init__.py b/src/plugins/shindan_maker/__init__.py index f3c1fc85..ee20fee4 100644 --- a/src/plugins/shindan_maker/__init__.py +++ b/src/plugins/shindan_maker/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='ShindanMaker', description='【ShindanMaker 占卜插件】\n' @@ -25,5 +24,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/shindan_maker/command.py b/src/plugins/shindan_maker/command.py index e1745068..be3b07a4 100644 --- a/src/plugins/shindan_maker/command.py +++ b/src/plugins/shindan_maker/command.py @@ -15,9 +15,10 @@ from nonebot.params import ArgStr, Depends from nonebot.plugin import CommandGroup -from src.params.handler import get_command_str_single_arg_parser_handler, get_command_str_multi_args_parser_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state -from src.utils.process_utils import semaphore_gather +from src.params.handler import get_command_str_multi_args_parser_handler, get_command_str_single_arg_parser_handler +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state +from src.utils import semaphore_gather from .data_source import ShindanMaker shindan_maker = CommandGroup( diff --git a/src/plugins/shindan_maker/data_source.py b/src/plugins/shindan_maker/data_source.py index 5e5a9319..01d3c358 100644 --- a/src/plugins/shindan_maker/data_source.py +++ b/src/plugins/shindan_maker/data_source.py @@ -15,23 +15,23 @@ from nonebot.log import logger from pydantic import ValidationError -from rapidfuzz import process, fuzz +from rapidfuzz import fuzz, process from zhconv import convert as zh_convert -from src.compat import parse_json_as, dump_json_as +from src.compat import dump_json_as, parse_json_as from src.resource import TemporaryResource -from src.utils.common_api import BaseCommonAPI -from src.utils.process_utils import semaphore_gather +from src.utils import BaseCommonAPI, semaphore_gather from .config import shindan_maker_plugin_config from .helper import ( parse_searching_result_page, parse_shindan_page_title, parse_shindan_page_token, - parse_shindan_result_page + parse_shindan_result_page, ) if TYPE_CHECKING: from nonebot.internal.driver import CookieTypes + from .model import ShindanMakerResult, ShindanMakerSearchResult _SHINDAN_CACHE: dict[str, int] = {} @@ -72,7 +72,7 @@ def _get_default_headers(cls) -> dict[str, str]: return {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None @classmethod @@ -80,7 +80,7 @@ async def download_resource( cls, url: str, *, - custom_file_name: Optional[str] = None, + custom_file_name: str | None = None, subdir: str | None = None, ) -> TemporaryResource: """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" @@ -125,8 +125,8 @@ async def _upgrade_shindan_cache(cls, data: dict[str, int]) -> None: @classmethod async def ranking_list( cls, - mode: Optional[Literal['pickup', 'latest', 'daily', 'monthly', 'favorite', 'favhot', 'overall']] = None - ) -> list["ShindanMakerSearchResult"]: + mode: Literal['pickup', 'latest', 'daily', 'monthly', 'favorite', 'favhot', 'overall'] | None = None + ) -> list['ShindanMakerSearchResult']: """列出占卜排行榜 :param mode: 排序方式, 默认: 按热度, pickup: 最新热度, latest: 最新添加, daily: 每日排名, monthly: 每月排名, favorite: 收藏数, favhot: 最新收藏, overall: 综合 @@ -143,11 +143,11 @@ async def search( keyword: str, *, mode: Literal['search', 'themes'] = 'search', - last_number: Optional[int] = None, - page: Optional[int] = None, - order: Optional[Literal['popular', 'favorites']] = None + last_number: int | None = None, + page: int | None = None, + order: Literal['popular', 'favorites'] | None = None - ) -> list["ShindanMakerSearchResult"]: + ) -> list['ShindanMakerSearchResult']: """搜索占卜 :param keyword: 搜索关键词 @@ -168,7 +168,7 @@ async def search( return await parse_searching_result_page(content=await cls._get_resource_as_text(url=search_url, params=params)) @classmethod - async def complex_ranking(cls) -> list["ShindanMakerSearchResult"]: + async def complex_ranking(cls) -> list['ShindanMakerSearchResult']: """通过排行榜获取更多的占卜""" searching_tasks = [ cls.ranking_list(), @@ -186,7 +186,7 @@ async def complex_ranking(cls) -> list["ShindanMakerSearchResult"]: return result @classmethod - async def complex_search(cls, keyword: str) -> list["ShindanMakerSearchResult"]: + async def complex_search(cls, keyword: str) -> list['ShindanMakerSearchResult']: """搜索更多的占卜""" keyword_ht = zh_convert(keyword, 'zh-hant') @@ -206,7 +206,7 @@ async def complex_search(cls, keyword: str) -> list["ShindanMakerSearchResult"]: await cls._upgrade_shindan_cache(data={item.name: item.id for item in result}) return result - async def query_shindan_result(self, input_name: str) -> "ShindanMakerResult": + async def query_shindan_result(self, input_name: str) -> 'ShindanMakerResult': """获取占卜结果 :param input_name: 占卜对象名称 @@ -246,7 +246,7 @@ async def query_shindan_result(self, input_name: str) -> "ShindanMakerResult": return await parse_shindan_result_page(content=self._parse_content_as_text(response=response)) @classmethod - async def fuzzy_shindan(cls, shindan: str, input_name: str) -> Optional["ShindanMakerResult"]: + async def fuzzy_shindan(cls, shindan: str, input_name: str) -> Optional['ShindanMakerResult']: """通过模糊查找进行占卜""" if not _SHINDAN_CACHE: await cls._read_shindan_cache() diff --git a/src/plugins/sticker_maker/__init__.py b/src/plugins/sticker_maker/__init__.py index 7f5e35db..4ee8f7c5 100644 --- a/src/plugins/sticker_maker/__init__.py +++ b/src/plugins/sticker_maker/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='表情包', description='【表情包助手插件】\n' @@ -22,5 +21,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/sticker_maker/command.py b/src/plugins/sticker_maker/command.py index e50851b6..a2cc8b16 100644 --- a/src/plugins/sticker_maker/command.py +++ b/src/plugins/sticker_maker/command.py @@ -16,8 +16,9 @@ from nonebot.typing import T_State from src.params.handler import get_command_str_multi_args_parser_handler, get_set_default_state_handler -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state -from .render import get_render, get_all_render_name, download_source_image +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state +from .render import download_source_image, get_all_render_name, get_render sticker_maker = on_command( 'sticker-maker', diff --git a/src/plugins/sticker_maker/render/__init__.py b/src/plugins/sticker_maker/render/__init__.py index ed1dec77..2f10ee17 100644 --- a/src/plugins/sticker_maker/render/__init__.py +++ b/src/plugins/sticker_maker/render/__init__.py @@ -8,8 +8,7 @@ @Software : PyCharm """ -from .renders import get_render, get_all_render_name, download_source_image - +from .renders import download_source_image, get_all_render_name, get_render __all__ = [ 'get_render', diff --git a/src/plugins/sticker_maker/render/consts.py b/src/plugins/sticker_maker/render/consts.py index 98325bfa..c73e31b7 100644 --- a/src/plugins/sticker_maker/render/consts.py +++ b/src/plugins/sticker_maker/render/consts.py @@ -10,7 +10,6 @@ from src.resource import StaticResource, TemporaryResource - FONT_RESOURCE: StaticResource = StaticResource('fonts') """默认字体文件目录""" diff --git a/src/plugins/sticker_maker/render/model.py b/src/plugins/sticker_maker/render/model.py index 689f1ccc..0ff64258 100644 --- a/src/plugins/sticker_maker/render/model.py +++ b/src/plugins/sticker_maker/render/model.py @@ -9,9 +9,10 @@ """ import abc +from collections.abc import Sequence from datetime import datetime from io import BytesIO -from typing import TYPE_CHECKING, Literal, Optional, Sequence +from typing import TYPE_CHECKING, Literal, Optional import imageio.v3 as iio from PIL import Image @@ -28,8 +29,8 @@ class BaseStickerRender(abc.ABC): def __init__( self, - text: Optional[str] = None, - external_image: Optional["TemporaryResource"] = None, + text: str | None = None, + external_image: Optional['TemporaryResource'] = None, ) -> None: """使用待生成的素材实例化生成器 @@ -75,21 +76,21 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: @classmethod @abc.abstractmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: """获取获取制作表情包所需要的字体集""" raise NotImplementedError @classmethod @abc.abstractmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: """获取获取制作表情包所需要的模板图片集""" raise NotImplementedError - def get_external_image(self) -> Optional["TemporaryResource"]: + def get_external_image(self) -> Optional['TemporaryResource']: """获取获取制作表情包所需要的, 由用户提供的图片 (默认用户仅能通过命令提供一张图片)""" return self.__external_image - def get_text(self) -> Optional[str]: + def get_text(self) -> str | None: """生成表情包所使用的文字""" return self.__text @@ -98,14 +99,14 @@ def set_text(self, text: str) -> None: self.__text = text @staticmethod - def _resize_to_width(image: "Image.Image", width: int) -> "Image.Image": + def _resize_to_width(image: 'Image.Image', width: int) -> 'Image.Image': """等比缩放 PIL.Image.Image 为指定宽度""" image_resize_height = width * image.height // image.width make_image = image.resize((width, image_resize_height)) return make_image @staticmethod - def _output_pil_image(image: "Image.Image", output_format: str = 'JPEG') -> bytes: + def _output_pil_image(image: 'Image.Image', output_format: str = 'JPEG') -> bytes: """提取 PIL.Image.Image 为 bytes""" match output_format.upper(): case 'PNG': @@ -125,28 +126,28 @@ def _output_pil_image(image: "Image.Image", output_format: str = 'JPEG') -> byte @abc.abstractmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': """模板处理核心流程, 负责使用提供的各项素材生成表情包, 返回为表情包图片的内容 (默认用户仅能通过命令提供一张图片)""" raise NotImplementedError @classmethod def _main_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> list["Image.Image"]: + ) -> list['Image.Image']: """针对用户提供的图片进行处理, 自动识别用户提供图片是否为动态图片, 返回为表情包图片的内容""" def iter_gif_frame(_image: Image.Image): @@ -176,7 +177,7 @@ def iter_gif_frame(_image: Image.Image): return output_images - def _make(self) -> "TemporaryResource": + def _make(self) -> 'TemporaryResource': """默认的表情包处理流程""" external_image_file = self.get_external_image() external_image = Image.open(external_image_file.path) if external_image_file is not None else None @@ -216,11 +217,11 @@ def _make(self) -> "TemporaryResource": return save_file @run_sync - def _async_make(self) -> "TemporaryResource": + def _async_make(self) -> 'TemporaryResource': """异步执行默认的表情包处理流程""" return self._make() - async def make(self) -> "TemporaryResource": + async def make(self) -> 'TemporaryResource': """表情包制作入口函数, 默认使用 self._async_make 方法制作表情包并输出, 但可被重载并自定义其他制作流程""" return await self._async_make() diff --git a/src/plugins/sticker_maker/render/renders.py b/src/plugins/sticker_maker/render/renders.py index da5317b8..d993950e 100644 --- a/src/plugins/sticker_maker/render/renders.py +++ b/src/plugins/sticker_maker/render/renders.py @@ -8,16 +8,17 @@ @Software : PyCharm """ +from collections.abc import Sequence from datetime import date -from typing import TYPE_CHECKING, Any, Optional, Sequence, Literal +from typing import TYPE_CHECKING, Any, Literal, Optional import numpy -from PIL import Image, ImageDraw, ImageFont, ImageEnhance +from PIL import Image, ImageDraw, ImageEnhance, ImageFont -from src.service import OmegaRequests +from src.utils import OmegaRequests from src.utils.image_utils import ImageUtils from src.utils.tencent_cloud_api import TencentTMT -from .consts import STATIC_RESOURCE, FONT_RESOURCE, TMP_PATH +from .consts import FONT_RESOURCE, STATIC_RESOURCE, TMP_PATH from .model import BaseStickerRender if TYPE_CHECKING: @@ -44,24 +45,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'PNG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('pixel.ttf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('traitor', 'default_bg.png')] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': image = cls._resize_to_width(image=static_images[0], width=output_width) text = '有内鬼, 中止交易' if text is None else text[:100] @@ -115,24 +116,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('SourceHanSansSC-Regular.otf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('jichou', 'default_bg.png')] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': image = cls._resize_to_width(image=static_images[0], width=output_width) # 处理文本主体 @@ -179,24 +180,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'PNG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('SourceHanSansSC-Heavy.otf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': # 处理文本主体 text = 'Git Hub' if text is None else text test_sentences = text.strip().split(maxsplit=1) @@ -275,24 +276,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('SourceHanSansSC-Regular.otf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('luxunsay', 'default_bg.png')] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': image = cls._resize_to_width(image=static_images[0], width=output_width) # 处理文本主体 @@ -347,7 +348,7 @@ class LuxunWriteRender(LuxunSayRender): """鲁迅写表情包模板""" @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('luxunwrite', 'default_bg.png')] @@ -371,24 +372,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('HanYiWeiBeiJian.ttf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('jiangzhuang', 'default_bg.png')] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': image = cls._resize_to_width(image=static_images[0], width=output_width) # 处理文本主体 @@ -430,24 +431,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('HanYiWeiBeiJian.ttf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('xibaoh', 'default_bg.png')] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': image = cls._resize_to_width(image=static_images[0], width=output_width) # 处理文本主体 @@ -494,24 +495,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('SourceHanSerif-Bold.ttc')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('xibaos', 'default_bg.png')] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': image = cls._resize_to_width(image=static_images[0], width=output_width) # 处理文本主体 @@ -558,24 +559,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('msyhbd.ttc')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGB': @@ -623,24 +624,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('msyhbd.ttc')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGB': @@ -718,24 +719,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('msyhbd.ttc')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGB': @@ -790,24 +791,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('msyhbd.ttc')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGB': @@ -860,24 +861,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGB': @@ -909,24 +910,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('SourceHanSansSC-Bold.otf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGBA': @@ -983,24 +984,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'PNG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('marriage', 'default_bg.png')] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGBA': @@ -1036,24 +1037,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'JPEG' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('fzzxhk.ttf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGB': @@ -1108,7 +1109,7 @@ async def _translate_preprocessor(self) -> None: text_ja = text_ja.replace('\n', ' ') self.set_text(f'{text_origin.strip()}\n{text_ja.strip()}') - async def make(self) -> "TemporaryResource": + async def make(self) -> 'TemporaryResource': await self._translate_preprocessor() return await self._async_make() @@ -1133,37 +1134,37 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'GIF' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('petpet', f'template_p{i}.png') for i in range(5)] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': raise NotImplementedError @classmethod def _main_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> list["Image.Image"]: + ) -> list['Image.Image']: resize_paste_loc: list[tuple[tuple[int, int], tuple[int, int]]] = [ ((95, 95), (12, 15)), ((97, 80), (11, 30)), @@ -1207,24 +1208,24 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'GIF' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('worship', f'template_p{i}.png') for i in range(10)] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': raise NotImplementedError @staticmethod @@ -1250,14 +1251,14 @@ def _get_perspective_data( @classmethod def _main_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> list["Image.Image"]: + ) -> list['Image.Image']: if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGBA': @@ -1305,37 +1306,37 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'GIF' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('twist', f'template_p{i}.png') for i in range(10)] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': raise NotImplementedError @classmethod def _main_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> list["Image.Image"]: + ) -> list['Image.Image']: if external_image is None: raise ValueError('external_image can not be None') if external_image.format != 'RGBA': @@ -1382,37 +1383,37 @@ def get_output_format(cls) -> Literal['JPEG', 'PNG', 'GIF']: return 'GIF' @classmethod - def get_default_fonts(cls) -> list["StaticResource"]: + def get_default_fonts(cls) -> list['StaticResource']: return [FONT_RESOURCE('SourceHanSansSC-Regular.otf')] @classmethod - def get_static_images(cls) -> list["StaticResource"]: + def get_static_images(cls) -> list['StaticResource']: return [STATIC_RESOURCE('wangjingze', f'template_p{i}.jpg') for i in range(46)] @classmethod def _core_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> "Image.Image": + ) -> 'Image.Image': raise NotImplementedError @classmethod def _main_render( cls, - text: Optional[str], - static_images: Sequence["Image.Image"], - external_image: Optional["Image.Image"], + text: str | None, + static_images: Sequence['Image.Image'], + external_image: Optional['Image.Image'], *, - fonts: Sequence["StaticResource"], + fonts: Sequence['StaticResource'], output_width: int, output_format: str, - ) -> list["Image.Image"]: + ) -> list['Image.Image']: text = '我王境泽就是饿死 死外边,从这跳下去 也不会吃你们一点东西 真香' if text is None else text # 分割文本 @@ -1483,7 +1484,7 @@ def get_all_render_name() -> list[str]: return [str(x) for x in _ALL_Render.keys()] -async def download_source_image(url: str) -> "TemporaryResource": +async def download_source_image(url: str) -> 'TemporaryResource': """下载图片到本地, 保持原始文件名, 直接覆盖同名文件""" file_name = OmegaRequests.hash_url_file_name('sticker_source_tmp', url=url) return await OmegaRequests().download(url=url, file=TMP_PATH(file_name)) diff --git a/src/plugins/tarot/__init__.py b/src/plugins/tarot/__init__.py index c794f5b4..608b7785 100644 --- a/src/plugins/tarot/__init__.py +++ b/src/plugins/tarot/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='塔罗牌', description='【塔罗牌插件】\n' @@ -24,5 +23,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/tarot/command.py b/src/plugins/tarot/command.py index 4559b0e7..07b973d8 100644 --- a/src/plugins/tarot/command.py +++ b/src/plugins/tarot/command.py @@ -17,9 +17,10 @@ from src.params.handler import get_command_str_single_arg_parser_handler from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, OmegaMessageSegment, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state from .helper import generate_tarot_card, get_tarot_resource_name, set_tarot_resource -from .resources import get_tarot_resource, get_available_tarot_resource +from .resources import get_available_tarot_resource, get_tarot_resource @on_command( diff --git a/src/plugins/tarot/helper.py b/src/plugins/tarot/helper.py index 6365407f..54da5e4e 100644 --- a/src/plugins/tarot/helper.py +++ b/src/plugins/tarot/helper.py @@ -21,15 +21,15 @@ from .resources import TarotResource if TYPE_CHECKING: - from src.service import OmegaMatcherInterface as OmMI from src.resource import TemporaryResource + from src.service import OmegaMatcherInterface as OmMI _TAROT_RESOURCE_NODE: Literal['tarot_resource'] = 'tarot_resource' """配置卡牌资源的节点""" -async def get_tarot_resource_name(interface: "OmMI") -> str | None: +async def get_tarot_resource_name(interface: 'OmMI') -> str | None: """根据当前 Event 获取对应 Entity 配置的塔罗资源名""" if interface.matcher.plugin is None: return None @@ -49,7 +49,7 @@ async def get_tarot_resource_name(interface: "OmMI") -> str | None: return None -async def set_tarot_resource(resource_name: str, interface: "OmMI") -> None: +async def set_tarot_resource(resource_name: str, interface: 'OmMI') -> None: """根据当前 event 配置对应对象塔罗资源""" if interface.matcher.plugin is None: return None @@ -71,7 +71,7 @@ async def generate_tarot_card( need_upright: bool = True, need_reversed: bool = True, width: int = 1024 -) -> "TemporaryResource": +) -> 'TemporaryResource': """绘制塔罗卡片 :param id_: 牌id @@ -141,7 +141,7 @@ def _handle_tarot_card() -> bytes: # 生成背景 background = Image.new( - mode="RGB", + mode='RGB', size=(width, background_height), color=(255, 255, 255)) diff --git a/src/plugins/tarot/resources.py b/src/plugins/tarot/resources.py index c0de58aa..faef084f 100644 --- a/src/plugins/tarot/resources.py +++ b/src/plugins/tarot/resources.py @@ -14,7 +14,7 @@ from .model import TarotCards, TarotPack -class TarotResource(object): +class TarotResource: """塔罗牌资源基类""" def __init__(self, source_name: str, pack: TarotPack, file_format: str): self.resource_folder: StaticResource = tarot_local_resource_config.image_resource_folder(source_name) diff --git a/src/plugins/translate/__init__.py b/src/plugins/translate/__init__.py index 499b1dc0..f5e96e77 100644 --- a/src/plugins/translate/__init__.py +++ b/src/plugins/translate/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='翻译', description='【翻译插件】\n' @@ -23,5 +22,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/translate/command.py b/src/plugins/translate/command.py index 28c14e2e..3d5d5969 100644 --- a/src/plugins/translate/command.py +++ b/src/plugins/translate/command.py @@ -17,7 +17,8 @@ from pydantic import BaseModel, ConfigDict from src.params.handler import get_command_str_single_arg_parser_handler, get_shell_command_parse_failed_handler -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from src.utils.tencent_cloud_api import TencentTMT diff --git a/src/plugins/weibo_monitor/__init__.py b/src/plugins/weibo_monitor/__init__.py index 30143a4c..de22b642 100644 --- a/src/plugins/weibo_monitor/__init__.py +++ b/src/plugins/weibo_monitor/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='微博订阅', description='【微博订阅插件】\n' @@ -26,5 +25,4 @@ from . import command as command from . import monitor as monitor - __all__ = [] diff --git a/src/plugins/weibo_monitor/command.py b/src/plugins/weibo_monitor/command.py index 7f77e8fe..94800ab3 100644 --- a/src/plugins/weibo_monitor/command.py +++ b/src/plugins/weibo_monitor/command.py @@ -16,7 +16,8 @@ from src.params.handler import get_command_str_single_arg_parser_handler, get_set_default_state_handler from src.params.permission import IS_ADMIN -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from src.utils.weibo_api import Weibo from .helpers import add_weibo_user_sub, delete_weibo_user_sub, query_entity_subscribed_weibo_user_sub_source from .monitor import scheduler diff --git a/src/plugins/weibo_monitor/helpers.py b/src/plugins/weibo_monitor/helpers.py index 0fa1dcf1..f146aed9 100644 --- a/src/plugins/weibo_monitor/helpers.py +++ b/src/plugins/weibo_monitor/helpers.py @@ -8,23 +8,27 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Iterable +from collections.abc import Iterable +from typing import TYPE_CHECKING from nonebot import logger from nonebot.exception import ActionFailed -from sqlalchemy.exc import NoResultFound -from src.database import WeiboDetailDAL, begin_db_session +from src.database import SocialMediaContentDAL, begin_db_session from src.database.internal.subscription_source import SubscriptionSource, SubscriptionSourceType from src.service import ( - OmegaMatcherInterface as OmMI, - OmegaEntityInterface as OmEI, OmegaEntity, OmegaMessage, OmegaMessageSegment, ) +from src.service import ( + OmegaEntityInterface as OmEI, +) +from src.service import ( + OmegaMatcherInterface as OmMI, +) from src.service.omega_base.internal import OmegaWeiboUserSubSource -from src.utils.process_utils import run_async_delay, semaphore_gather +from src.utils import run_async_delay, semaphore_gather from src.utils.weibo_api import Weibo if TYPE_CHECKING: @@ -43,15 +47,17 @@ async def _query_weibo_sub_source(uid: int) -> SubscriptionSource: return source_res -async def _check_new_weibo(cards: Iterable["WeiboCard"]) -> list["WeiboCard"]: +async def _check_new_weibo(cards: Iterable['WeiboCard']) -> list['WeiboCard']: """检查新的微博(数据库中没有的)""" async with begin_db_session() as session: - all_mids = [x.mblog.id for x in cards] - new_mids = await WeiboDetailDAL(session=session).query_not_exists_ids(mids=all_mids) - return [x for x in cards if x.mblog.id in new_mids] + all_mids = [str(x.mblog.id) for x in cards] + new_mids = await SocialMediaContentDAL(session=session).query_source_not_exists_mids( + source=WEIBO_SUB_TYPE, mids=all_mids + ) + return [x for x in cards if str(x.mblog.id) in new_mids] -async def _add_upgrade_weibo_content(card: "WeiboCard") -> None: +async def _add_upgrade_weibo_content(card: 'WeiboCard') -> None: """在数据库中添加微博内容""" retweeted_content = ( card.mblog.retweeted_status.text @@ -60,14 +66,15 @@ async def _add_upgrade_weibo_content(card: "WeiboCard") -> None: ) async with begin_db_session() as session: - dal = WeiboDetailDAL(session=session) - try: - weibo = await dal.query_unique(mid=card.mblog.id) - await dal.update(id_=weibo.id, content=card.mblog.text, retweeted_content=retweeted_content) - except NoResultFound: - await dal.add( - mid=card.mblog.id, uid=card.mblog.user.id, content=card.mblog.text, retweeted_content=retweeted_content - ) + await SocialMediaContentDAL(session=session).upsert( + source=WEIBO_SUB_TYPE, + m_id=str(card.mblog.id), + m_type=card.card_type, + m_uid=str(card.mblog.user.id), + title=f'{card.mblog.user.screen_name}的微博', + content=card.mblog.text, + ref_content=retweeted_content, + ) async def _add_user_new_weibo_content(uid: int) -> None: @@ -122,7 +129,7 @@ async def query_all_subscribed_weibo_user_sub_source() -> list[int]: return [int(x.sub_id) for x in source_res] -async def query_subscribed_entity_by_weibo_user(uid: int) -> list["Entity"]: +async def query_subscribed_entity_by_weibo_user(uid: int) -> list['Entity']: """根据微博用户查询已经订阅了这个用户的内部 Entity 对象""" async with begin_db_session() as session: sub_source = OmegaWeiboUserSubSource(session=session, uid=uid) @@ -130,7 +137,7 @@ async def query_subscribed_entity_by_weibo_user(uid: int) -> list["Entity"]: return subscribed_entity -async def _format_weibo_update_message(card: "WeiboCard") -> str | OmegaMessage: +async def _format_weibo_update_message(card: 'WeiboCard') -> str | OmegaMessage: """处理微博内容为消息""" send_message = f'【微博】{card.mblog.user.screen_name}' img_urls = [] @@ -179,7 +186,7 @@ async def _format_weibo_update_message(card: "WeiboCard") -> str | OmegaMessage: return send_message -async def _msg_sender(entity: "Entity", message: str | OmegaMessage) -> None: +async def _msg_sender(entity: 'Entity', message: str | OmegaMessage) -> None: """向 entity 发送消息""" try: async with begin_db_session() as session: diff --git a/src/plugins/weibo_monitor/monitor.py b/src/plugins/weibo_monitor/monitor.py index 56b4475f..3ac98412 100644 --- a/src/plugins/weibo_monitor/monitor.py +++ b/src/plugins/weibo_monitor/monitor.py @@ -13,8 +13,8 @@ from nonebot.log import logger from src.exception import WebSourceException -from src.service import scheduler, reschedule_job -from src.utils.process_utils import semaphore_gather +from src.service import reschedule_job, scheduler +from src.utils import semaphore_gather from .helpers import query_all_subscribed_weibo_user_sub_source, weibo_user_monitor_main _MONITOR_JOB_ID: Literal['weibo_update_monitor'] = 'weibo_update_monitor' diff --git a/src/plugins/what_to_eat/__init__.py b/src/plugins/what_to_eat/__init__.py index 7a9d6aa6..0050ea42 100644 --- a/src/plugins/what_to_eat/__init__.py +++ b/src/plugins/what_to_eat/__init__.py @@ -10,7 +10,6 @@ from nonebot.plugin import PluginMetadata - __plugin_meta__ = PluginMetadata( name='今天吃啥', description='【今天吃啥】\n' @@ -26,5 +25,4 @@ from . import command as command - __all__ = [] diff --git a/src/plugins/what_to_eat/command.py b/src/plugins/what_to_eat/command.py index 1e82fad6..a99c3af2 100644 --- a/src/plugins/what_to_eat/command.py +++ b/src/plugins/what_to_eat/command.py @@ -15,7 +15,8 @@ from nonebot.params import Depends, RawCommand from nonebot.plugin import on_command -from src.service import OmegaMatcherInterface as OmMI, enable_processor_state +from src.service import OmegaMatcherInterface as OmMI +from src.service import enable_processor_state from .data_source import get_random_food_msg diff --git a/src/plugins/what_to_eat/data_source.py b/src/plugins/what_to_eat/data_source.py index 1270b6fb..bb8b479e 100644 --- a/src/plugins/what_to_eat/data_source.py +++ b/src/plugins/what_to_eat/data_source.py @@ -10,7 +10,8 @@ import random import re -from typing import Literal, Optional, Sequence +from collections.abc import Sequence +from typing import Literal from nonebot.log import logger from nonebot.utils import run_sync @@ -18,7 +19,8 @@ from src.compat import parse_json_as from src.resource import StaticResource, TemporaryResource -from src.service import OmegaMessage, OmegaMessageSegment, OmegaRequests +from src.service import OmegaMessage, OmegaMessageSegment +from src.utils import OmegaRequests _RESOURCE_PATH: StaticResource = StaticResource('images', 'what_to_eat') _TMP_PATH: TemporaryResource = TemporaryResource('what_to_eat') @@ -27,7 +29,7 @@ type FoodType = Literal['早', '午', '晚', '夜'] """菜品类型""" -_MENU_TMP: list["MenuFood"] = [] +_MENU_TMP: list['MenuFood'] = [] """菜单缓存""" @@ -67,7 +69,7 @@ async def _get_food_msg(food: MenuFood) -> OmegaMessage: @run_sync -def _get_random_food(menu: Sequence[MenuFood], food_type: Optional[FoodType] = None) -> MenuFood: +def _get_random_food(menu: Sequence[MenuFood], food_type: FoodType | None = None) -> MenuFood: """获取随机食谱""" match food_type: case '早': @@ -83,7 +85,7 @@ def _get_random_food(menu: Sequence[MenuFood], food_type: Optional[FoodType] = N return food -async def get_random_food_msg(food_type: Optional[FoodType] = None) -> OmegaMessage | OmegaMessageSegment: +async def get_random_food_msg(food_type: FoodType | None = None) -> OmegaMessage | OmegaMessageSegment: menu = await _get_menu() food = await _get_random_food(menu=menu, food_type=food_type) diff --git a/src/plugins/wordcloud/__init__.py b/src/plugins/wordcloud/__init__.py new file mode 100644 index 00000000..fd4ff1cb --- /dev/null +++ b/src/plugins/wordcloud/__init__.py @@ -0,0 +1,31 @@ +""" +@Author : Ailitonia +@Date : 2024/10/27 00:19 +@FileName : wordcloud +@Project : omega-miya +@Description : 词云插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from nonebot.plugin import PluginMetadata + +__plugin_meta__ = PluginMetadata( + name='词云', + description='【WordCloud 词云插件】\n' + '根据历史聊天记录生成词云图片', + usage='/词云\n' + '/本周词云\n' + '/本月词云\n' + '/我的词云\n' + '/我的本周词云\n' + '/我的本月词云\n\n' + '管理员命令:\n' + '/添加自定义词典', + config=None, + extra={'author': 'Ailitonia'}, +) + +from . import command as command + +__all__ = [] diff --git a/src/plugins/wordcloud/command.py b/src/plugins/wordcloud/command.py new file mode 100644 index 00000000..ed2e06ae --- /dev/null +++ b/src/plugins/wordcloud/command.py @@ -0,0 +1,168 @@ +""" +@Author : Ailitonia +@Date : 2024/11/17 22:43 +@FileName : command +@Project : omega-miya +@Description : 词云插件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from datetime import datetime, timedelta +from typing import Annotated + +from nonebot.internal.adapter import Bot as BaseBot +from nonebot.internal.adapter import Event as BaseEvent +from nonebot.log import logger +from nonebot.params import ArgStr, Depends +from nonebot.permission import SUPERUSER +from nonebot.plugin import CommandGroup + +from src.params.handler import get_command_str_single_arg_parser_handler +from src.service import OmegaMatcherInterface as OmMI +from src.service import OmegaMessageSegment, enable_processor_state +from .data_source import add_user_dict, query_entity_message_history, query_profile_image +from .helpers import draw_message_history_wordcloud + +# 注册事件响应器 +wordcloud = CommandGroup( + 'wordcloud', + priority=10, + block=True, + state=enable_processor_state(name='WordCloud', level=10, cooldown=45) +) + + +@wordcloud.command( + 'add-user-dict', + aliases={'词云添加自定义词典', '词云添加用户词典'}, + handlers=[get_command_str_single_arg_parser_handler('content')], + permission=SUPERUSER +).got('content', prompt='请输入需要添加的词语或短语:') +async def handle_add_user_dict( + interface: Annotated[OmMI, Depends(OmMI.depend())], + content: Annotated[str, ArgStr('content')], +) -> None: + content = content.strip() + try: + await add_user_dict(content=content) + await interface.send_reply(f'已添加自定义词典: {content}') + except Exception as e: + logger.error(f'WordCloud | 添加自定义词典失败, {e}') + await interface.send_reply('添加自定义词典失败') + + +@wordcloud.command('daily', aliases={'词云', '今日词云', '今天聊了啥'}).handle() +async def handle_daily_wordcloud( + bot: BaseBot, + event: BaseEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + start_time = datetime.now() - timedelta(days=1) + desc_text = '自一天前以来的消息词云' + await wordcloud_generate_handler( + bot=bot, event=event, interface=interface, start_time=start_time, desc_text=desc_text + ) + + +@wordcloud.command('weekly', aliases={'本周词云', '这周聊了啥'}).handle() +async def handle_weekly_wordcloud( + bot: BaseBot, + event: BaseEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + start_time = datetime.now() - timedelta(days=7) + desc_text = '自一周前以来的消息词云' + await wordcloud_generate_handler( + bot=bot, event=event, interface=interface, start_time=start_time, desc_text=desc_text + ) + + +@wordcloud.command('monthly', aliases={'本月词云'}).handle() +async def handle_monthly_wordcloud( + bot: BaseBot, + event: BaseEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + start_time = datetime.now() - timedelta(days=30) + desc_text = '自一个月前以来的消息词云' + await wordcloud_generate_handler( + bot=bot, event=event, interface=interface, start_time=start_time, desc_text=desc_text + ) + + +@wordcloud.command('my-daily', aliases={'我的词云', '我的今日词云', '我今天聊了啥'}).handle() +async def handle_my_daily_wordcloud( + bot: BaseBot, + event: BaseEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + start_time = datetime.now() - timedelta(days=1) + desc_text = f'{interface.get_event_user_nickname()}的今日词云' + await wordcloud_generate_handler( + bot=bot, event=event, interface=interface, start_time=start_time, desc_text=desc_text, match_user=True + ) + + +@wordcloud.command('my-weekly', aliases={'我的本周词云', '我这周聊了啥'}).handle() +async def handle_my_weekly_wordcloud( + bot: BaseBot, + event: BaseEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + start_time = datetime.now() - timedelta(days=7) + desc_text = f'{interface.get_event_user_nickname()}的本周词云' + await wordcloud_generate_handler( + bot=bot, event=event, interface=interface, start_time=start_time, desc_text=desc_text, match_user=True + ) + + +@wordcloud.command('my-monthly', aliases={'我的本月词云'}).handle() +async def handle_my_monthly_wordcloud( + bot: BaseBot, + event: BaseEvent, + interface: Annotated[OmMI, Depends(OmMI.depend())], +) -> None: + start_time = datetime.now() - timedelta(days=30) + desc_text = f'{interface.get_event_user_nickname()}的本月词云' + await wordcloud_generate_handler( + bot=bot, event=event, interface=interface, start_time=start_time, desc_text=desc_text, match_user=True + ) + + +async def wordcloud_generate_handler( + bot: BaseBot, + event: BaseEvent, + interface: OmMI, + start_time: datetime, + desc_text: str, + *, + match_event: bool = True, + match_user: bool = False, +) -> None: + """词云处理流程 Handler""" + try: + message_history_list = await query_entity_message_history( + bot=bot, event=event, start_time=start_time, match_event=match_event, match_user=match_user + ) + if len(message_history_list) < 100 and len([x for x in message_history_list if x.message_text.strip()]) < 10: + logger.info(f'WordCloud | {interface.entity} 没有足够的历史消息记录用于生成词云') + await interface.send_reply('没有足够的历史消息记录用于生成词云, 请稍后再试') + return + + profile_image = await query_profile_image(bot, event, match_user=match_user) + + desc_text += f'\n已统计 {len(message_history_list)} 条消息\n生成于: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}' + + wordcloud_image = await draw_message_history_wordcloud( + messages=message_history_list, profile_image_file=profile_image, desc_text=desc_text + ) + + logger.success(f'WordCloud | 生成 {interface.entity} 自 {start_time} 以来的词云成功') + await interface.send_reply(OmegaMessageSegment.image(wordcloud_image.path)) + except Exception as e: + logger.error(f'WordCloud | 生成 {interface.entity} 自 {start_time} 以来的词云失败, {e!r}') + await interface.send_reply('生成词云失败, 请稍后再试或联系管理员处理') + + +__all__ = [] diff --git a/src/plugins/wordcloud/config.py b/src/plugins/wordcloud/config.py new file mode 100644 index 00000000..98b9b08b --- /dev/null +++ b/src/plugins/wordcloud/config.py @@ -0,0 +1,87 @@ +""" +@Author : Ailitonia +@Date : 2024/10/27 00:23 +@FileName : config +@Project : omega-miya +@Description : 词云插件配置 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from dataclasses import dataclass +from typing import Any, Literal + +from nonebot import get_plugin_config, logger +from pydantic import BaseModel, ConfigDict, ValidationError + +from src.resource import StaticResource, TemporaryResource +from src.service.artwork_collection import ALLOW_ARTWORK_ORIGIN + + +class WordcloudPluginConfig(BaseModel): + """Wordcloud 插件配置""" + # 从全局配置读取命令头配置 + command_start: set[str] + + # 分词模式 + wordcloud_plugin_message_analyse_mode: Literal['TF-IDF', 'TextRank'] = 'TF-IDF' + # 排除机器人自身的消息 + wordcloud_plugin_exclude_bot_self_message: bool = True + + # 生成词云图片的尺寸 + wordcloud_plugin_generate_default_width: int = 1600 + wordcloud_plugin_generate_default_height: int = 1200 + + # 生成图片的背景颜色 + wordcloud_plugin_background_color: str = 'white' + # 是否额外使用内置图库中的作品作为背景图片 + wordcloud_plugin_enable_collected_artwork_background: bool = False + # 背景图图库来源, 可配置: pixiv, danbooru, gelbooru, konachan, yandere, local, 当配置为 `None` 时, 代表从所有的来源随机获取 + wordcloud_plugin_artwork_background_origin: ALLOW_ARTWORK_ORIGIN | None = 'pixiv' + + # 生成词云频率的颜色映射图 + wordcloud_plugin_colormap: str = 'plasma' + + model_config = ConfigDict(extra='ignore') + + @property + def default_image_size(self) -> tuple[int, int]: + return self.wordcloud_plugin_generate_default_width, self.wordcloud_plugin_generate_default_height + + @property + def wordcloud_default_options(self) -> dict[str, Any]: + return { + 'width': self.wordcloud_plugin_generate_default_width, + 'height': self.wordcloud_plugin_generate_default_height, + 'background_color': self.wordcloud_plugin_background_color, + 'colormap': self.wordcloud_plugin_colormap, + } + + +@dataclass +class WordcloudPluginLocalResourceConfig: + # 默认字体文件 + default_font_file = StaticResource('fonts', 'fzzxhk.ttf') + # 默认停用词清单 + default_stop_words_file = StaticResource('docs', 'wordcloud', 'stop_words', 'default_stop_words.txt') + # 默认输出路径 + default_output_dir = TemporaryResource('wordcloud', 'output') + # 头像缓存路径 + profile_image_tmp_dir = TemporaryResource('wordcloud', 'profile_image') + # 用户自定义词典位置 + user_dict_file = TemporaryResource('wordcloud', 'user_dict', 'user_dict.txt') + + +try: + wordcloud_plugin_resource_config = WordcloudPluginLocalResourceConfig() + wordcloud_plugin_config = get_plugin_config(WordcloudPluginConfig) +except ValidationError as e: + import sys + + logger.opt(colors=True).critical(f'Wordcloud 插件配置格式验证失败, 错误信息:\n{e}') + sys.exit(f'Wordcloud 插件配置格式验证失败, {e}') + +__all__ = [ + 'wordcloud_plugin_config', + 'wordcloud_plugin_resource_config', +] diff --git a/src/plugins/wordcloud/data_source.py b/src/plugins/wordcloud/data_source.py new file mode 100644 index 00000000..58e6e225 --- /dev/null +++ b/src/plugins/wordcloud/data_source.py @@ -0,0 +1,83 @@ +""" +@Author : Ailitonia +@Date : 2024/10/27 00:19 +@FileName : data_source +@Project : omega-miya +@Description : 词云内容生成模块 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from os import SEEK_END, SEEK_SET +from typing import TYPE_CHECKING, Optional + +from src.database import HistoryDAL, begin_db_session +from src.service import OmegaEntityInterface as OmEI +from src.service import OmegaMatcherInterface as OmMI +from src.utils import OmegaRequests +from .config import wordcloud_plugin_config, wordcloud_plugin_resource_config + +if TYPE_CHECKING: + from datetime import datetime + + from nonebot.internal.adapter import Bot as BaseBot + from nonebot.internal.adapter import Event as BaseEvent + + from src.database.internal.history import History + from src.resource import TemporaryResource + + +async def query_entity_message_history( + bot: 'BaseBot', + event: 'BaseEvent', + *, + start_time: Optional['datetime'] = None, + end_time: Optional['datetime'] = None, + match_event: bool = True, + match_user: bool = False, +) -> list['History']: + """查询当前事件的消息历史记录""" + async with begin_db_session() as session: + event_entity = OmMI.get_entity(bot, event, session, acquire_type='event') + user_entity = OmMI.get_entity(bot, event, session, acquire_type='user') + histories_list = await HistoryDAL(session).query_entity_records( + bot_self_id=bot.self_id, + event_entity_id=event_entity.entity_id if match_event else None, + user_entity_id=user_entity.entity_id if match_user else None, + start_time=start_time, + end_time=end_time, + exclude_bot_self_message=wordcloud_plugin_config.wordcloud_plugin_exclude_bot_self_message, + ) + return histories_list + + +async def query_profile_image(bot: 'BaseBot', event: 'BaseEvent', match_user: bool = False) -> 'TemporaryResource': + """获取头像""" + async with begin_db_session() as session: + if match_user: + entity = OmMI.get_entity(bot, event, session, acquire_type='user') + else: + entity = OmMI.get_entity(bot, event, session, acquire_type='event') + url = await OmEI(entity=entity).get_entity_profile_image_url() + + image_name = OmegaRequests.hash_url_file_name('signin-head-image', url=url) + image_file = wordcloud_plugin_resource_config.profile_image_tmp_dir(image_name) + return await OmegaRequests().download(url=url, file=image_file) + + +async def add_user_dict(content: str) -> None: + """新增用户词典内容""" + async with wordcloud_plugin_resource_config.user_dict_file.async_open('a+', encoding='utf-8') as af: + await af.seek(0, SEEK_SET) + exists_user_dicts = set(x.strip() for x in await af.readlines()) + if content not in exists_user_dicts: + await af.seek(0, SEEK_END) + await af.write(f'{content.strip()}\n') + + + +__all__ = [ + 'query_entity_message_history', + 'query_profile_image', + 'add_user_dict', +] diff --git a/src/plugins/wordcloud/helpers.py b/src/plugins/wordcloud/helpers.py new file mode 100644 index 00000000..8058e2e3 --- /dev/null +++ b/src/plugins/wordcloud/helpers.py @@ -0,0 +1,233 @@ +""" +@Author : Ailitonia +@Date : 2024/11/17 18:25 +@FileName : helpers +@Project : omega-miya +@Description : 词云图片绘制模块 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +import re +from collections.abc import Sequence +from datetime import datetime +from io import BytesIO +from typing import TYPE_CHECKING, Optional + +import jieba +import jieba.analyse +import numpy as np +from PIL import Image, ImageDraw, ImageFilter, ImageFont, ImageOps +from emoji import replace_emoji +from nonebot.log import logger +from nonebot.utils import run_sync +from wordcloud import WordCloud + +from src.service.artwork_collection import get_artwork_collection, get_artwork_collection_type +from .config import wordcloud_plugin_config, wordcloud_plugin_resource_config + +if TYPE_CHECKING: + from numpy.typing import NDArray + + from src.database.internal.history import History + from src.resource import TemporaryResource + + +def prepare_message(messages: Sequence[str]) -> str: + """预处理消息文本""" + # 过滤命令消息 + command_start = tuple(i for i in wordcloud_plugin_config.command_start if i) + message = ' '.join(m for m in messages if not m.startswith(command_start)) + + # 过滤网址, ref: https://stackoverflow.com/a/17773849 + pattern = re.compile( + r'(https?://(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.\S{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]' + r'+[a-zA-Z0-9]\.\S{2,}|https?://(?:www\.|(?!www))[a-zA-Z0-9]+\.\S{2,}|www\.[a-zA-Z0-9]+\.\S{2,})' + ) + message = pattern.sub('', message) + + # 去除零宽空白符 + message = re.sub(r'\u200b', '', message) + # 去除 emoji + message = replace_emoji(message) + + return message + + +def _analyse_tf_idf(message_text: str) -> dict[str, float]: + """基于 TF-IDF 算法的关键词抽取方法统计词频""" + return {str(word): freq for word, freq in jieba.analyse.extract_tags(message_text, topK=0, withWeight=True)} + + +def _analyse_textrank(message_text: str) -> dict[str, float]: + """基于 TextRank 算法的关键词抽取方法统计词频""" + return {str(word): freq for word, freq in jieba.analyse.textrank(message_text, topK=0, withWeight=True)} + + +def analyse_message(message_text: str) -> dict[str, float]: + """使用 jieba 分词, 并进行关键词抽取和词频统计""" + # 设置停用词表和加载用户词典 + jieba.analyse.set_stop_words(wordcloud_plugin_resource_config.default_stop_words_file.resolve_path) + if wordcloud_plugin_resource_config.user_dict_file.is_file: + jieba.load_userdict(wordcloud_plugin_resource_config.user_dict_file.resolve_path) + + # 分词和统计词频 + match wordcloud_plugin_config.wordcloud_plugin_message_analyse_mode: + case 'TextRank': + return _analyse_textrank(message_text) + case 'TF-IDF' | _: + return _analyse_tf_idf(message_text) + + +async def _get_random_background_artwork() -> 'TemporaryResource': + """从数据库获取作品作为背景图""" + random_artworks = await get_artwork_collection_type().query_any_origin_by_condition( + keywords=None, origin=wordcloud_plugin_config.wordcloud_plugin_artwork_background_origin, + num=3, allow_classification_range=(2, 3), allow_rating_range=(0, 0), ratio=1 + ) + + for artwork in random_artworks: + try: + return await get_artwork_collection(artwork=artwork).artwork_proxy.get_page_file() + except Exception as e: + logger.warning(f'getting artwork(origin={artwork.origin}, aid={artwork.aid}) page file failed, {e}') + continue + + raise RuntimeError('all attempts to fetch artwork resources have failed') + + +def _draw_wordcloud_mask(width: int, height: int) -> 'NDArray': + """生成词云蒙版""" + mask_size = (width, height) + background: Image.Image = Image.new(mode='RGBA', size=mask_size, color=(255, 255, 255, 0)) + mask_draw = ImageDraw.Draw(background) + mask_draw.chord(xy=((0, 0), mask_size), start=0, end=90, fill=(0, 0, 0, 255)) + mask_draw.polygon( + xy=( + (0, 0), + (mask_size[0], 0), + (mask_size[0], mask_size[1] // 2), + (mask_size[0] // 2, mask_size[1]), + (0, mask_size[1]), + ), + fill=(0, 0, 0, 255) + ) + with BytesIO() as buffer: + background.save(buffer, format='PNG') + mask_np = np.array(Image.open(buffer)) + return mask_np + + +def _generate_message_history_wordcloud(messages: Sequence['History'], **wordcloud_options) -> 'Image.Image': + """根据查询到的消息历史记录绘制词云""" + # 统计历史消息词频 + prepared_message = prepare_message(messages=[m.message_text for m in messages]) + word_frequency = analyse_message(message_text=prepared_message) + + # 生成词云 + wordcloud = WordCloud(**wordcloud_options) + wordcloud_image: Image.Image = wordcloud.generate_from_frequencies(word_frequency).to_image() + + return wordcloud_image + + +@run_sync +def _draw_message_history_wordcloud( + messages: Sequence['History'], + background_file: Optional['TemporaryResource'] = None, + profile_image_file: Optional['TemporaryResource'] = None, + desc_text: str | None = None, +) -> bytes: + """根据查询到的消息历史记录绘制词云""" + if background_file is not None: + background = Image.open(background_file.resolve_path).convert('RGBA') + background = Image.blend(background, Image.new('RGBA', background.size, (255, 255, 255, 255)), 0.75) + background = background.filter(ImageFilter.GaussianBlur(radius=2)) + width, height = background.size + else: + background = Image.new('RGBA', wordcloud_plugin_config.default_image_size, (255, 255, 255, 255)) + width, height = wordcloud_plugin_config.default_image_size + + # 放置背景图片 + mask = _draw_wordcloud_mask(width=width, height=height) + image_main = Image.new(mode='RGBA', size=(width, height), color=(255, 255, 255, 255)) + image_main.paste(background, mask=Image.fromarray(mask)) + + # 生成词云图片 + wordcloud_options = wordcloud_plugin_config.wordcloud_default_options + wordcloud_options.update({ + 'font_path': wordcloud_plugin_resource_config.default_font_file.resolve_path, + 'mask': mask, + 'width': width, + 'height': height, + }) + wordcloud_image = _generate_message_history_wordcloud(messages=messages, **wordcloud_options) + + # 放置词云图片 + image_main.paste( + im=wordcloud_image, + mask=ImageOps.colorize( + image=wordcloud_image.convert('L'), + black=(255, 255, 255), + white=(0, 0, 0), + blackpoint=225 + ).convert('L') + ) + + # 处理和放置头像 + if profile_image_file is not None: + profile_image = Image.open(profile_image_file.resolve_path).convert('RGBA') + profile_image = profile_image.resize(size=(height // 10, height // 10)) + profile_mask = Image.new(mode='L', size=profile_image.size, color=0) + ImageDraw.Draw(profile_mask).ellipse(xy=((0, 0), profile_image.size), fill=255) + image_main.paste( + im=profile_image, + box=(width - int(height / 10 * 1.2), height // 10 * 8), + mask=profile_mask, + ) + + # 放置文本内容 + if desc_text is not None: + font = ImageFont.truetype(wordcloud_plugin_resource_config.default_font_file.resolve_path, size=height // 54) + ImageDraw.Draw(image_main).multiline_text( + xy=(width - int(height / 10 * 0.2), int(height / 10 * 9.2)), + text=desc_text, + font=font, + anchor='ra', + align='right', + fill=(96, 96, 96) + ) + + with BytesIO() as bf: + image_main.save(bf, 'PNG') + content = bf.getvalue() + return content + + +async def draw_message_history_wordcloud( + messages: Sequence['History'], + profile_image_file: Optional['TemporaryResource'] = None, + desc_text: str | None = None, +) -> 'TemporaryResource': + """根据查询到的消息历史记录绘制词云""" + background = None + if wordcloud_plugin_config.wordcloud_plugin_enable_collected_artwork_background: + background = await _get_random_background_artwork() + + wordcloud_image_content = await _draw_message_history_wordcloud( + messages=messages, + background_file=background, + profile_image_file=profile_image_file, + desc_text=desc_text, + ) + output_file_name = f'wordcloud_{hash(wordcloud_image_content)}_{datetime.now().strftime("%Y-%m-%d-%H-%M-%S")}.png' + output_file = wordcloud_plugin_resource_config.default_output_dir(output_file_name) + + async with output_file.async_open('wb') as af: + await af.write(wordcloud_image_content) + return output_file + + +__all__ = [ + 'draw_message_history_wordcloud', +] diff --git a/src/resource.py b/src/resource.py index 3d605d77..bc44429c 100644 --- a/src/resource.py +++ b/src/resource.py @@ -2,31 +2,32 @@ @Author : Ailitonia @Date : 2022/04/05 3:27 @FileName : resource.py -@Project : nonebot2_miya +@Project : nonebot2_miya @Description : 本地资源文件模块 @GitHub : https://github.com/Ailitonia -@Software : PyCharm +@Software : PyCharm """ import abc import os import pathlib import sys -from contextlib import contextmanager, asynccontextmanager +from collections.abc import Generator +from contextlib import asynccontextmanager, contextmanager from copy import deepcopy +from datetime import datetime from functools import wraps from typing import ( + IO, TYPE_CHECKING, Any, AsyncContextManager, ContextManager, - Generator, - NoReturn, - IO, Literal, - Optional, + NoReturn, Self, - overload + final, + overload, ) import aiofiles @@ -34,25 +35,50 @@ from src.exception import LocalSourceException if TYPE_CHECKING: + from io import FileIO, TextIOWrapper + from aiofiles.threadpool.binary import AsyncFileIO from aiofiles.threadpool.text import AsyncTextIOWrapper - from io import FileIO, TextIOWrapper +@final class ResourceNotFolderError(LocalSourceException): """LocalResource 实例不是文件夹""" + @property + def message(self) -> str: + return f'{self.path.as_posix()!r} is not a directory, or directory {self.path.as_posix()!r} is not exists' + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(path={self.path.as_posix()!r}, message={self.message})' + +@final class ResourceNotFileError(LocalSourceException): """LocalResource 实例不是文件""" + @property + def message(self) -> str: + return f'{self.path.as_posix()!r} is not a file, or file {self.path.as_posix()!r} is not exists' + + def __repr__(self) -> str: + return f'{self.__class__.__name__}(path={self.path.as_posix()!r}, message={self.message})' + -_static_resource_folder = pathlib.Path(os.path.abspath(sys.path[0])).joinpath('static') +_LOG_FOLDER = pathlib.Path(os.path.abspath(sys.path[0])).joinpath('log') +"""日志文件路径""" +_STATIC_RESOURCE_FOLDER = pathlib.Path(os.path.abspath(sys.path[0])).joinpath('static') """静态资源文件路径""" -_temporary_resource_folder = pathlib.Path(os.path.abspath(sys.path[0])).joinpath('.tmp') +_TEMPORARY_RESOURCE_FOLDER = pathlib.Path(os.path.abspath(sys.path[0])).joinpath('.tmp') """临时文件文件路径""" -if not _temporary_resource_folder.exists(): # 初始化临时文件路径所在文件夹 - _temporary_resource_folder.mkdir() + +# 初始化日志文件路径文件夹 +if not _LOG_FOLDER.exists(): + _LOG_FOLDER.mkdir() + +# 初始化临时文件路径文件夹 +if not _TEMPORARY_RESOURCE_FOLDER.exists(): + _TEMPORARY_RESOURCE_FOLDER.mkdir() class BaseResource(abc.ABC): @@ -62,10 +88,10 @@ class BaseResource(abc.ABC): path: pathlib.Path @abc.abstractmethod - def __init__(self, *args: str | pathlib.PurePath): + def __init__(self, *args: str): raise NotImplementedError - def __call__(self, *args: str | pathlib.PurePath) -> Self: + def __call__(self, *args: str) -> Self: new_obj = deepcopy(self) new_obj.path = self.path.joinpath(*args) return new_obj @@ -91,15 +117,15 @@ def is_dir(self) -> bool: """路径目标是否为文件夹且存在""" return self.is_exist and self.path.is_dir() - def raise_not_file(self) -> Optional[NoReturn]: + def raise_not_file(self) -> NoReturn | None: """路径目标不是文件或不存在时抛出 ResourceNotFileError 异常""" if not self.is_file: - raise ResourceNotFileError(f'"{self}" is not a file, or file "{self}" is not exists') + raise ResourceNotFileError(self.path) - def raise_not_dir(self) -> Optional[NoReturn]: + def raise_not_dir(self) -> NoReturn | None: """路径目标不是文件夹或不存在时抛出 ResourceNotFolderError 异常""" if not self.is_dir: - raise ResourceNotFolderError(f'"{self}" is not a directory, or directory "{self}" is not exists') + raise ResourceNotFolderError(self.path) @staticmethod def check_directory(func): @@ -109,7 +135,7 @@ def _wrapper(self: Self, *args, **kwargs): if self.path.exists() and self.path.is_dir(): return func(self, *args, **kwargs) else: - raise ResourceNotFolderError(f'"{self}" is not a directory, or directory "{self}" is not exists') + raise ResourceNotFolderError(self.path) return _wrapper @staticmethod @@ -124,12 +150,12 @@ def _wrapper(self: Self, *args, **kwargs): pathlib.Path.mkdir(self.path.parent, parents=True) return func(self, *args, **kwargs) else: - raise ResourceNotFileError(f'"{self}" is not a file, or file "{self}" is not exists') + raise ResourceNotFileError(self.path) return _wrapper @property def resolve_path(self) -> str: - return str(self.path.resolve()) + return self.path.resolve().as_posix() @property @check_file @@ -140,23 +166,23 @@ def file_uri(self) -> str: def open( self, mode: Literal['r', 'w', 'x', 'a', 'r+', 'w+', 'x+', 'a+'], - encoding: Optional[str] = None, + encoding: str | None = None, **kwargs - ) -> ContextManager["TextIOWrapper"]: + ) -> ContextManager['TextIOWrapper']: ... @overload def open( self, mode: Literal['rb', 'wb', 'xb', 'ab', 'rb+', 'wb+', 'xb+', 'ab+'], - encoding: Optional[str] = None, + encoding: str | None = None, **kwargs - ) -> ContextManager["FileIO"]: + ) -> ContextManager['FileIO']: ... @contextmanager @check_file - def open(self, mode, encoding: Optional[str] = None, **kwargs) -> Generator[IO, Any, None]: + def open(self, mode, encoding: str | None = None, **kwargs) -> Generator[IO, Any, None]: """返回文件 handle""" with self.path.open(mode=mode, encoding=encoding, **kwargs) as _fh: yield _fh @@ -165,23 +191,23 @@ def open(self, mode, encoding: Optional[str] = None, **kwargs) -> Generator[IO, def async_open( self, mode: Literal['r', 'w', 'x', 'a', 'r+', 'w+', 'x+', 'a+'], - encoding: Optional[str] = None, + encoding: str | None = None, **kwargs - ) -> AsyncContextManager["AsyncTextIOWrapper"]: + ) -> AsyncContextManager['AsyncTextIOWrapper']: ... @overload def async_open( self, mode: Literal['rb', 'wb', 'xb', 'ab', 'rb+', 'wb+', 'xb+', 'ab+'], - encoding: Optional[str] = None, + encoding: str | None = None, **kwargs - ) -> AsyncContextManager["AsyncFileIO"]: + ) -> AsyncContextManager['AsyncFileIO']: ... @asynccontextmanager @check_file - async def async_open(self, mode, encoding: Optional[str] = None, **kwargs): + async def async_open(self, mode, encoding: str | None = None, **kwargs): """返回文件 async handle""" async with aiofiles.open(file=self.path, mode=mode, encoding=encoding, **kwargs) as _afh: yield _afh @@ -196,12 +222,55 @@ def list_all_files(self) -> list[Self]: file_list.append(self.__class__(dir_path, file_name)) return file_list + @check_directory + def list_current_files(self) -> list[Self]: + """遍历文件夹内所有文件并返回文件列表(不包含子目录)""" + file_list = [] + for file_name in os.listdir(self.path): + file = self(file_name) + if file.is_file: + file_list.append(file) + return file_list + + +class AnyResource(BaseResource): + """任意位置资源文件""" + + def __init__(self, path: str | pathlib.PurePath, /, *args: str): + self.path: pathlib.Path = pathlib.Path(path) + if args: + self.path = self.path.joinpath(*args) + + +class LogFileResource(BaseResource): + """日志文件""" + + def __init__(self): + self.path: pathlib.Path = deepcopy(_LOG_FOLDER) + self.timestamp_str = datetime.now().strftime('%Y%m%d-%H%M%S') + + @property + def debug(self) -> pathlib.Path: + return self(f'{self.timestamp_str}-DEBUG.log').path + + @property + def info(self) -> pathlib.Path: + return self(f'{self.timestamp_str}-INFO.log').path + + @property + def warring(self) -> pathlib.Path: + return self(f'{self.timestamp_str}-WARRING.log').path + + @property + def error(self) -> pathlib.Path: + return self(f'{self.timestamp_str}-ERROR.log').path + class StaticResource(BaseResource): """静态资源文件""" - def __init__(self, *args: str | pathlib.PurePath): - self.path: pathlib.Path = deepcopy(_static_resource_folder) + def __init__(self, *args: str): + self.path: pathlib.Path = deepcopy(_STATIC_RESOURCE_FOLDER) if args: self.path = self.path.joinpath(*args) @@ -209,14 +278,16 @@ def __init__(self, *args: str | pathlib.PurePath): class TemporaryResource(BaseResource): """临时资源文件""" - def __init__(self, *args: str | pathlib.PurePath): - self.path: pathlib.Path = deepcopy(_temporary_resource_folder) + def __init__(self, *args: str): + self.path: pathlib.Path = deepcopy(_TEMPORARY_RESOURCE_FOLDER) if args: self.path = self.path.joinpath(*args) __all__ = [ + 'AnyResource', 'BaseResource', + 'LogFileResource', 'StaticResource', 'TemporaryResource', 'ResourceNotFolderError', diff --git a/src/service/__init__.py b/src/service/__init__.py index 0ce07206..23a243f3 100644 --- a/src/service/__init__.py +++ b/src/service/__init__.py @@ -8,19 +8,26 @@ @Software : PyCharm """ -from .apscheduler import scheduler, reschedule_job -from .omega_base import OmegaEntity, OmegaEntityInterface, OmegaMatcherInterface, OmegaMessage, OmegaMessageSegment +from .apscheduler import reschedule_job, scheduler +from .omega_base import ( + OmegaEntity, + OmegaEntityInterface, + OmegaMatcherInterface, + OmegaMessage, + OmegaMessageSegment, + OmegaMessageTransfer, +) +from .omega_global_cache import OmegaGlobalCache from .omega_processor import enable_processor_state -from .omega_requests import OmegaRequests - __all__ = [ 'OmegaEntity', 'OmegaEntityInterface', + 'OmegaGlobalCache', 'OmegaMatcherInterface', 'OmegaMessage', 'OmegaMessageSegment', - 'OmegaRequests', + 'OmegaMessageTransfer', 'enable_processor_state', 'reschedule_job', 'scheduler', diff --git a/src/service/artwork_collection/__init__.py b/src/service/artwork_collection/__init__.py index 44caa9f2..0a4d42cc 100644 --- a/src/service/artwork_collection/__init__.py +++ b/src/service/artwork_collection/__init__.py @@ -8,23 +8,24 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Literal, Optional, overload +from typing import TYPE_CHECKING, Literal, overload from src.service.artwork_proxy import ALLOW_ARTWORK_ORIGIN from .sites import ( + BehoimiArtworkCollection, DanbooruArtworkCollection, GelbooruArtworkCollection, - BehoimiArtworkCollection, KonachanArtworkCollection, KonachanSafeArtworkCollection, - YandereArtworkCollection, LocalCollectedArtworkCollection, NoneArtworkCollection, PixivArtworkCollection, + YandereArtworkCollection, ) if TYPE_CHECKING: from src.database.internal.artwork_collection import ArtworkCollection as DBArtworkCollection + from .typing import ArtworkCollectionType, CollectedArtwork @@ -68,7 +69,7 @@ def get_artwork_collection_type(origin: Literal['none', None] = None) -> type[No ... -def get_artwork_collection_type(origin: Optional[ALLOW_ARTWORK_ORIGIN] = None) -> "ArtworkCollectionType": +def get_artwork_collection_type(origin: ALLOW_ARTWORK_ORIGIN | None = None) -> 'ArtworkCollectionType': """根据 origin 名称获取 ArtworkCollection 类""" match origin: case 'pixiv': @@ -89,7 +90,7 @@ def get_artwork_collection_type(origin: Optional[ALLOW_ARTWORK_ORIGIN] = None) - return NoneArtworkCollection -def get_artwork_collection(artwork: "DBArtworkCollection") -> "CollectedArtwork": +def get_artwork_collection(artwork: 'DBArtworkCollection') -> 'CollectedArtwork': """根据数据库查询 ArtworkCollection 结果获取对应的 ArtworkCollection 实例""" artwork_collection_type = get_artwork_collection_type(origin=artwork.origin) # type: ignore return artwork_collection_type(artwork_id=artwork.aid) diff --git a/src/service/artwork_collection/internal.py b/src/service/artwork_collection/internal.py index 4144cddc..021a1080 100644 --- a/src/service/artwork_collection/internal.py +++ b/src/service/artwork_collection/internal.py @@ -9,7 +9,8 @@ """ import abc -from typing import TYPE_CHECKING, Literal, Optional, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Literal from sqlalchemy.exc import NoResultFound @@ -18,10 +19,10 @@ if TYPE_CHECKING: from src.database.internal.artwork_collection import ( - ArtworkCollection as DBArtworkCollection, ArtworkClassificationStatistic as DBArtworkClassificationStatistic, - ArtworkRatingStatistic as DBArtworkRatingStatistic, ) + from src.database.internal.artwork_collection import ArtworkCollection as DBArtworkCollection + from src.database.internal.artwork_collection import ArtworkRatingStatistic as DBArtworkRatingStatistic from src.service.artwork_proxy.internal import BaseArtworkProxy from src.service.artwork_proxy.typing import ArtworkProxyType @@ -40,7 +41,7 @@ def aid(self) -> str: return self.__ap.s_aid @property - def artwork_proxy(self) -> "BaseArtworkProxy": + def artwork_proxy(self) -> 'BaseArtworkProxy': """对外暴露该作品对应图库的统一接口, 便于插件调用""" return self.__ap @@ -51,16 +52,16 @@ def origin_name(self) -> str: @staticmethod async def query_any_origin_by_condition( - keywords: Optional[str | Sequence[str]], - origin: Optional[str | Sequence[str]] = None, + keywords: str | Sequence[str] | None, + origin: str | Sequence[str] | None = None, num: int = 3, *, - allow_classification_range: Optional[tuple[int, int]] = (2, 3), - allow_rating_range: Optional[tuple[int, int]] = (0, 0), + allow_classification_range: tuple[int, int] | None = (2, 3), + allow_rating_range: tuple[int, int] | None = (0, 0), acc_mode: bool = False, - ratio: Optional[int] = None, - order_mode: Literal['random', 'aid', 'aid_desc', 'create_time', 'create_time_desc'] = 'random' - ) -> list["DBArtworkCollection"]: + ratio: int | None = None, + order_mode: Literal['random', 'latest', 'aid', 'aid_desc'] = 'random', + ) -> list['DBArtworkCollection']: """从所有或任意指定来源根据要求查询作品, default classification range: 2-3, default rating range: 0-0""" if isinstance(keywords, str): keywords = [keywords] @@ -82,7 +83,7 @@ async def query_any_origin_by_condition( @classmethod @abc.abstractmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': """内部方法, 用于获取对应图站的统一接口类""" raise NotImplementedError @@ -92,7 +93,7 @@ def _get_origin_name(cls) -> str: return cls._get_base_artwork_proxy_type().get_base_origin_name() @classmethod - def _init_self_artwork_proxy(cls, artwork_id: str | int) -> "BaseArtworkProxy": + def _init_self_artwork_proxy(cls, artwork_id: str | int) -> 'BaseArtworkProxy': """内部方法, 实列化时初始化作品统一接口""" artwork_proxy_class = cls._get_base_artwork_proxy_type() return artwork_proxy_class(artwork_id=artwork_id) @@ -100,15 +101,15 @@ def _init_self_artwork_proxy(cls, artwork_id: str | int) -> "BaseArtworkProxy": @classmethod async def query_by_condition( cls, - keywords: Optional[str | Sequence[str]], + keywords: str | Sequence[str] | None, num: int = 3, *, - allow_classification_range: Optional[tuple[int, int]] = (2, 3), - allow_rating_range: Optional[tuple[int, int]] = (0, 0), + allow_classification_range: tuple[int, int] | None = (2, 3), + allow_rating_range: tuple[int, int] | None = (0, 0), acc_mode: bool = False, - ratio: Optional[int] = None, - order_mode: Literal['random', 'aid', 'aid_desc', 'create_time', 'create_time_desc'] = 'random' - ) -> list["DBArtworkCollection"]: + ratio: int | None = None, + order_mode: Literal['random', 'latest', 'aid', 'aid_desc'] = 'random', + ) -> list['DBArtworkCollection']: """根据要求查询作品, default classification range: 2-3, default rating range: 0-0""" return await cls.query_any_origin_by_condition( origin=cls._get_origin_name(), keywords=keywords, num=num, @@ -121,10 +122,10 @@ async def random( cls, num: int = 3, *, - allow_classification_range: Optional[tuple[int, int]] = (2, 3), - allow_rating_range: Optional[tuple[int, int]] = (0, 0), - ratio: Optional[int] = None - ) -> list["DBArtworkCollection"]: + allow_classification_range: tuple[int, int] | None = (2, 3), + allow_rating_range: tuple[int, int] | None = (0, 0), + ratio: int | None = None + ) -> list['DBArtworkCollection']: """获取随机作品, default classification range: 2-3, default rating range: 0-0""" return await cls.query_by_condition( keywords=None, num=num, ratio=ratio, @@ -135,8 +136,8 @@ async def random( async def query_classification_statistic( cls, *, - keywords: Optional[str | list[str]] = None, - ) -> "DBArtworkClassificationStatistic": + keywords: str | list[str] | None = None, + ) -> 'DBArtworkClassificationStatistic': """按分类统计收录作品数""" if isinstance(keywords, str): keywords = [keywords] @@ -151,8 +152,8 @@ async def query_classification_statistic( async def query_rating_statistic( cls, *, - keywords: Optional[str | list[str]] = None, - ) -> "DBArtworkRatingStatistic": + keywords: str | list[str] | None = None, + ) -> 'DBArtworkRatingStatistic': """按分级统计收录作品数""" if isinstance(keywords, str): keywords = [keywords] @@ -166,9 +167,9 @@ async def query_rating_statistic( @classmethod async def query_user_all( cls, - uid: Optional[str] = None, - uname: Optional[str] = None, - ) -> list["DBArtworkCollection"]: + uid: str | None = None, + uname: str | None = None, + ) -> list['DBArtworkCollection']: """通过 uid 或用户名精准查找用户所有作品""" async with begin_db_session() as session: result = await ArtworkCollectionDAL(session=session).query_user_all( @@ -179,8 +180,8 @@ async def query_user_all( @classmethod async def query_user_all_aids( cls, - uid: Optional[str] = None, - uname: Optional[str] = None, + uid: str | None = None, + uname: str | None = None, ) -> list[str]: """通过 uid 或用户名精准查找用户所有作品的 artwork_id""" async with begin_db_session() as session: @@ -190,24 +191,48 @@ async def query_user_all_aids( return result @classmethod - async def query_exists_aids(cls, aids: Sequence[str]) -> list[str]: - """根据提供的 aids 列表查询数据库中已存在的列表中的 aid""" + async def query_exists_aids( + cls, + aids: Sequence[str], + *, + filter_classification: int | None = None, + filter_rating: int | None = None, + ) -> list[str]: + """根据提供的 aids 列表查询数据库中已存在的列表中的 aid + + :param aids: 待匹配的作品 artwork_id 清单 + :param filter_classification: 筛选指定的作品分类, 只有该分类的作品都会被视为存在 + :param filter_rating: 筛选指定的作品分级, 只有该分级的作品都会被视为存在 + """ async with begin_db_session() as session: result = await ArtworkCollectionDAL(session=session).query_exists_aids( - origin=cls._get_origin_name(), aids=aids + origin=cls._get_origin_name(), aids=aids, + filter_classification=filter_classification, filter_rating=filter_rating ) return result @classmethod - async def query_not_exists_aids(cls, aids: Sequence[str]) -> list[str]: - """根据提供的 aids 列表查询数据库中不存在的列表中的 aid""" + async def query_not_exists_aids( + cls, + aids: Sequence[str], + *, + exclude_classification: int | None = None, + exclude_rating: int | None = None, + ) -> list[str]: + """根据提供的 aids 列表查询数据库中不存在的列表中的 aid + + :param aids: 待匹配的作品 artwork_id 清单 + :param exclude_classification: 排除指定的作品分类, 所有非该分类的作品都会被视为不存在 + :param exclude_rating: 排除指定的作品分级, 所有非该分级的作品都会被视为不存在 + """ async with begin_db_session() as session: result = await ArtworkCollectionDAL(session=session).query_not_exists_aids( - origin=cls._get_origin_name(), aids=aids + origin=cls._get_origin_name(), aids=aids, + exclude_classification=exclude_classification, exclude_rating=exclude_rating ) return result - async def query_artwork(self) -> "DBArtworkCollection": + async def query_artwork(self) -> 'DBArtworkCollection': """查询数据库获取作品信息""" async with begin_db_session() as session: result = await ArtworkCollectionDAL(session=session).query_unique( @@ -219,8 +244,8 @@ async def add_and_upgrade_artwork_into_database( self, *, use_cache: bool = True, - classification: Optional[int] = None, - rating: Optional[int] = None, + classification: int | None = None, + rating: int | None = None, force_update_mark: bool = False, ) -> None: """查询图站获取作品元数据, 向数据库新增该作品信息, 若已存在则更新 @@ -245,7 +270,7 @@ async def add_and_upgrade_artwork_into_database( rating = max(artwork.rating, rating) await artwork_dal.update( - id_=artwork.id, + origin=self.origin_name, aid=self.__ap.s_aid, title=artwork_data.title, uid=artwork_data.uid, uname=artwork_data.uname, classification=classification, rating=rating, width=artwork_data.width, height=artwork_data.height, @@ -268,8 +293,8 @@ async def add_artwork_into_database_ignore_exists( self, *, use_cache: bool = True, - classification: Optional[int] = None, - rating: Optional[int] = None, + classification: int | None = None, + rating: int | None = None, ) -> None: """查询图站获取作品元数据, 向数据库新增该作品信息, 若已存在忽略 @@ -301,9 +326,7 @@ async def add_artwork_into_database_ignore_exists( async def delete_artwork_from_database(self) -> None: """从数据库删除该作品信息""" async with begin_db_session() as session: - artwork_dal = ArtworkCollectionDAL(session=session) - artwork = await artwork_dal.query_unique(origin=self.origin_name, aid=self.__ap.s_aid) - await artwork_dal.delete(id_=artwork.id) + await ArtworkCollectionDAL(session=session).delete(origin=self.origin_name, aid=self.__ap.s_aid) __all__ = [ diff --git a/src/service/artwork_collection/sites/__init__.py b/src/service/artwork_collection/sites/__init__.py index 40b94d24..2f00ce94 100644 --- a/src/service/artwork_collection/sites/__init__.py +++ b/src/service/artwork_collection/sites/__init__.py @@ -9,9 +9,9 @@ """ from .booru import ( + BehoimiArtworkCollection, DanbooruArtworkCollection, GelbooruArtworkCollection, - BehoimiArtworkCollection, KonachanArtworkCollection, KonachanSafeArtworkCollection, YandereArtworkCollection, @@ -20,7 +20,6 @@ from .none import NoneArtworkCollection from .pixiv import PixivArtworkCollection - __all__ = [ 'DanbooruArtworkCollection', 'GelbooruArtworkCollection', diff --git a/src/service/artwork_collection/sites/booru.py b/src/service/artwork_collection/sites/booru.py index 1ad5509f..a413e562 100644 --- a/src/service/artwork_collection/sites/booru.py +++ b/src/service/artwork_collection/sites/booru.py @@ -11,9 +11,9 @@ from typing import TYPE_CHECKING from src.service.artwork_proxy import ( + BehoimiArtworkProxy, DanbooruArtworkProxy, GelbooruArtworkProxy, - BehoimiArtworkProxy, KonachanArtworkProxy, KonachanSafeArtworkProxy, YandereArtworkProxy, @@ -28,7 +28,7 @@ class DanbooruArtworkCollection(BaseArtworkCollection): """Danbooru 收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return DanbooruArtworkProxy @@ -36,7 +36,7 @@ class GelbooruArtworkCollection(BaseArtworkCollection): """Gelbooru 收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return GelbooruArtworkProxy @@ -44,7 +44,7 @@ class BehoimiArtworkCollection(BaseArtworkCollection): """Behoimi 收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return BehoimiArtworkProxy @@ -52,7 +52,7 @@ class KonachanArtworkCollection(BaseArtworkCollection): """Konachan 收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return KonachanArtworkProxy @@ -60,7 +60,7 @@ class KonachanSafeArtworkCollection(BaseArtworkCollection): """KonachanSafe 收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return KonachanSafeArtworkProxy @@ -68,7 +68,7 @@ class YandereArtworkCollection(BaseArtworkCollection): """Yandere 收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return YandereArtworkProxy diff --git a/src/service/artwork_collection/sites/booru.pyi b/src/service/artwork_collection/sites/booru.pyi index 24daf33c..5dcd1beb 100644 --- a/src/service/artwork_collection/sites/booru.pyi +++ b/src/service/artwork_collection/sites/booru.pyi @@ -1,13 +1,13 @@ from src.service.artwork_proxy import ( + BehoimiArtworkProxy, DanbooruArtworkProxy, GelbooruArtworkProxy, - BehoimiArtworkProxy, KonachanArtworkProxy, KonachanSafeArtworkProxy, YandereArtworkProxy, ) -from ..internal import BaseArtworkCollection +from ..internal import BaseArtworkCollection class DanbooruArtworkCollection(BaseArtworkCollection): @property diff --git a/src/service/artwork_collection/sites/local.py b/src/service/artwork_collection/sites/local.py index 3d57cf96..1c729ad9 100644 --- a/src/service/artwork_collection/sites/local.py +++ b/src/service/artwork_collection/sites/local.py @@ -21,7 +21,7 @@ class LocalCollectedArtworkCollection(BaseArtworkCollection): """本地图片收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return LocalCollectedArtworkProxy diff --git a/src/service/artwork_collection/sites/local.pyi b/src/service/artwork_collection/sites/local.pyi index 7f4875cb..938741e8 100644 --- a/src/service/artwork_collection/sites/local.pyi +++ b/src/service/artwork_collection/sites/local.pyi @@ -1,6 +1,6 @@ from src.service.artwork_proxy import LocalCollectedArtworkProxy -from ..internal import BaseArtworkCollection +from ..internal import BaseArtworkCollection class LocalCollectedArtworkCollection(BaseArtworkCollection): @property diff --git a/src/service/artwork_collection/sites/none.py b/src/service/artwork_collection/sites/none.py index 4454bf88..dd46a305 100644 --- a/src/service/artwork_collection/sites/none.py +++ b/src/service/artwork_collection/sites/none.py @@ -21,7 +21,7 @@ class NoneArtworkCollection(BaseArtworkCollection): """空适配""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return NoneArtworkProxy diff --git a/src/service/artwork_collection/sites/none.pyi b/src/service/artwork_collection/sites/none.pyi index 6eb0e2a7..2d7c788a 100644 --- a/src/service/artwork_collection/sites/none.pyi +++ b/src/service/artwork_collection/sites/none.pyi @@ -1,6 +1,6 @@ from src.service.artwork_proxy import NoneArtworkProxy -from ..internal import BaseArtworkCollection +from ..internal import BaseArtworkCollection class NoneArtworkCollection(BaseArtworkCollection): @property diff --git a/src/service/artwork_collection/sites/pixiv.py b/src/service/artwork_collection/sites/pixiv.py index 0b62dc30..8617c533 100644 --- a/src/service/artwork_collection/sites/pixiv.py +++ b/src/service/artwork_collection/sites/pixiv.py @@ -21,7 +21,7 @@ class PixivArtworkCollection(BaseArtworkCollection): """Pixiv 收藏作品合集""" @classmethod - def _get_base_artwork_proxy_type(cls) -> "ArtworkProxyType": + def _get_base_artwork_proxy_type(cls) -> 'ArtworkProxyType': return PixivArtworkProxy diff --git a/src/service/artwork_collection/sites/pixiv.pyi b/src/service/artwork_collection/sites/pixiv.pyi index 7b56eafc..aa1701e7 100644 --- a/src/service/artwork_collection/sites/pixiv.pyi +++ b/src/service/artwork_collection/sites/pixiv.pyi @@ -1,6 +1,6 @@ from src.service.artwork_proxy import PixivArtworkProxy -from ..internal import BaseArtworkCollection +from ..internal import BaseArtworkCollection class PixivArtworkCollection(BaseArtworkCollection): @property diff --git a/src/service/artwork_proxy/__init__.py b/src/service/artwork_proxy/__init__.py index 94064912..06f401bb 100644 --- a/src/service/artwork_proxy/__init__.py +++ b/src/service/artwork_proxy/__init__.py @@ -11,15 +11,15 @@ from typing import Literal from .sites import ( + BehoimiArtworkProxy, DanbooruArtworkProxy, GelbooruArtworkProxy, - LocalCollectedArtworkProxy, - BehoimiArtworkProxy, KonachanArtworkProxy, KonachanSafeArtworkProxy, - YandereArtworkProxy, + LocalCollectedArtworkProxy, NoneArtworkProxy, PixivArtworkProxy, + YandereArtworkProxy, ) type ALLOW_ARTWORK_ORIGIN = Literal[ diff --git a/src/service/artwork_proxy/add_ons/__init__.py b/src/service/artwork_proxy/add_ons/__init__.py index ecc291cd..3461d2fe 100644 --- a/src/service/artwork_proxy/add_ons/__init__.py +++ b/src/service/artwork_proxy/add_ons/__init__.py @@ -10,7 +10,6 @@ from .image_ops import ImageOpsMixin, ImageOpsPlusPoolMixin - __all__ = [ 'ImageOpsMixin', 'ImageOpsPlusPoolMixin', diff --git a/src/service/artwork_proxy/add_ons/image_ops.py b/src/service/artwork_proxy/add_ons/image_ops.py index 00355d07..3dbe7cba 100644 --- a/src/service/artwork_proxy/add_ons/image_ops.py +++ b/src/service/artwork_proxy/add_ons/image_ops.py @@ -9,18 +9,20 @@ """ import abc -from typing import TYPE_CHECKING, Literal, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Literal from nonebot.utils import run_sync +from src.utils import semaphore_gather from src.utils.image_utils import ImageUtils -from src.utils.image_utils.template import generate_thumbs_preview_image, PreviewImageThumbs, PreviewImageModel -from src.utils.process_utils import semaphore_gather +from src.utils.image_utils.template import PreviewImageModel, PreviewImageThumbs, generate_thumbs_preview_image from .typing import ArtworkProxyAddonsMixin from ..models import ArtworkPool if TYPE_CHECKING: from src.resource import TemporaryResource + from ..models import ArtworkData from ..typing import ArtworkPageParamType @@ -30,7 +32,7 @@ class ImageOpsMixin(ArtworkProxyAddonsMixin, abc.ABC): @staticmethod @run_sync - def _handle_blur(image: "TemporaryResource", origin_mark: str) -> ImageUtils: + def _handle_blur(image: 'TemporaryResource', origin_mark: str) -> ImageUtils: """模糊处理图片""" _image = ImageUtils.init_from_file(file=image) _image.gaussian_blur() @@ -40,7 +42,7 @@ def _handle_blur(image: "TemporaryResource", origin_mark: str) -> ImageUtils: @staticmethod @run_sync - def _handle_mark(image: "TemporaryResource", origin_mark: str) -> ImageUtils: + def _handle_mark(image: 'TemporaryResource', origin_mark: str) -> ImageUtils: """标记水印""" _image = ImageUtils.init_from_file(file=image) _image.mark(text=origin_mark) @@ -49,7 +51,7 @@ def _handle_mark(image: "TemporaryResource", origin_mark: str) -> ImageUtils: @staticmethod @run_sync - def _handle_noise(image: "TemporaryResource", origin_mark: str) -> ImageUtils: + def _handle_noise(image: 'TemporaryResource', origin_mark: str) -> ImageUtils: """噪点处理图片""" _image = ImageUtils.init_from_file(file=image) _image.gaussian_noise(sigma=16) @@ -63,9 +65,9 @@ async def _process_artwork_page( self, page_index: int = 0, *, - page_type: "ArtworkPageParamType" = 'regular', + page_type: 'ArtworkPageParamType' = 'regular', process_mode: Literal['mark', 'blur', 'noise'] = 'mark', - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """处理作品图片""" artwork_data = await self.query() origin_mark = f'{artwork_data.origin.title()} | {artwork_data.aid}' @@ -82,16 +84,16 @@ async def _process_artwork_page( image = await self._handle_mark(image=page_file, origin_mark=origin_mark) output_file_name = f'{page_file.path.stem}_marked.jpg' - output_file = self._get_path_config().processed_path(output_file_name) + output_file = self.path_config.processed_path(output_file_name) return await image.save(file=output_file) async def get_custom_proceed_page_file( self, page_index: int = 0, *, - page_type: "ArtworkPageParamType" = 'regular', + page_type: 'ArtworkPageParamType' = 'regular', process_mode: Literal['mark', 'blur', 'noise'] = 'mark', - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """使用自定义方法处理作品图片""" return await self._process_artwork_page(page_index=page_index, page_type=page_type, process_mode=process_mode) @@ -99,9 +101,9 @@ async def get_proceed_page_file( self, page_index: int = 0, *, - page_type: "ArtworkPageParamType" = 'regular', + page_type: 'ArtworkPageParamType' = 'regular', no_blur_rating: int = 1, - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """根据作品分级处理作品图片 :param page_index: 作品图片页码 @@ -131,7 +133,7 @@ async def _get_any_image_preview_thumb_data(cls, url: str, desc_text: str) -> Pr async def _get_preview_thumb_data( self, *, - page_type: "ArtworkPageParamType" = 'preview', + page_type: 'ArtworkPageParamType' = 'preview', no_blur_rating: int = 1, ) -> PreviewImageThumbs: """获取生成预览图所需要的作品数据""" @@ -175,9 +177,9 @@ async def _get_any_images_preview_data( async def _get_artworks_preview_data( cls, preview_name: str, - artworks: Sequence["ImageOpsMixin"], + artworks: Sequence['ImageOpsMixin'], *, - page_type: "ArtworkPageParamType" = 'preview', + page_type: 'ArtworkPageParamType' = 'preview', no_blur_rating: int = 1, limit: int = 100, ) -> PreviewImageModel: @@ -202,7 +204,7 @@ async def generate_any_images_preview( edge_scale: float = 1 / 32, num_of_line: int = 6, limit: int = 100, - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """生成多个任意图片的预览图 :param preview_name: 预览图标题 @@ -233,15 +235,15 @@ async def generate_any_images_preview( async def generate_artworks_preview( cls, preview_name: str, - artworks: Sequence["ImageOpsMixin"], + artworks: Sequence['ImageOpsMixin'], *, - page_type: "ArtworkPageParamType" = 'preview', + page_type: 'ArtworkPageParamType' = 'preview', no_blur_rating: int = 1, preview_size: tuple[int, int] = (256, 256), # 默认预览图缩略图大小 edge_scale: float = 1 / 32, num_of_line: int = 6, limit: int = 100, - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """生成多个作品的预览图 :param preview_name: 预览图标题 @@ -277,7 +279,7 @@ class ImageOpsPlusPoolMixin(ImageOpsMixin, abc.ABC): """作品图片处理工具插件(附加图集处理功能)""" @classmethod - def _get_pool_meta_file(cls, pool_id: str) -> "TemporaryResource": + def _get_pool_meta_file(cls, pool_id: str) -> 'TemporaryResource': return cls._generate_path_config().meta_path(f'pool_{pool_id}.json') @classmethod @@ -310,7 +312,7 @@ async def query_pool(cls, pool_id: str, *, use_cache: bool = True) -> ArtworkPoo return await cls._fast_query_pool(pool_id=pool_id, use_cache=use_cache) @classmethod - async def query_pool_all_artworks(cls, pool_id: str) -> list["ArtworkData"]: + async def query_pool_all_artworks(cls, pool_id: str) -> list['ArtworkData']: """获取图集中所有作品信息""" pool_data = await cls.query_pool(pool_id=pool_id) @@ -320,7 +322,7 @@ async def query_pool_all_artworks(cls, pool_id: str) -> list["ArtworkData"]: return list(all_artwork_data) @classmethod - async def query_pool_all_artwork_pages(cls, pool_id: str) -> list["TemporaryResource"]: + async def query_pool_all_artwork_pages(cls, pool_id: str) -> list['TemporaryResource']: """获取图集中所有作品图片""" pool_data = await cls.query_pool(pool_id=pool_id) @@ -330,7 +332,7 @@ async def query_pool_all_artwork_pages(cls, pool_id: str) -> list["TemporaryReso return [file for artwork_pages in all_artwork_pages for file in artwork_pages] @classmethod - async def generate_pool_preview(cls, pool_id: str) -> "TemporaryResource": + async def generate_pool_preview(cls, pool_id: str) -> 'TemporaryResource': """生成图集的预览图""" pool_data = await cls.query_pool(pool_id=pool_id) diff --git a/src/service/artwork_proxy/config.py b/src/service/artwork_proxy/config.py index d95d7a08..762ea7d6 100644 --- a/src/service/artwork_proxy/config.py +++ b/src/service/artwork_proxy/config.py @@ -11,7 +11,7 @@ from src.resource import StaticResource, TemporaryResource -class ArtworkProxyPathConfig(object): +class ArtworkProxyPathConfig: """作品本地缓存路径配置""" _default_text_font_name: str = 'SourceHanSansSC-Regular.otf' _default_theme_font_name: str = 'fzzxhk.ttf' diff --git a/src/service/artwork_proxy/internal.py b/src/service/artwork_proxy/internal.py index 65e79e1a..5bcf9079 100644 --- a/src/service/artwork_proxy/internal.py +++ b/src/service/artwork_proxy/internal.py @@ -10,17 +10,18 @@ import abc from pathlib import PurePath -from typing import TYPE_CHECKING, Optional, Self -from urllib.parse import urlparse, unquote +from typing import TYPE_CHECKING, Self +from urllib.parse import unquote, urlparse from pydantic import ValidationError -from src.utils.process_utils import semaphore_gather +from src.utils import semaphore_gather from .config import ArtworkProxyPathConfig from .models import ArtworkData if TYPE_CHECKING: from src.resource import TemporaryResource + from .typing import ArtworkPageParamType @@ -32,7 +33,7 @@ def __init__(self, artwork_id: str | int): self.__path_config = self._generate_path_config() # 实例缓存 - self.artwork_data: Optional[ArtworkData] = None + self.artwork_data: ArtworkData | None = None def __repr__(self) -> str: return f'{self.__class__.__name__}(artwork_id={self.s_aid})' @@ -42,7 +43,7 @@ def i_aid(self) -> int: if isinstance(self.__id, int): return self.__id else: - return int(self.__id) # 忽略类型检查,任由异常抛出并由后续流程处理 + return int(self.__id) # 忽略数字类型检查,任由 `ValueError` 异常抛出并由后续流程处理 @property def s_aid(self) -> str: @@ -53,8 +54,8 @@ def meta_file_name(self) -> str: return f'{self.s_aid}.json' @property - def meta_file(self) -> "TemporaryResource": - return self._get_path_config().meta_path(self.meta_file_name) + def meta_file(self) -> 'TemporaryResource': + return self.path_config.meta_path(self.meta_file_name) @property def origin_name(self) -> str: @@ -64,7 +65,7 @@ def origin_name(self) -> str: @property def path_config(self) -> ArtworkProxyPathConfig: """对外暴露该作品对应存储路径配置, 便于插件调用""" - return self._get_path_config() + return self.__path_config @staticmethod def parse_url_file_suffix(url: str) -> str: @@ -82,10 +83,6 @@ def _generate_path_config(cls) -> ArtworkProxyPathConfig: """内部方法, 生成该图库的本地存储路径配置项""" return ArtworkProxyPathConfig(base_path_name=cls.get_base_origin_name()) - def _get_path_config(self) -> ArtworkProxyPathConfig: - """内部方法, 获取该图库的本地存储路径配置项""" - return self.__path_config - @classmethod @abc.abstractmethod async def _get_resource_as_bytes(cls, url: str, *, timeout: int = 30) -> bytes: @@ -106,7 +103,7 @@ async def _random(cls, *, limit: int = 20) -> list[str | int]: @classmethod @abc.abstractmethod - async def _search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[str | int]: + async def _search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[str | int]: """内部方法, 根据关键词搜索作品 ID 列表""" raise NotImplementedError @@ -116,7 +113,7 @@ async def random(cls, *, limit: int = 20) -> list[Self]: return [cls(artwork_id=aid) for aid in await cls._random(limit=limit)] @classmethod - async def search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[Self]: + async def search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[Self]: """根据关键词搜索作品列表""" return [cls(artwork_id=aid) for aid in await cls._search(keyword=keyword, page=page, **kwargs)] @@ -167,7 +164,7 @@ async def get_std_preview_desc(self, *, text_len_limit: int = 12) -> str: async def _query_page( self, page_index: int = 0, - page_type: "ArtworkPageParamType" = 'regular' + page_type: 'ArtworkPageParamType' = 'regular' ) -> bytes: """内部方法, 加载作品图片资源""" artwork_data = await self.query() @@ -185,8 +182,8 @@ async def _query_page( async def _save_page( self, page_index: int = 0, - page_type: "ArtworkPageParamType" = 'regular' - ) -> "TemporaryResource": + page_type: 'ArtworkPageParamType' = 'regular' + ) -> 'TemporaryResource': """内部方法, 保存作品资源到本地""" artwork_data = await self.query() @@ -199,7 +196,7 @@ async def _save_page( page = artwork_data.index_pages[page_index].regular_file page_file_name = f'{self.s_aid}_{page_type}_p{page_index}.{page.file_ext.strip(".")}' - page_file = self._get_path_config().artwork_path(page_file_name) + page_file = self.path_config.artwork_path(page_file_name) # 如果已经存在则直接返回本地资源 if page_file.is_file: @@ -214,7 +211,7 @@ async def _save_page( async def _load_page( self, page_index: int = 0, - page_type: "ArtworkPageParamType" = 'regular' + page_type: 'ArtworkPageParamType' = 'regular' ) -> bytes: """内部方法, 获取作品资源, 优先从本地缓存资源加载""" page_file = await self._save_page(page_index=page_index, page_type=page_type) @@ -226,7 +223,7 @@ async def _load_page( async def get_page_bytes( self, page_index: int = 0, - page_type: "ArtworkPageParamType" = 'regular' + page_type: 'ArtworkPageParamType' = 'regular' ) -> bytes: """获取作品文件内容, 使用本地缓存""" return await self._load_page(page_index=page_index, page_type=page_type) @@ -234,16 +231,16 @@ async def get_page_bytes( async def get_page_file( self, page_index: int = 0, - page_type: "ArtworkPageParamType" = 'regular' - ) -> "TemporaryResource": + page_type: 'ArtworkPageParamType' = 'regular' + ) -> 'TemporaryResource': """获取作品文件资源, 使用本地缓存""" return await self._save_page(page_index=page_index, page_type=page_type) async def get_all_pages_file( self, page_limit: int = 10, - page_type: "ArtworkPageParamType" = 'regular' - ) -> list["TemporaryResource"]: + page_type: 'ArtworkPageParamType' = 'regular' + ) -> list['TemporaryResource']: """获取作品所有文件资源列表, 使用本地缓存 :param page_limit: 返回作品图片最大数量限制, 从第一张图开始计算, 避免漫画作品等单作品图片数量过多出现问题, 0 为无限制 @@ -268,11 +265,11 @@ async def get_all_pages_file( return list(all_pages_file) - async def download_page(self, page_index: int = 0) -> "TemporaryResource": + async def download_page(self, page_index: int = 0) -> 'TemporaryResource': """下载作品原图到本地""" return await self.get_page_file(page_index=page_index, page_type='original') - async def download_all_pages(self) -> list["TemporaryResource"]: + async def download_all_pages(self) -> list['TemporaryResource']: """下载作品全部原图到本地""" return await self.get_all_pages_file(page_limit=0, page_type='original') diff --git a/src/service/artwork_proxy/models.py b/src/service/artwork_proxy/models.py index b031c047..8ef42ff2 100644 --- a/src/service/artwork_proxy/models.py +++ b/src/service/artwork_proxy/models.py @@ -9,9 +9,8 @@ """ from enum import IntEnum, unique -from typing import Optional -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from src.compat import AnyHttpUrlStr as AnyHttpUrl @@ -19,8 +18,9 @@ @unique class ArtworkClassification(IntEnum): """作品分类级别(标记作品元数据/分级/来源等信息是否可靠, 是否是由人工审核过的)""" - Unknown = -1 # 无法确认分类级别, 一般为本地图片或无确切来源的图片 - Unclassified = 0 # 未分类, 一般为无分级图站作品默认分类级别 + Ignored = -2 # 可能是由于低质/敏感话题/广告等因素, 被人工手动审核/标记为忽略该作品, 一般情况下不应当使用分类为此等级的作品 + Unknown = -1 # 无法确认分类级别, 一般为本地图片或无确切来源(各种不标明来源的页面, 推文, 动态, etc.)的图片 + Unclassified = 0 # 未分类, 一般为无分级网站(pixiv, twitter, etc.)作品默认分类级别 AIGenerated = 1 # 确认/疑似为 AI 生成作品 Automatic = 2 # 由图站分类/图站分级/第三方接口分类, 可能由人工进行分类但不完全可信, 一般可作为应用层插件使用的最低可信级别 Confirmed = 3 # 由人工审核/确认为 "人类生成" 的作品, 且分级可信 @@ -46,8 +46,8 @@ class ArtworkPageFile(BaseArtworkProxyModel): """作品图片详情""" url: AnyHttpUrl file_ext: str - width: Optional[int] = None - height: Optional[int] = None + width: int | None = None + height: int | None = None class ArtworkPage(BaseArtworkProxyModel): @@ -69,9 +69,14 @@ class ArtworkData(BaseArtworkProxyModel): width: int height: int tags: list[str] - description: Optional[str] = None + description: str | None = None + like_count: int | None = None # 喜欢/点赞数量 + bookmark_count: int | None = None # 收藏数量 + view_count: int | None = None # 浏览次数 + comment_count: int | None = None # 评论量 source: str # 原始出处地址(指能直接获得该作品的来源), 一般来说为 url pages: list[ArtworkPage] + extra_resource: list[AnyHttpUrl] = Field(default_factory=list) # 其他额外资源链接 @property def cover_page_url(self) -> AnyHttpUrl: @@ -103,7 +108,7 @@ class ArtworkPool(BaseArtworkProxyModel): origin: str pool_id: str name: str - description: Optional[str] = None + description: str | None = None artwork_ids: list[str] @property diff --git a/src/service/artwork_proxy/sites/__init__.py b/src/service/artwork_proxy/sites/__init__.py index fd7ef11b..90cf74d6 100644 --- a/src/service/artwork_proxy/sites/__init__.py +++ b/src/service/artwork_proxy/sites/__init__.py @@ -20,7 +20,6 @@ from .none import NoneArtworkProxy from .pixiv import PixivArtworkProxy - __all__ = [ 'DanbooruArtworkProxy', 'GelbooruArtworkProxy', diff --git a/src/service/artwork_proxy/sites/danbooru.py b/src/service/artwork_proxy/sites/danbooru.py index d3a2710f..bf063564 100644 --- a/src/service/artwork_proxy/sites/danbooru.py +++ b/src/service/artwork_proxy/sites/danbooru.py @@ -40,7 +40,7 @@ async def _get_resource_as_text(cls, url: str, *, timeout: int = 10) -> str: return await cls._get_api().get_resource_as_text(url=url, timeout=timeout) @staticmethod - def _get_variant_page_file(variant: Optional["PostVariantTypes"]) -> ArtworkPageFile: + def _get_variant_page_file(variant: Optional['PostVariantTypes']) -> ArtworkPageFile: if variant is None: model_data = { 'url': 'https://example.com/FileNotFound', @@ -58,11 +58,11 @@ def _get_variant_page_file(variant: Optional["PostVariantTypes"]) -> ArtworkPage return ArtworkPageFile.model_validate(model_data) @classmethod - def _get_preview_file(cls, media_asset: "PostMediaAsset") -> ArtworkPageFile: + def _get_preview_file(cls, media_asset: 'PostMediaAsset') -> ArtworkPageFile: return cls._get_variant_page_file(variant=media_asset.variant_type_180) @classmethod - def _get_regular_file(cls, media_asset: "PostMediaAsset") -> ArtworkPageFile: + def _get_regular_file(cls, media_asset: 'PostMediaAsset') -> ArtworkPageFile: if media_asset.variant_type_sample is not None: return cls._get_variant_page_file(variant=media_asset.variant_type_sample) elif media_asset.variant_type_720 is not None: @@ -71,7 +71,7 @@ def _get_regular_file(cls, media_asset: "PostMediaAsset") -> ArtworkPageFile: return cls._get_variant_page_file(variant=media_asset.variant_type_360) @classmethod - def _get_original_file(cls, media_asset: "PostMediaAsset") -> ArtworkPageFile: + def _get_original_file(cls, media_asset: 'PostMediaAsset') -> ArtworkPageFile: if media_asset.variant_type_full is not None: return cls._get_variant_page_file(variant=media_asset.variant_type_full) else: @@ -91,7 +91,7 @@ async def _random(cls, *, limit: int = 20) -> list[str | int]: return [x.id for x in artworks_data] @classmethod - async def _search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[str | int]: + async def _search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[str | int]: artworks_data = await cls._get_api().posts_index(tags=keyword, page=page, **kwargs) return [x.id for x in artworks_data] @@ -161,6 +161,7 @@ async def _query(self) -> ArtworkData: 'height': artwork_data.image_height, 'tags': tags_general, 'description': description, + 'like_count': artwork_data.score, 'source': artwork_data.source, 'pages': [{ 'preview_file': self._get_preview_file(media_asset=artwork_data.media_asset), @@ -185,8 +186,8 @@ async def get_std_desc(self, *, desc_len_limit: int = 128) -> str: async def get_std_preview_desc(self, *, text_len_limit: int = 12) -> str: artwork_data = await self.query() - artist = f"Artist: {artwork_data.uname}" - artist = f"{artist[:text_len_limit]}..." if len(artist) > text_len_limit else artist + artist = f'Artist: {artwork_data.uname}' + artist = f'{artist[:text_len_limit]}...' if len(artist) > text_len_limit else artist return f'{artwork_data.origin.title()}\nID: {artwork_data.aid}\n{artist}' diff --git a/src/service/artwork_proxy/sites/gelbooru.py b/src/service/artwork_proxy/sites/gelbooru.py index 0483cf08..90f3ffb3 100644 --- a/src/service/artwork_proxy/sites/gelbooru.py +++ b/src/service/artwork_proxy/sites/gelbooru.py @@ -9,7 +9,6 @@ """ import abc -from typing import Optional from src.utils.booru_api import gelbooru_api from src.utils.booru_api.gelbooru import BaseGelbooruAPI, GelbooruAPI @@ -41,7 +40,7 @@ async def _random(cls, *, limit: int = 20) -> list[str | int]: return [x.id for x in artworks_data.post] @classmethod - async def _search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[str | int]: + async def _search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[str | int]: artworks_data = await cls._get_api().posts_index(tags=keyword, page=page, **kwargs) return [x.id for x in artworks_data.post] @@ -102,6 +101,7 @@ async def _query(self) -> ArtworkData: 'height': artwork_data.height, 'tags': tags, 'description': None, + 'like_count': artwork_data.score, 'source': artwork_data.source, 'pages': [{ 'preview_file': { diff --git a/src/service/artwork_proxy/sites/local.py b/src/service/artwork_proxy/sites/local.py index 6d0d513b..57a75ed6 100644 --- a/src/service/artwork_proxy/sites/local.py +++ b/src/service/artwork_proxy/sites/local.py @@ -9,7 +9,7 @@ """ import random -from typing import TYPE_CHECKING, Optional, Self +from typing import TYPE_CHECKING, Self from ..add_ons import ImageOpsMixin from ..internal import BaseArtworkProxy @@ -17,6 +17,7 @@ if TYPE_CHECKING: from src.resource import TemporaryResource + from ..typing import ArtworkPageParamType @@ -41,7 +42,7 @@ async def _random(cls, *, limit: int = 20) -> list[str | int]: return [file.path.name for file in random.sample(path_config.artwork_path.list_all_files(), k=limit)] @classmethod - async def _search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[str | int]: + async def _search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[str | int]: path_config = cls._generate_path_config() return [file.path.name for file in path_config.artwork_path.list_all_files() if keyword in file.path.name] @@ -53,7 +54,7 @@ async def list_all_artwork(cls) -> list[Self]: return [cls(file.path.name) for file in path_config.artwork_path.list_all_files()] @property - def self_file(self) -> "TemporaryResource": + def self_file(self) -> 'TemporaryResource': return self.path_config.artwork_path(self.s_aid) async def _query(self) -> ArtworkData: @@ -111,8 +112,8 @@ async def get_std_preview_desc(self, *, text_len_limit: int = 12) -> str: async def _save_page( self, page_index: int = 0, - page_type: "ArtworkPageParamType" = 'regular' - ) -> "TemporaryResource": + page_type: 'ArtworkPageParamType' = 'regular' + ) -> 'TemporaryResource': return self.self_file diff --git a/src/service/artwork_proxy/sites/moebooru.py b/src/service/artwork_proxy/sites/moebooru.py index 6a862500..5c1c0077 100644 --- a/src/service/artwork_proxy/sites/moebooru.py +++ b/src/service/artwork_proxy/sites/moebooru.py @@ -9,21 +9,9 @@ """ import abc -from typing import Optional - -from src.utils.booru_api import ( - behoimi_api, - konachan_api, - konachan_safe_api, - yandere_api -) -from src.utils.booru_api.moebooru import ( - BaseMoebooruAPI, - BehoimiAPI, - KonachanAPI, - KonachanSafeAPI, - YandereAPI -) + +from src.utils.booru_api import behoimi_api, konachan_api, konachan_safe_api, yandere_api +from src.utils.booru_api.moebooru import BaseMoebooruAPI, BehoimiAPI, KonachanAPI, KonachanSafeAPI, YandereAPI from ..add_ons import ImageOpsPlusPoolMixin from ..internal import BaseArtworkProxy from ..models import ArtworkData, ArtworkPool @@ -52,7 +40,7 @@ async def _random(cls, *, limit: int = 20) -> list[str | int]: return [x.id for x in artworks_data] @classmethod - async def _search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[str | int]: + async def _search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[str | int]: artworks_data = await cls._get_api().posts_index(tags=keyword, page=page, **kwargs) return [x.id for x in artworks_data] @@ -110,6 +98,7 @@ async def _query(self) -> ArtworkData: 'height': artwork_data.height, 'tags': tags, 'description': None, + 'like_count': artwork_data.score, 'source': artwork_data.source, 'pages': [{ 'preview_file': { diff --git a/src/service/artwork_proxy/sites/none.py b/src/service/artwork_proxy/sites/none.py index 236869e5..3bd6b56c 100644 --- a/src/service/artwork_proxy/sites/none.py +++ b/src/service/artwork_proxy/sites/none.py @@ -8,7 +8,6 @@ @Software : PyCharm """ -from typing import Optional from ..internal import BaseArtworkProxy from ..models import ArtworkData @@ -34,7 +33,7 @@ async def _random(cls, *, limit: int = 20) -> list[str | int]: raise NotImplementedError @classmethod - async def _search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[str | int]: + async def _search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[str | int]: raise NotImplementedError async def _query(self) -> ArtworkData: diff --git a/src/service/artwork_proxy/sites/pixiv.py b/src/service/artwork_proxy/sites/pixiv.py index d8f6d14a..b14e0bba 100644 --- a/src/service/artwork_proxy/sites/pixiv.py +++ b/src/service/artwork_proxy/sites/pixiv.py @@ -9,7 +9,6 @@ """ import random -from typing import Optional from src.utils.pixiv_api import PixivArtwork from ..add_ons import ImageOpsMixin @@ -38,7 +37,7 @@ async def _random(cls, *, limit: int = 20) -> list[str | int]: return [x for x in random.sample(artworks_data.recommend_pids, k=limit)] @classmethod - async def _search(cls, keyword: str, *, page: Optional[int] = None, **kwargs) -> list[str | int]: + async def _search(cls, keyword: str, *, page: int | None = None, **kwargs) -> list[str | int]: page = 1 if page is None else page if kwargs: artworks_data = await PixivArtwork.search(word=keyword, page=page, **kwargs) @@ -69,6 +68,10 @@ async def _query(self) -> ArtworkData: 'height': artwork_data.height, 'tags': artwork_data.tags, 'description': artwork_data.description, + 'like_count': artwork_data.like_count, + 'bookmark_count': artwork_data.bookmark_count, + 'view_count': artwork_data.view_count, + 'comment_count': artwork_data.comment_count, 'source': artwork_data.url, 'pages': [ { @@ -92,7 +95,8 @@ async def _query(self) -> ArtworkData: } } for _, page in artwork_data.all_page.items() - ] + ], + 'extra_resource': [artwork_data.ugoira_meta.originalSrc] if artwork_data.ugoira_meta is not None else [] }) async def get_std_desc(self, *, desc_len_limit: int = 128) -> str: @@ -113,15 +117,15 @@ async def get_std_desc(self, *, desc_len_limit: int = 128) -> str: async def get_std_preview_desc(self, *, text_len_limit: int = 12) -> str: artwork_data = await self.query() - origin = f"{artwork_data.origin.title()}: {artwork_data.aid}" + origin = f'{artwork_data.origin.title()}: {artwork_data.aid}' title = ( - f"{artwork_data.title[:text_len_limit]}..." + f'{artwork_data.title[:text_len_limit]}...' if len(artwork_data.title) > text_len_limit else artwork_data.title ) - author = f"Author: {artwork_data.uname}" - author = f"{author[:text_len_limit]}..." if len(author) > text_len_limit else author + author = f'Author: {artwork_data.uname}' + author = f'{author[:text_len_limit]}...' if len(author) > text_len_limit else author return f'{origin}\n{title}\n{author}' diff --git a/src/service/artwork_proxy/typing.py b/src/service/artwork_proxy/typing.py index ecf0bbb8..d4764b87 100644 --- a/src/service/artwork_proxy/typing.py +++ b/src/service/artwork_proxy/typing.py @@ -8,8 +8,9 @@ @Software : PyCharm """ +from collections.abc import Callable from functools import wraps -from typing import Callable, Literal, TypeVar, cast +from typing import Literal, TypeVar, cast from .internal import BaseArtworkProxy diff --git a/src/service/gocqhttp_addition_event_patch/__init__.py b/src/service/gocqhttp_addition_event_patch/__init__.py index 4d4da143..9039707b 100644 --- a/src/service/gocqhttp_addition_event_patch/__init__.py +++ b/src/service/gocqhttp_addition_event_patch/__init__.py @@ -10,7 +10,7 @@ from nonebot.log import logger -from .model import GroupCardNoticeEvent, OfflineFileNoticeEvent, ClientStatusNoticeEvent, EssenceNoticeEvent +from .model import ClientStatusNoticeEvent, EssenceNoticeEvent, GroupCardNoticeEvent, OfflineFileNoticeEvent logger.opt(colors=True).info('Addition event patch(go-cqhttp) loaded') diff --git a/src/service/gocqhttp_addition_event_patch/model.py b/src/service/gocqhttp_addition_event_patch/model.py index a315cd6e..dbda1c99 100644 --- a/src/service/gocqhttp_addition_event_patch/model.py +++ b/src/service/gocqhttp_addition_event_patch/model.py @@ -8,21 +8,21 @@ @Software : PyCharm """ -from typing import Type, TypeVar, Literal, override +from typing import Literal, TypeVar, override from nonebot.adapters.onebot.v11.adapter import Adapter from nonebot.adapters.onebot.v11.event import Event, NoticeEvent from nonebot.log import logger from pydantic import BaseModel, ConfigDict -Event_T = TypeVar("Event_T", bound=Type[Event]) +Event_T = TypeVar('Event_T', bound=type[Event]) def register_event(event: Event_T) -> Event_T: Adapter.add_custom_model(event) logger.opt(colors=True).trace( - f"Custom event {event.__qualname__!r} registered to adapter {Adapter.get_name()!r} " - f"from module {event.__module__!r}" + f'Custom event {event.__qualname__!r} registered to adapter {Adapter.get_name()!r} ' + f'from module {event.__module__!r}' ) return event @@ -31,7 +31,7 @@ def register_event(event: Event_T) -> Event_T: class GroupCardNoticeEvent(NoticeEvent): """群成员名片更新提醒事件(此事件不保证时效性, 仅在收到消息时校验卡片)""" - notice_type: Literal["group_card"] + notice_type: Literal['group_card'] group_id: int user_id: int card_new: str @@ -47,7 +47,7 @@ def get_user_id(self) -> str: @override def get_session_id(self) -> str: - return f"group_{self.group_id}_{self.user_id}" + return f'group_{self.group_id}_{self.user_id}' class OfflineFile(BaseModel): @@ -55,14 +55,14 @@ class OfflineFile(BaseModel): size: int url: str - model_config = ConfigDict(extra="allow") + model_config = ConfigDict(extra='allow') @register_event class OfflineFileNoticeEvent(NoticeEvent): """接收到离线文件提醒事件""" - notice_type: Literal["offline_file"] + notice_type: Literal['offline_file'] user_id: int file: OfflineFile @@ -76,7 +76,7 @@ def get_user_id(self) -> str: @override def get_session_id(self) -> str: - return f"{self.user_id}_{self.file.name}" + return f'{self.user_id}_{self.file.name}' class Device(BaseModel): @@ -90,14 +90,14 @@ class Device(BaseModel): device_name: str device_kind: str - model_config = ConfigDict(extra="allow") + model_config = ConfigDict(extra='allow') @register_event class ClientStatusNoticeEvent(NoticeEvent): """其他客户端在线状态变更""" - notice_type: Literal["client_status"] + notice_type: Literal['client_status'] client: Device online: bool @@ -111,15 +111,15 @@ def get_user_id(self) -> str: @override def get_session_id(self) -> str: - return f"{self.self_id}_{self.client.app_id}" + return f'{self.self_id}_{self.client.app_id}' @register_event class EssenceNoticeEvent(NoticeEvent): """精华消息变更""" - notice_type: Literal["essence"] - sub_type: Literal["add", "delete"] + notice_type: Literal['essence'] + sub_type: Literal['add', 'delete'] group_id: int sender_id: int operator_id: int @@ -135,7 +135,7 @@ def get_user_id(self) -> str: @override def get_session_id(self) -> str: - return f"group_{self.group_id}_{self.sender_id}" + return f'group_{self.group_id}_{self.sender_id}' __all__ = [ diff --git a/src/service/gocqhttp_guild_patch/LICENSE b/src/service/gocqhttp_guild_patch/LICENSE deleted file mode 100644 index 83c3c7f2..00000000 --- a/src/service/gocqhttp_guild_patch/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Mix - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/service/gocqhttp_guild_patch/README.md b/src/service/gocqhttp_guild_patch/README.md deleted file mode 100644 index 865bc339..00000000 --- a/src/service/gocqhttp_guild_patch/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# nonebot-plugin-guild-patch - -_Patch plugin for NoneBot2 QQ guild (go-cqhttp) support._ - -_NoneBot2 QQ 频道 (go-cqhttp) 支持适配补丁插件._ - -![PyPI](https://img.shields.io/pypi/v/nonebot-plugin-guild-patch?style=for-the-badge) - -[![GitHub issues](https://img.shields.io/github/issues/mnixry/nonebot-plugin-guild-patch)](https://github.com/mnixry/nonebot-plugin-guild-patch/issues) -[![GitHub forks](https://img.shields.io/github/forks/mnixry/nonebot-plugin-guild-patch)](https://github.com/mnixry/nonebot-plugin-guild-patch/network) -[![GitHub stars](https://img.shields.io/github/stars/mnixry/nonebot-plugin-guild-patch)](https://github.com/mnixry/nonebot-plugin-guild-patch/stargazers) -[![GitHub license](https://img.shields.io/github/license/mnixry/nonebot-plugin-guild-patch)](https://github.com/mnixry/nonebot-plugin-guild-patch/blob/main/LICENSE) - -> **注: 本补丁没有经过充分测试, 不建议在生产环境使用, 如果发现任何问题请[Issue 反馈](https://github.com/mnixry/nonebot-plugin-guild-patch/issues/new/choose)** - -## 适用版本 - -- `go-cqhttp` >= `1.0.0-beta8-fix2` -- `NoneBot2` >= `2.0.0b1` - -## 支持功能 - -- [x] 正常接收并处理频道消息事件 - - [x] 支持字符串形式消息上报 - - [x] 支持数组形式消息上报 -- [x] 支持`bot.send`和`matcher.send`直接向频道发送消息 -- [x] 支持`event.to_me`以支持`to_me`规则 -- [ ] 可选的事件转换器, 将频道消息事件转换为群消息 - -## 安装 - -使用`nb-cli`或者其他什么你喜欢的方式安装并加载该插件即可 - -如果它被成功加载, 你在调试模式下应该看到这样的日志: - -```diff -11-13 09:14:52 [DEBUG] nonebot | Succeeded to load adapter "onebot" -11-13 09:14:52 [SUCCESS] nonebot | Succeeded to import "nonebot.plugins.echo" -+ 11-13 09:14:52 [SUCCESS] nonebot | Succeeded to import "nonebot_plugin_guild_patch" -11-13 09:14:52 [SUCCESS] nonebot | Running NoneBot... -11-13 09:14:52 [DEBUG] nonebot | Loaded adapters: cqhttp -11-13 09:14:52 [INFO] uvicorn | Started server process [114514] -11-13 09:14:52 [INFO] uvicorn | Waiting for application startup. -11-13 09:14:52 [INFO] uvicorn | Application startup complete. -``` - -## 使用 - -这里有一个示例插件, 它只会接收来自频道的消息 - -```python -from nonebot.plugin import on_command -from nonebot.adapters.onebot import Bot, MessageSegment - -from nonebot_plugin_guild_patch import GuildMessageEvent - -matcher = on_command('image') - - -@matcher.handle() -async def _(bot: Bot, event: GuildMessageEvent): - await matcher.send( - MessageSegment.image( - file='https://1mg.obfs.dev/', - cache=False, - )) -``` - -## 开源许可 - -本项目使用[MIT](./LICENSE)许可证开源 - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. diff --git a/src/service/gocqhttp_guild_patch/__init__.py b/src/service/gocqhttp_guild_patch/__init__.py deleted file mode 100644 index 73025618..00000000 --- a/src/service/gocqhttp_guild_patch/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Optional, Union - -from nonebot.adapters.onebot.v11 import Bot, Event, Message, MessageSegment -from nonebot.log import logger - -from .models import ( - ChannelCreatedNoticeEvent, - ChannelDestroyedNoticeEvent, - ChannelNoticeEvent, - ChannelUpdatedNoticeEvent, - GuildChannelRecallNoticeEvent, - GuildMessageEvent, - MessageReactionsUpdatedNoticeEvent, -) -from .permission import GUILD, GUILD_ADMIN, GUILD_OWNER, GUILD_SUPERUSER - -original_send = Bot.send - - -async def patched_send( - self: Bot, - event: Event, - message: Union[Message, MessageSegment, str], - **kwargs, -): - guild_id: Optional[int] = getattr(event, "guild_id", None) - channel_id: Optional[int] = getattr(event, "channel_id", None) - - if not (guild_id and channel_id): - return await original_send(self, event, message, **kwargs) - logger.opt(colors=True).debug( - "Sending guild message to " - f"guild_id={guild_id}, channel_id={channel_id}" - ) - - user_id: Optional[int] = getattr(event, "user_id", None) - - message_sent = Message() - if user_id and kwargs.get("at_sender", False): - message_sent += MessageSegment.at(user_id) + " " - message_sent += message - - return await self.send_guild_channel_msg( - guild_id=guild_id, - channel_id=channel_id, - message=message_sent, - **kwargs, - ) - - -Bot.send = patched_send - -logger.opt(colors=True).info('Guild patch(go-cqhttp) loaded') - - -__all__ = [ - 'GUILD', - 'GUILD_OWNER', - 'GUILD_ADMIN', - 'GUILD_SUPERUSER', - 'GuildMessageEvent', - 'ChannelNoticeEvent', - 'GuildChannelRecallNoticeEvent', - 'MessageReactionsUpdatedNoticeEvent', - 'ChannelUpdatedNoticeEvent', - 'ChannelCreatedNoticeEvent', - 'ChannelDestroyedNoticeEvent', -] diff --git a/src/service/gocqhttp_guild_patch/models.py b/src/service/gocqhttp_guild_patch/models.py deleted file mode 100644 index 28697a3a..00000000 --- a/src/service/gocqhttp_guild_patch/models.py +++ /dev/null @@ -1,243 +0,0 @@ -from typing import List, Optional, Tuple, Type, TypeVar, override - -from nonebot.adapters.onebot.v11 import ( - Adapter, - Event, - Message, - MessageEvent, - MessageSegment, - NoticeEvent, -) -from nonebot.log import logger -from nonebot.utils import escape_tag -from pydantic import BaseModel, ConfigDict, Field, TypeAdapter, field_validator, model_validator -from typing_extensions import Literal - -Event_T = TypeVar("Event_T", bound=Type[Event]) - - -def register_event(event: Event_T) -> Event_T: - Adapter.add_custom_model(event) - logger.opt(colors=True).trace( - f"Custom event {event.__qualname__!r} registered " - f"from module {event.__class__.__module__!r}" - ) - return event - - -@register_event -class GuildMessageEvent(MessageEvent): - """收到频道消息""" - - message_type: Literal["guild"] - self_tiny_id: int - message_id: str - guild_id: int - channel_id: int - - raw_message: str = Field(alias="message") - font: None = None - - @field_validator("raw_message", mode="before") - @classmethod - def _validate_raw_message(cls, raw_message): - if isinstance(raw_message, str): - return raw_message - elif isinstance(raw_message, list): - return str(TypeAdapter(Message).validate_python(raw_message)) - raise ValueError("unknown raw message type") - - @model_validator(mode='before') - @classmethod - def _validate_is_tome(cls, values): - message = values.get("message") - self_tiny_id = values.get("self_tiny_id") - message, is_tome = cls._check_at_me(message=message, self_tiny_id=self_tiny_id) - values.update( - {"message": message, "to_me": is_tome, "raw_message": str(message)} - ) - return values - - @override - def is_tome(self) -> bool: - return self.to_me or any( - str(msg_seg.data.get("qq", "")) == str(self.self_tiny_id) - for msg_seg in self.message - if msg_seg.type == "at" - ) - - @override - def get_event_description(self) -> str: - return ( - f"Message {self.message_id} from " - f'{self.user_id}@[Guild:{self.guild_id}/Channel:{self.channel_id}] "%s"' - % "".join( - map( - lambda x: escape_tag(str(x)) - if x.is_text() - else f"{escape_tag(str(x))}", - self.message, - ) - ) - ) - - @override - def get_session_id(self) -> str: - return f"guild_{self.guild_id}_channel_{self.channel_id}_{self.user_id}" - - @staticmethod - def _check_at_me(message: Message, self_tiny_id: int) -> Tuple[Message, bool]: - """检查消息开头或结尾是否存在 @机器人,去除并赋值 event.to_me""" - is_tome = False - # ensure message not empty - if not message: - message.append(MessageSegment.text("")) - - def _is_at_me_seg(segment: MessageSegment): - return segment.type == "at" and str(segment.data.get("qq", "")) == str( - self_tiny_id - ) - - # check the first segment - if _is_at_me_seg(message[0]): - is_tome = True - message.pop(0) - if message and message[0].type == "text": - message[0].data["text"] = message[0].data["text"].lstrip() - if not message[0].data["text"]: - del message[0] - if message and _is_at_me_seg(message[0]): - message.pop(0) - if message and message[0].type == "text": - message[0].data["text"] = message[0].data["text"].lstrip() - if not message[0].data["text"]: - del message[0] - - if not is_tome: - # check the last segment - i = -1 - last_msg_seg = message[i] - if ( - last_msg_seg.type == "text" - and not last_msg_seg.data["text"].strip() - and len(message) >= 2 - ): - i -= 1 - last_msg_seg = message[i] - - if _is_at_me_seg(last_msg_seg): - is_tome = True - del message[i:] - - if not message: - message.append(MessageSegment.text("")) - - return message, is_tome - - -class ReactionInfo(BaseModel): - emoji_id: str - emoji_index: int - emoji_type: int - emoji_name: str - count: int - clicked: bool - - model_config = ConfigDict(extra="allow") - - -@register_event -class ChannelNoticeEvent(NoticeEvent): - """频道通知事件""" - - notice_type: Literal["channel"] - self_tiny_id: int - guild_id: int - channel_id: int - user_id: int - sub_type: None = None - - -@register_event -class GuildChannelRecallNoticeEvent(ChannelNoticeEvent): - """频道消息撤回""" - - notice_type: Literal["guild_channel_recall"] - operator_id: int - message_id: str - - -@register_event -class MessageReactionsUpdatedNoticeEvent(ChannelNoticeEvent): - """频道消息表情贴更新""" - - notice_type: Literal["message_reactions_updated"] - message_id: str - current_reactions: Optional[List[ReactionInfo]] = None - - -class SlowModeInfo(BaseModel): - slow_mode_key: int - slow_mode_text: str - speak_frequency: int - slow_mode_circle: int - - model_config = ConfigDict(extra="allow") - - -class ChannelInfo(BaseModel): - owner_guild_id: int - channel_id: int - channel_type: int - channel_name: str - create_time: int - creator_id: Optional[int] = None - creator_tiny_id: int - talk_permission: int - visible_type: int - current_slow_mode: int - slow_modes: List[SlowModeInfo] = [] - - model_config = ConfigDict(extra="allow") - - -@register_event -class ChannelUpdatedNoticeEvent(ChannelNoticeEvent): - """子频道信息更新""" - - notice_type: Literal["channel_updated"] - operator_id: int - old_info: ChannelInfo - new_info: ChannelInfo - - -@register_event -class ChannelCreatedNoticeEvent(ChannelNoticeEvent): - """子频道创建""" - - notice_type: Literal["channel_created"] - operator_id: int - channel_info: ChannelInfo - - -@register_event -class ChannelDestroyedNoticeEvent(ChannelNoticeEvent): - """子频道删除""" - - notice_type: Literal["channel_destroyed"] - operator_id: int - channel_info: ChannelInfo - - -__all__ = [ - 'GuildMessageEvent', - 'ChannelNoticeEvent', - 'GuildChannelRecallNoticeEvent', - 'MessageReactionsUpdatedNoticeEvent', - 'ChannelUpdatedNoticeEvent', - 'ChannelCreatedNoticeEvent', - 'ChannelDestroyedNoticeEvent', - 'ReactionInfo', - 'SlowModeInfo', - 'ChannelInfo', -] diff --git a/src/service/gocqhttp_guild_patch/permission.py b/src/service/gocqhttp_guild_patch/permission.py deleted file mode 100644 index 04e91520..00000000 --- a/src/service/gocqhttp_guild_patch/permission.py +++ /dev/null @@ -1,56 +0,0 @@ -from nonebot.adapters.onebot.v11.bot import Bot -from nonebot.permission import Permission - -from .models import GuildMessageEvent - - -async def _guild(event: GuildMessageEvent) -> bool: - return True - - -async def _guild_admin(bot: Bot, event: GuildMessageEvent): - roles = { - role["role_name"] - for role in ( - await bot.get_guild_member_profile( - guild_id=event.guild_id, user_id=event.user_id - ) - )["roles"] - } - return "管理员" in roles - - -async def _guild_owner(bot: Bot, event: GuildMessageEvent): - roles = { - role["role_name"] - for role in ( - await bot.get_guild_member_profile( - guild_id=event.guild_id, user_id=event.user_id - ) - )["roles"] - } - return "频道主" in roles - - -async def _guild_superuser(bot: Bot, event: GuildMessageEvent) -> bool: - return ( - f"{bot.adapter.get_name().lower()}:{event.get_user_id()}" - in bot.config.superusers - ) or (event.get_user_id() in bot.config.superusers) - - -GUILD: Permission = Permission(_guild) -"""匹配任意频道消息类型事件""" -GUILD_SUPERUSER: Permission = Permission(_guild_superuser) -"""匹配任意超级用户频道消息类型事件""" -GUILD_ADMIN: Permission = Permission(_guild_admin) -"""匹配任意频道管理员消息类型事件""" -GUILD_OWNER: Permission = Permission(_guild_owner) -"""匹配任意频道频道主消息类型事件""" - -__all__ = [ - 'GUILD', - 'GUILD_OWNER', - 'GUILD_ADMIN', - 'GUILD_SUPERUSER', -] diff --git a/src/service/gocqhttp_self_sent_patch/model.py b/src/service/gocqhttp_self_sent_patch/model.py index c0c96263..a65e6dff 100644 --- a/src/service/gocqhttp_self_sent_patch/model.py +++ b/src/service/gocqhttp_self_sent_patch/model.py @@ -8,21 +8,21 @@ @Software : PyCharm """ -from typing import Optional, Type, TypeVar, Literal, override +from typing import Literal, TypeVar, override from nonebot.adapters.onebot.utils import highlight_rich_message from nonebot.adapters.onebot.v11.adapter import Adapter -from nonebot.adapters.onebot.v11.event import Event, MessageEvent, Anonymous +from nonebot.adapters.onebot.v11.event import Anonymous, Event, MessageEvent from nonebot.log import logger -Event_T = TypeVar("Event_T", bound=Type[Event]) +Event_T = TypeVar('Event_T', bound=type[Event]) def register_event(event: Event_T) -> Event_T: Adapter.add_custom_model(event) logger.opt(colors=True).trace( - f"Custom event {event.__qualname__!r} registered to adapter {Adapter.get_name()!r} " - f"from module {event.__module__!r}" + f'Custom event {event.__qualname__!r} registered to adapter {Adapter.get_name()!r} ' + f'from module {event.__module__!r}' ) return event @@ -31,16 +31,16 @@ def register_event(event: Event_T) -> Event_T: class MessageSentEvent(MessageEvent): """自身发送消息事件""" - post_type: Literal["message_sent"] - message_seq: Optional[int] = None - target_id: Optional[int] = None - group_id: Optional[int] = 0 - anonymous: Optional[Anonymous] = None + post_type: Literal['message_sent'] + message_seq: int | None = None + target_id: int | None = None + group_id: int | None = 0 + anonymous: Anonymous | None = None to_me: bool = False @override def get_type(self) -> str: - return "message" + return 'message' @override def get_event_description(self) -> str: @@ -55,7 +55,7 @@ def get_user_id(self) -> str: @override def get_session_id(self) -> str: - return f"self_sent_{self.self_id}" + return f'self_sent_{self.self_id}' @override def is_tome(self) -> bool: diff --git a/src/service/omega_api/__init__.py b/src/service/omega_api/__init__.py index 0015ab8e..449501fe 100644 --- a/src/service/omega_api/__init__.py +++ b/src/service/omega_api/__init__.py @@ -8,21 +8,19 @@ @Software : PyCharm """ -# TODO #1 规范 api 请求及响应模型 -# TODO #2 规范 api 依赖注入 -# TODO #3 添加 api 认证 - import inspect -from typing import TypeVar, ParamSpec, Callable, Coroutine -from nonebot import get_driver, get_app +from collections.abc import Callable, Coroutine + +from nonebot import get_app, get_driver from nonebot.log import logger from .helpers import return_standard_api_result from .model import BaseApiModel, BaseApiReturn -P = ParamSpec("P") -R = TypeVar("R") +# TODO #1 规范 api 请求及响应模型 +# TODO #2 规范 api 依赖注入 +# TODO #3 添加 api 认证 def register_get_route(path: str, *, enabled: bool = True): @@ -34,7 +32,7 @@ def register_get_route(path: str, *, enabled: bool = True): if not path.startswith('/'): path = '/' + path - def decorator(func: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Coroutine[None, None, R]]: + def decorator[R, ** P](func: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Coroutine[None, None, R]]: if not inspect.iscoroutinefunction(func): raise ValueError('The decorated function must be coroutine function') @@ -50,8 +48,8 @@ def decorator(func: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Corout host = str(driver.config.host) port = driver.config.port - if host in ["0.0.0.0", "127.0.0.1"]: - host = "localhost" + if host in ['0.0.0.0', '127.0.0.1']: + host = 'localhost' logger.opt(colors=True).info( f"Service {inspect.getmodule(func).__name__} running at: " f"http://{host}:{port}/{path.removeprefix('/')}" @@ -66,5 +64,5 @@ def decorator(func: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Corout 'BaseApiModel', 'BaseApiReturn', 'register_get_route', - 'return_standard_api_result' + 'return_standard_api_result', ] diff --git a/src/service/omega_api/helpers.py b/src/service/omega_api/helpers.py index f60b20b2..e84cfc95 100644 --- a/src/service/omega_api/helpers.py +++ b/src/service/omega_api/helpers.py @@ -9,20 +9,15 @@ """ import inspect +from collections.abc import Callable, Coroutine from functools import wraps -from typing import Callable, Coroutine, ParamSpec, TypeVar from nonebot.log import logger from .model import BaseApiReturn -T = TypeVar("T") -R = TypeVar("R") -P = ParamSpec("P") - - -def return_standard_api_result( +def return_standard_api_result[** P, R]( func: Callable[P, Coroutine[None, None, R]] ) -> Callable[P, Coroutine[None, None, BaseApiReturn]]: """装饰一个 api handler 捕获其运行时的异常并使其返回 BaseApiReturn""" @@ -49,5 +44,5 @@ async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> BaseApiReturn: __all__ = [ - 'return_standard_api_result' + 'return_standard_api_result', ] diff --git a/src/service/omega_api/model.py b/src/service/omega_api/model.py index 6159e7ee..601cf650 100644 --- a/src/service/omega_api/model.py +++ b/src/service/omega_api/model.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from typing import Any, Optional +from typing import Any + from pydantic import BaseModel, ConfigDict @@ -23,7 +24,7 @@ class BaseApiReturn(BaseApiModel): error: bool body: Any message: str - exception: Optional[str] = None + exception: str | None = None @property def success(self) -> bool: diff --git a/src/service/omega_base/__init__.py b/src/service/omega_base/__init__.py index ff53f657..d2142898 100644 --- a/src/service/omega_base/__init__.py +++ b/src/service/omega_base/__init__.py @@ -9,14 +9,16 @@ """ from .internal import OmegaEntity -from .message import Message as OmegaMessage, MessageSegment as OmegaMessageSegment +from .message import Message as OmegaMessage +from .message import MessageSegment as OmegaMessageSegment +from .message import MessageTransferUtils as OmegaMessageTransfer from .middlewares import OmegaEntityInterface, OmegaMatcherInterface - __all__ = [ 'OmegaEntity', 'OmegaEntityInterface', 'OmegaMatcherInterface', 'OmegaMessage', 'OmegaMessageSegment', + 'OmegaMessageTransfer', ] diff --git a/src/service/omega_base/event/__init__.py b/src/service/omega_base/event/__init__.py index 65baf915..c43831e7 100644 --- a/src/service/omega_base/event/__init__.py +++ b/src/service/omega_base/event/__init__.py @@ -11,7 +11,6 @@ from .base import Event from .bot import BotActionEvent, BotConnectEvent, BotDisconnectEvent - __all__ = [ 'Event', 'BotActionEvent', diff --git a/src/service/omega_base/event/base.py b/src/service/omega_base/event/base.py index efc55c20..046a507b 100644 --- a/src/service/omega_base/event/base.py +++ b/src/service/omega_base/event/base.py @@ -8,12 +8,14 @@ @Software : PyCharm """ -from typing import override +from typing import TYPE_CHECKING, override -from nonebot.adapters import Event as BaseEvent -from nonebot.adapters import Message +from nonebot.internal.adapter import Event as BaseEvent from nonebot.utils import escape_tag +if TYPE_CHECKING: + from nonebot.internal.adapter import Message as BaseMessage + class Event(BaseEvent): """Omega 内部事件基类""" @@ -33,7 +35,7 @@ def get_event_description(self) -> str: return escape_tag(str(self.model_dump())) @override - def get_message(self) -> Message: + def get_message(self) -> 'BaseMessage': raise NotImplementedError @override diff --git a/src/service/omega_base/event/bot.py b/src/service/omega_base/event/bot.py index aa38d3ae..3dc91b31 100644 --- a/src/service/omega_base/event/bot.py +++ b/src/service/omega_base/event/bot.py @@ -8,12 +8,13 @@ @Software : PyCharm """ -from typing import Literal, override - -from nonebot.adapters import Message +from typing import TYPE_CHECKING, Literal, override from .base import Event as OmegaEvent +if TYPE_CHECKING: + from nonebot.internal.adapter import Message as BaseMessage + class BotActionEvent(OmegaEvent): """Bot 动作事件""" @@ -24,7 +25,7 @@ class BotActionEvent(OmegaEvent): action: str @override - def get_message(self) -> Message: + def get_message(self) -> 'BaseMessage': raise ValueError('Event has no message!') @override diff --git a/src/service/omega_base/internal/__init__.py b/src/service/omega_base/internal/__init__.py index bf415e01..fec7e8ed 100644 --- a/src/service/omega_base/internal/__init__.py +++ b/src/service/omega_base/internal/__init__.py @@ -15,7 +15,6 @@ from .subscription_source import InternalPixivisionSubscriptionSource as OmegaPixivisionSubSource from .subscription_source import InternalWeiboUserSubscriptionSource as OmegaWeiboUserSubSource - __all__ = [ 'OmegaEntity', 'OmegaBiliDynamicSubSource', diff --git a/src/service/omega_base/internal/consts.py b/src/service/omega_base/internal/consts.py index 87ec0f8a..bae97660 100644 --- a/src/service/omega_base/internal/consts.py +++ b/src/service/omega_base/internal/consts.py @@ -9,6 +9,7 @@ """ from typing import Literal + from pydantic.dataclasses import dataclass diff --git a/src/service/omega_base/internal/entity.py b/src/service/omega_base/internal/entity.py index 12634a5f..4d14bc9a 100644 --- a/src/service/omega_base/internal/entity.py +++ b/src/service/omega_base/internal/entity.py @@ -9,44 +9,42 @@ """ from datetime import date, datetime, timedelta -from typing import TYPE_CHECKING, Literal, Optional, Self +from typing import TYPE_CHECKING, Literal, Self from sqlalchemy.exc import NoResultFound from src.database.internal.auth_setting import AuthSetting, AuthSettingDAL from src.database.internal.bot import BotSelf, BotSelfDAL from src.database.internal.cooldown import CoolDown, CoolDownDAL -from src.database.internal.email_box import EmailBox, EmailBoxDAL -from src.database.internal.email_box_bind import EmailBoxBindDAL from src.database.internal.entity import Entity, EntityDAL, EntityType from src.database.internal.friendship import Friendship, FriendshipDAL from src.database.internal.sign_in import SignInDAL from src.database.internal.subscription import SubscriptionDAL from src.database.internal.subscription_source import SubscriptionSource, SubscriptionSourceDAL from .consts import ( + GLOBAL_COOLDOWN_EVENT, + RATE_LIMITING_COOLDOWN_EVENT, + SKIP_COOLDOWN_PERMISSION_NODE, PermissionGlobal, PermissionLevel, - SKIP_COOLDOWN_PERMISSION_NODE, - GLOBAL_COOLDOWN_EVENT, - RATE_LIMITING_COOLDOWN_EVENT ) if TYPE_CHECKING: from sqlalchemy.ext.asyncio import AsyncSession -class InternalEntity(object): +class InternalEntity: """封装后用于插件调用的数据库实体操作对象""" def __init__( self, - session: "AsyncSession", + session: 'AsyncSession', bot_id: str, entity_type: str, entity_id: str, parent_id: str, - entity_name: Optional[str] = None, - entity_info: Optional[str] = None + entity_name: str | None = None, + entity_info: str | None = None ) -> None: self.db_session = session self.bot_id = bot_id @@ -64,7 +62,7 @@ def tid(self) -> str: return f'{self.entity_type}_{self.entity_id}' @classmethod - async def init_from_entity_index_id(cls, session: "AsyncSession", index_id: int) -> Self: + async def init_from_entity_index_id(cls, session: 'AsyncSession', index_id: int) -> Self: entity = await EntityDAL(session=session).query_by_index_id(index_id=index_id) bot = await BotSelfDAL(session=session).query_by_index_id(index_id=entity.bot_index_id) return cls( @@ -76,12 +74,12 @@ async def init_from_entity_index_id(cls, session: "AsyncSession", index_id: int) ) @classmethod - async def query_all_entity_by_type(cls, session: "AsyncSession", entity_type: str) -> list[Entity]: + async def query_all_entity_by_type(cls, session: 'AsyncSession', entity_type: str) -> list[Entity]: """查询符合 entity_type 的全部结果""" return await EntityDAL(session=session).query_all_by_type(entity_type=entity_type) @classmethod - async def query_all_entity(cls, session: "AsyncSession") -> list[Entity]: + async def query_all_entity(cls, session: 'AsyncSession') -> list[Entity]: """查询符合 entity_type 的全部结果""" return await EntityDAL(session=session).query_all() @@ -102,8 +100,8 @@ async def query_entity_self(self) -> Entity: async def add_ignore_exists( self, - entity_name: Optional[str] = None, - entity_info: Optional[str] = None + entity_name: str | None = None, + entity_info: str | None = None ) -> None: """新增 Entity, 若已存在忽略""" bot = await self.query_bot_self() @@ -121,8 +119,8 @@ async def add_ignore_exists( async def add_upgrade( self, - entity_name: Optional[str] = None, - entity_info: Optional[str] = None + entity_name: str | None = None, + entity_info: str | None = None ) -> None: """新增 Entity, 若已存在则更新""" bot = await self.query_bot_self() @@ -167,7 +165,7 @@ async def set_friendship( async def change_friendship( self, *, - status: Optional[str] = None, + status: str | None = None, mood: float = 0, friendship: float = 0, energy: float = 0, @@ -207,8 +205,8 @@ async def query_friendship(self) -> Friendship: async def sign_in( self, *, - date_: Optional[date | datetime] = None, - sign_in_info: Optional[str] = None, + date_: date | datetime | None = None, + sign_in_info: str | None = None, ) -> None: """签到 @@ -412,7 +410,7 @@ async def set_auth_setting( node: str, available: int, *, - value: Optional[str] = None + value: str | None = None ) -> None: """设置 Entity 权限节点参数值""" entity = await self.query_entity_self() @@ -460,7 +458,7 @@ async def set_cooldown( self, cooldown_event: str, expired_time: datetime | timedelta, - description: Optional[str] = None + description: str | None = None ) -> None: """设置冷却 @@ -529,34 +527,7 @@ async def _check_rate_limiting_cooldown_expired(self) -> tuple[bool, datetime]: """ return await self.check_cooldown_expired(cooldown_event=RATE_LIMITING_COOLDOWN_EVENT) - async def bind_email_box(self, email_box: EmailBox, bind_info: Optional[str] = None) -> None: - """绑定邮箱""" - entity = await self.query_entity_self() - bind_dal = EmailBoxBindDAL(session=self.db_session) - - try: - bind = await bind_dal.query_unique(email_box_index_id=email_box.id, entity_index_id=entity.id) - await bind_dal.update(id_=bind.id, bind_info=bind_info) - except NoResultFound: - await bind_dal.add(email_box_index_id=email_box.id, entity_index_id=entity.id, bind_info=bind_info) - - async def unbind_email_box(self, email_box: EmailBox) -> None: - """解绑邮箱""" - entity = await self.query_entity_self() - bind_dal = EmailBoxBindDAL(session=self.db_session) - - try: - bind = await bind_dal.query_unique(email_box_index_id=email_box.id, entity_index_id=entity.id) - await bind_dal.delete(id_=bind.id) - except NoResultFound: - pass - - async def query_bound_email_box(self) -> list[EmailBox]: - """查询已绑定的全部邮箱""" - entity = await self.query_entity_self() - return await EmailBoxDAL(session=self.db_session).query_entity_bound_all(entity_index_id=entity.id) - - async def add_subscription(self, subscription_source: SubscriptionSource, sub_info: Optional[str] = None) -> None: + async def add_subscription(self, subscription_source: SubscriptionSource, sub_info: str | None = None) -> None: """添加订阅""" entity = await self.query_entity_self() subscription_dal = SubscriptionDAL(session=self.db_session) @@ -581,7 +552,7 @@ async def delete_subscription(self, subscription_source: SubscriptionSource) -> except NoResultFound: pass - async def query_subscribed_source(self, sub_type: Optional[str] = None) -> list[SubscriptionSource]: + async def query_subscribed_source(self, sub_type: str | None = None) -> list[SubscriptionSource]: """查询全部已订阅的订阅源 :param sub_type: 可选: 根据 sub_type 筛选, 若无则为全部类型 diff --git a/src/service/omega_base/internal/subscription_source.py b/src/service/omega_base/internal/subscription_source.py index 439f6aa0..67016a0c 100644 --- a/src/service/omega_base/internal/subscription_source.py +++ b/src/service/omega_base/internal/subscription_source.py @@ -9,12 +9,12 @@ """ import abc -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from sqlalchemy.exc import NoResultFound from src.database.internal.entity import Entity, EntityDAL -from src.database.internal.subscription_source import SubscriptionSource, SubscriptionSourceType, SubscriptionSourceDAL +from src.database.internal.subscription_source import SubscriptionSource, SubscriptionSourceDAL, SubscriptionSourceType if TYPE_CHECKING: from sqlalchemy.ext.asyncio import AsyncSession @@ -24,11 +24,11 @@ class InternalSubscriptionSource(abc.ABC): """封装后用于插件调用的数据库实体操作对象""" __slots__ = ('db_session', 'sub_id',) - db_session: "AsyncSession" + db_session: 'AsyncSession' sub_id: str @abc.abstractmethod - def __init__(self, session: "AsyncSession", *args, **kwargs): + def __init__(self, session: 'AsyncSession', *args, **kwargs): raise NotImplementedError @classmethod @@ -37,7 +37,7 @@ def get_sub_type(cls) -> str: raise NotImplementedError @classmethod - async def query_type_all(cls, session: "AsyncSession") -> list[SubscriptionSource]: + async def query_type_all(cls, session: 'AsyncSession') -> list[SubscriptionSource]: """查询 sub_type 对应的全部订阅源""" return await SubscriptionSourceDAL(session=session).query_type_all(sub_type=cls.get_sub_type()) @@ -47,7 +47,7 @@ async def query_subscription_source(self) -> SubscriptionSource: sub_type=self.get_sub_type(), sub_id=self.sub_id ) - async def add_upgrade(self, sub_user_name: str, sub_info: Optional[str] = None) -> None: + async def add_upgrade(self, sub_user_name: str, sub_info: str | None = None) -> None: """新增订阅源, 若已存在则更新""" source_dal = SubscriptionSourceDAL(session=self.db_session) try: @@ -63,7 +63,7 @@ async def delete(self) -> None: source = await self.query_subscription_source() await SubscriptionSourceDAL(session=self.db_session).delete(id_=source.id) - async def query_all_entity_subscribed(self, entity_type: Optional[str] = None) -> list[Entity]: + async def query_all_entity_subscribed(self, entity_type: str | None = None) -> list[Entity]: """查询订阅了该订阅源的所有 Entity 对象""" source = await self.query_subscription_source() dal = EntityDAL(session=self.db_session) @@ -73,7 +73,7 @@ async def query_all_entity_subscribed(self, entity_type: Optional[str] = None) - class InternalBilibiliLiveSubscriptionSource(InternalSubscriptionSource): """Bilibili 直播订阅源""" - def __init__(self, session: "AsyncSession", live_room_id: str | int): + def __init__(self, session: 'AsyncSession', live_room_id: str | int): self.db_session = session self.sub_id = str(live_room_id) @@ -85,7 +85,7 @@ def get_sub_type(cls) -> str: class InternalBilibiliDynamicSubscriptionSource(InternalSubscriptionSource): """Bilibili 动态订阅源""" - def __init__(self, session: "AsyncSession", uid: str | int): + def __init__(self, session: 'AsyncSession', uid: str | int): self.db_session = session self.sub_id = str(uid) @@ -97,7 +97,7 @@ def get_sub_type(cls) -> str: class InternalPixivUserSubscriptionSource(InternalSubscriptionSource): """Pixiv 用户订阅源""" - def __init__(self, session: "AsyncSession", uid: str | int): + def __init__(self, session: 'AsyncSession', uid: str | int): self.db_session = session self.sub_id = str(uid) @@ -109,7 +109,7 @@ def get_sub_type(cls) -> str: class InternalPixivisionSubscriptionSource(InternalSubscriptionSource): """Pixivision 特辑订阅源""" - def __init__(self, session: "AsyncSession"): + def __init__(self, session: 'AsyncSession'): self.db_session = session self.sub_id = 'pixivision' @@ -117,14 +117,14 @@ def __init__(self, session: "AsyncSession"): def get_sub_type(cls) -> str: return SubscriptionSourceType.pixivision.value - async def add_upgrade(self, sub_user_name: str = '', sub_info: Optional[str] = None) -> None: + async def add_upgrade(self, sub_user_name: str = '', sub_info: str | None = None) -> None: return await super().add_upgrade(sub_user_name='pixivision', sub_info='Pixivision特辑订阅') class InternalWeiboUserSubscriptionSource(InternalSubscriptionSource): """微博用户订阅源""" - def __init__(self, session: "AsyncSession", uid: str | int): + def __init__(self, session: 'AsyncSession', uid: str | int): self.db_session = session self.sub_id = str(uid) diff --git a/src/service/omega_base/message.py b/src/service/omega_base/message.py deleted file mode 100644 index 5ccdf701..00000000 --- a/src/service/omega_base/message.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2023/5/27 13:59 -@FileName : message -@Project : nonebot2_miya -@Description : Omega Internal Message -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from enum import StrEnum, unique -from pathlib import Path -from typing import Iterable, Type, Union, override - -import ujson as json -from nonebot.adapters import Message as BaseMessage -from nonebot.adapters import MessageSegment as BaseMessageSegment - - -@unique -class MessageSegmentType(StrEnum): - at = 'at' - at_all = 'at_all' - forward_id = 'forward_id' - custom_node = 'custom_node' - image = 'image' - image_file = 'image_file' - file = 'file' - text = 'text' - - -class MessageSegment(BaseMessageSegment["Message"]): - """Omega 中间件 MessageSegment 适配。具体方法参考协议消息段类型或源码。""" - - @classmethod - @override - def get_message_class(cls) -> Type["Message"]: - return Message - - @override - def __str__(self) -> str: - if self.is_text(): - return self.data.get('text', '') - return '' - - def __repr__(self) -> str: - if self.is_text(): - return self.data.get('text', '') - params = ', '.join([f'{k}={v}' for k, v in self.data.items() if v is not None]) - return f'[{self.type}{":" if params else ""}{params}]' - - @override - def is_text(self) -> bool: - return self.type == MessageSegmentType.text - - @staticmethod - def at(user_id: int | str) -> "MessageSegment": - return MessageSegment(type=MessageSegmentType.at, data={'user_id': str(user_id)}) - - @staticmethod - def at_all() -> "MessageSegment": - return MessageSegment(type=MessageSegmentType.at_all, data={'at_all': True}) - - @staticmethod - def forward_id(id_: int | str) -> "MessageSegment": - return MessageSegment(type=MessageSegmentType.forward_id, data={'id': str(id_)}) - - @staticmethod - def custom_node(user_id: int | str, nickname: str, content: str | BaseMessageSegment) -> "MessageSegment": - return MessageSegment( - type=MessageSegmentType.custom_node, - data={ - 'user_id': str(user_id), - 'nickname': str(nickname), - 'content': MessageSegment.text(content) if isinstance(content, str) else content - } - ) - - @staticmethod - def image(url: Union[str, Path]) -> "MessageSegment": - return MessageSegment( - type=MessageSegmentType.image, - data={'url': str(url.resolve()) if isinstance(url, Path) else url} - ) - - @staticmethod - def image_file(file: Path) -> "MessageSegment": - return MessageSegment( - type=MessageSegmentType.image_file, - data={'file': str(file.resolve())} - ) - - @staticmethod - def file(file: Path) -> "MessageSegment": - return MessageSegment( - type=MessageSegmentType.file, - data={'file': str(file.resolve())} - ) - - @staticmethod - def text(text: str) -> "MessageSegment": - return MessageSegment(type=MessageSegmentType.text, data={'text': text}) - - -class Message(BaseMessage[MessageSegment]): - """Omega 中间件 Message 适配。""" - - @classmethod - @override - def get_segment_class(cls) -> Type[MessageSegment]: - return MessageSegment - - def __repr__(self) -> str: - return "".join(repr(seg) for seg in self) - - @staticmethod - @override - def _construct(msg: str) -> Iterable[MessageSegment]: - yield MessageSegment.text(text=msg) - - @classmethod - def loads(cls, message_data: str) -> "Message": - """将导出的消息 json 字符串转化为 Message 对象""" - message = cls(MessageSegment(**seg) for seg in json.loads(message_data)) - return message - - def dumps(self) -> str: - """将 Message 转化为 json 字符串导出""" - message_data = json.dumps([{'type': seg.type, 'data': seg.data} for seg in self], ensure_ascii=False) - return message_data - - def extract_image_urls(self) -> list[str]: - """提取消息中的图片链接""" - return [ - segment.data['url'] - for segment in self - if (segment.type == MessageSegmentType.image.value) and ('url' in segment.data) - ] - - def filter(self, types: Iterable[str]) -> "Message": - """过滤消息段类型""" - return self.__class__(seg for seg in self.copy() if seg.type in types) - - -__all__ = [ - 'Message', - 'MessageSegment', - 'MessageSegmentType', -] diff --git a/src/service/omega_base/message/__init__.py b/src/service/omega_base/message/__init__.py new file mode 100644 index 00000000..76e8a9da --- /dev/null +++ b/src/service/omega_base/message/__init__.py @@ -0,0 +1,19 @@ +""" +@Author : Ailitonia +@Date : 2024/11/17 18:09 +@FileName : __init__ +@Project : omega-miya +@Description : Omega Internal Message +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from .message import Message, MessageSegment, MessageSegmentType +from .transfer import MessageTransferUtils + +__all__ = [ + 'Message', + 'MessageSegment', + 'MessageSegmentType', + 'MessageTransferUtils', +] diff --git a/src/service/omega_base/message/message.py b/src/service/omega_base/message/message.py new file mode 100644 index 00000000..ab7ed5bf --- /dev/null +++ b/src/service/omega_base/message/message.py @@ -0,0 +1,287 @@ +""" +@Author : Ailitonia +@Date : 2023/5/27 13:59 +@FileName : message +@Project : nonebot2_miya +@Description : Omega Internal Message +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from collections.abc import Iterable, Sequence +from enum import StrEnum, unique +from pathlib import Path +from typing import Any, Union, override + +import ujson as json +from nonebot.internal.adapter import Message as BaseMessage +from nonebot.internal.adapter import MessageSegment as BaseMessageSegment + + +@unique +class MessageSegmentType(StrEnum): + at = 'at' + at_all = 'at_all' + emoji = 'emoji' + + audio = 'audio' + file = 'file' + image = 'image' + image_file = 'image_file' + video = 'video' + voice = 'voice' + + reply = 'reply' + ref_node = 'ref_node' + custom_node = 'custom_node' + + json_hyper = 'json_hyper' + xml_hyper = 'xml_hyper' + + text = 'text' + other = 'other' + + +class MessageSegment(BaseMessageSegment['Message']): + """Omega 中间件 MessageSegment 适配。具体方法参考协议消息段类型或源码。""" + + @classmethod + @override + def get_message_class(cls) -> type['Message']: + return Message + + @override + def __str__(self) -> str: + if self.is_text(): + return self.data.get('text', '') + return '' + + def __repr__(self) -> str: + if self.is_text(): + return self.data.get('text', '') + params = ', '.join([f'{k}={v}' for k, v in self.data.items() if v is not None]) + return f'[{self.type}{":" if params else ""}{params}]' + + @override + def is_text(self) -> bool: + return self.type == MessageSegmentType.text + + @staticmethod + def at(user_id: int | str) -> 'MessageSegment': + """At 消息段, 表示一类提醒某用户的消息段类型 + + - type: at + - data_map: {user_id: str} + """ + return MessageSegment(type=MessageSegmentType.at, data={'user_id': str(user_id)}) + + @staticmethod + def at_all() -> 'MessageSegment': + """AtAll 消息段, 表示一类提醒所有人的消息段类型 + + - type: at_all + - data_map: {at_all: bool} + """ + return MessageSegment(type=MessageSegmentType.at_all, data={'at_all': True}) + + @staticmethod + def emoji(id_: str, *, name: str | None = None) -> 'MessageSegment': + """Emoji 消息段, 表示一类表情元素消息段类型 + + - type: emoji + - data_map: {id: str, name: Optional[str]} + """ + return MessageSegment(type=MessageSegmentType.emoji, data={'id': id_, 'name': name}) + + @staticmethod + def audio(url: str | Path) -> 'MessageSegment': + """Audio 消息段, 表示一类音频消息段类型 + + - type: audio + - data_map: {url: str} + """ + return MessageSegment( + type=MessageSegmentType.audio, + data={'url': url.resolve().as_posix() if isinstance(url, Path) else url} + ) + + @staticmethod + def file(file: Path) -> 'MessageSegment': + """File 消息段, 表示一类文件消息段类型 + + - type: file + - data_map: {file: str} + """ + return MessageSegment( + type=MessageSegmentType.file, + data={'file': file.resolve().as_posix()} + ) + + @staticmethod + def image(url: str | Path) -> 'MessageSegment': + """Image 消息段, 表示一类图片消息段类型 + + - type: image + - data_map: {url: str} + """ + return MessageSegment( + type=MessageSegmentType.image, + data={'url': url.resolve().as_posix() if isinstance(url, Path) else url} + ) + + @staticmethod + def image_file(file: Path) -> 'MessageSegment': + """ImageFile 消息段, 表示一类以文件发送的图片消息段类型 + + - type: image_file + - data_map: {file: str} + """ + return MessageSegment( + type=MessageSegmentType.image_file, + data={'file': file.resolve().as_posix()} + ) + + @staticmethod + def video(url: str | Path) -> 'MessageSegment': + """Video 消息段, 表示一类视频消息段类型 + + - type: video + - data_map: {url: str} + """ + return MessageSegment( + type=MessageSegmentType.video, + data={'url': url.resolve().as_posix() if isinstance(url, Path) else url} + ) + + @staticmethod + def voice(url: str | Path) -> 'MessageSegment': + """Voice 消息段, 表示一类语音消息段类型 + + - type: voice + - data_map: {url: str} + """ + return MessageSegment( + type=MessageSegmentType.voice, + data={'url': url.resolve().as_posix() if isinstance(url, Path) else url} + ) + + @staticmethod + def reply(id_: int | str) -> 'MessageSegment': + """Reply 消息段, 表示一类回复消息段类型 + + - type: reply + - data_map: {id: str} + """ + return MessageSegment(type=MessageSegmentType.reply, data={'id': str(id_)}) + + @staticmethod + def ref_node(id_: int | str) -> 'MessageSegment': + """ReferenceNode 消息段, 表示转发消息的引用消息段类型 + + - type: ref_node + - data_map: {id: str} + """ + return MessageSegment(type=MessageSegmentType.ref_node, data={'id': str(id_)}) + + @staticmethod + def custom_node( + user_id: int | str, + nickname: str, + content: Sequence[Union[str, 'MessageSegment']], + ) -> 'MessageSegment': + """CustomNode 消息段, 表示转发消息的自定义消息段类型 + + - type: custom_node + - data_map: {user_id: str, nickname: str, content: list[MessageSegment]} + """ + return MessageSegment( + type=MessageSegmentType.custom_node, + data={ + 'user_id': str(user_id), + 'nickname': str(nickname), + 'content': [MessageSegment.text(x) if isinstance(x, str) else x for x in content] + } + ) + + @staticmethod + def json_hyper(raw: str) -> 'MessageSegment': + """JSON Hyper 消息段, 表示一类以 JSON 传输的超文本消息内容, 如卡片消息、ark消息、小程序等消息段类型 + + - type: json_hyper + - data_map: {raw: str} + """ + return MessageSegment(type=MessageSegmentType.json_hyper, data={'raw': raw}) + + @staticmethod + def xml_hyper(raw: str) -> 'MessageSegment': + """XML Hyper 消息段, 表示一类以 XML 传输的超文本消息内容, 如卡片消息、ark消息、小程序等消息段类型 + + - type: xml_hyper + - data_map: {raw: str} + """ + return MessageSegment(type=MessageSegmentType.xml_hyper, data={'raw': raw}) + + @staticmethod + def text(text: str) -> 'MessageSegment': + """纯文本消息段类型 + + - type: text + - data_map: {text: str} + """ + return MessageSegment(type=MessageSegmentType.text, data={'text': text}) + + @staticmethod + def other(type_: str, data: dict[str, Any]) -> 'MessageSegment': + """其他消息段类型 + + - type: other + - data_map: {type: str, data: dict[str, Any]} + """ + return MessageSegment(type=MessageSegmentType.other, data={'type': type_, 'data': data}) + + +class Message(BaseMessage[MessageSegment]): + """Omega 中间件 Message 适配。""" + + @classmethod + @override + def get_segment_class(cls) -> type[MessageSegment]: + return MessageSegment + + def __repr__(self) -> str: + return ''.join(repr(seg) for seg in self) + + @staticmethod + @override + def _construct(msg: str) -> Iterable[MessageSegment]: + yield MessageSegment.text(text=msg) + + @classmethod + def loads(cls, message_data: str) -> 'Message': + """将导出的消息 json 字符串转化为 Message 对象""" + message = cls(MessageSegment(**seg) for seg in json.loads(message_data)) + return message + + def dumps(self) -> str: + """将 Message 转化为 json 字符串导出""" + message_data = json.dumps([{'type': seg.type, 'data': seg.data} for seg in self], ensure_ascii=False) + return message_data + + def extract_image_urls(self) -> list[str]: + """提取消息中的图片链接""" + return [ + segment.data['url'] + for segment in self + if (segment.type == MessageSegmentType.image) and ('url' in segment.data) + ] + + def filter(self, types: Iterable[str]) -> 'Message': + """过滤消息段类型""" + return self.__class__(seg for seg in self.copy() if seg.type in types) + + +__all__ = [ + 'Message', + 'MessageSegment', + 'MessageSegmentType', +] diff --git a/src/service/omega_base/message/transfer.py b/src/service/omega_base/message/transfer.py new file mode 100644 index 00000000..85e6562e --- /dev/null +++ b/src/service/omega_base/message/transfer.py @@ -0,0 +1,82 @@ +""" +@Author : Ailitonia +@Date : 2024/11/17 18:11 +@FileName : transfer +@Project : omega-miya +@Description : 消息转储工具 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from src.resource import TemporaryResource +from src.utils import OmegaRequests +from .message import Message as OmegaMessage +from .message import MessageSegment as OmegaMessageSegment + +if TYPE_CHECKING: + from nonebot.internal.adapter import Message as BaseMessage + + from src.resource import TemporaryResource + from src.service import OmegaMatcherInterface + + +@dataclass +class MessageTransferPath: + """消息转储缓存配置""" + + # 缓存文件夹 + default_save_folder: TemporaryResource = TemporaryResource('message_transfer_utils') + + def get_target_folder(self, adapter_name: str, seg_type: str) -> TemporaryResource: + return self.default_save_folder(adapter_name, seg_type) + + +_SAVE_PATH = MessageTransferPath() +"""媒体文件缓存路径""" + + +class MessageTransferUtils[TM: 'BaseMessage']: + """消息转储工具""" + + def __init__(self, interface: 'OmegaMatcherInterface', origin_message: TM): + self._adapter_name = interface.bot.adapter.get_name() + self._origin_message = origin_message + self.parsed_message: OmegaMessage = interface.get_message_extractor()(message=origin_message).message + + def _generate_resource_file(self, seg_type: str, url: str) -> 'TemporaryResource': + """生成缓存文件路径""" + target_folder = _SAVE_PATH.get_target_folder(adapter_name=self._adapter_name, seg_type=seg_type) + file_name = OmegaRequests.hash_url_file_name(url=url) + return target_folder(file_name) + + async def _download_resource_file(self, seg_type: str, url: str) -> 'TemporaryResource': + """下载缓存文件""" + target_file = self._generate_resource_file(seg_type=seg_type, url=url) + return await OmegaRequests().download(url=url, file=target_file) + + async def dump_segment_resource(self, message_segment: 'OmegaMessageSegment') -> 'OmegaMessageSegment': + match message_segment.type: + case 'audio' | 'image' | 'video' | 'voice': + if str(image_url := message_segment.data.get('url', '')).startswith(('http://', 'https://')): + target_file = await self._download_resource_file(seg_type=message_segment.type, url=image_url) + message_segment = OmegaMessageSegment.image(url=target_file.path) + case _: + pass + + return message_segment + + async def dumps(self) -> 'OmegaMessage': + new_message = OmegaMessage() + + for message_segment in self.parsed_message: + new_message.append(await self.dump_segment_resource(message_segment=message_segment)) + + return new_message + + +__all__ = [ + 'MessageTransferUtils', +] diff --git a/src/service/omega_base/middlewares/__init__.py b/src/service/omega_base/middlewares/__init__.py index 225cd1bd..72acc1a8 100644 --- a/src/service/omega_base/middlewares/__init__.py +++ b/src/service/omega_base/middlewares/__init__.py @@ -11,7 +11,6 @@ from . import platforms as platforms from .interface import OmegaEntityInterface, OmegaMatcherInterface - __all__ = [ 'OmegaEntityInterface', 'OmegaMatcherInterface', diff --git a/src/service/omega_base/middlewares/const.py b/src/service/omega_base/middlewares/const.py index fe60f642..d5c9540d 100644 --- a/src/service/omega_base/middlewares/const.py +++ b/src/service/omega_base/middlewares/const.py @@ -11,7 +11,6 @@ from src.database.internal.bot import BotType as SupportedPlatform from src.database.internal.entity import EntityType as SupportedTarget - __all__ = [ 'SupportedPlatform', 'SupportedTarget' diff --git a/src/service/omega_base/middlewares/exception.py b/src/service/omega_base/middlewares/exception.py index 3462c854..9affdeb5 100644 --- a/src/service/omega_base/middlewares/exception.py +++ b/src/service/omega_base/middlewares/exception.py @@ -8,23 +8,56 @@ @Software : PyCharm """ +from typing import final + from src.exception import PlatformException +@final class AdapterNotSupported(PlatformException): - def __init__(self, adapter_name: str) -> None: - message = f'adapter "{adapter_name}" not supported' - super().__init__(self, message) + """平台适配器不支持的方法""" + + def __init__(self, adapter_name: str, reason: str | None = None) -> None: + self.adapter_name = adapter_name + self.reason = reason + + @property + def message(self) -> str: + return f'adapter {self.adapter_name!r} not supported{"" if self.reason is None else f", {self.reason}"}' + def __repr__(self): + return f'{self.__class__.__name__}(adapter={self.adapter_name!r}, message={self.message})' + +@final class TargetNotSupported(PlatformException): - def __init__(self, target_name: str) -> None: - message = f'target "{target_name}" not supported' - super().__init__(self, message) + """平台对象不支持的方法""" + + def __init__(self, target_name: str, reason: str | None = None) -> None: + self.target_name = target_name + self.reason = reason + @property + def message(self) -> str: + return f'target {self.target_name!r} not supported{"" if self.reason is None else f", {self.reason}"}' + def __repr__(self): + return f'{self.__class__.__name__}(target={self.target_name!r}, message={self.message})' + + +@final class BotNoFound(PlatformException): - pass + """找不到 Bot 实例或 Bot 未上线""" + + def __init__(self, bot_self_id: str): + self.bot_self_id = bot_self_id + + @property + def message(self) -> str: + return f'bot {self.bot_self_id!r} not found, or bot is not online' + + def __repr__(self): + return f'{self.__class__.__name__}(self_id={self.bot_self_id!r}, message={self.message})' __all__ = [ diff --git a/src/service/omega_base/middlewares/interface.py b/src/service/omega_base/middlewares/interface.py index 6db06583..3ec35350 100644 --- a/src/service/omega_base/middlewares/interface.py +++ b/src/service/omega_base/middlewares/interface.py @@ -9,11 +9,14 @@ """ import asyncio +import inspect +from collections.abc import Callable, Coroutine from functools import wraps -from typing import TYPE_CHECKING, Annotated, Any, Callable, NoReturn, Optional, Self +from typing import TYPE_CHECKING, Annotated, Any, NoReturn, Self, cast -from nonebot.exception import PausedException, FinishedException, RejectedException -from nonebot.internal.adapter import Bot as BaseBot, Event as BaseEvent +from nonebot.exception import FinishedException, PausedException, RejectedException +from nonebot.internal.adapter import Bot as BaseBot +from nonebot.internal.adapter import Event as BaseEvent from nonebot.log import logger from nonebot.matcher import Matcher, current_bot, current_event, current_matcher from nonebot.params import Depends @@ -23,84 +26,69 @@ from .const import SupportedPlatform, SupportedTarget from .exception import AdapterNotSupported, TargetNotSupported from .platform_interface import entity_target_register, event_depend_register, message_builder_register -from .typing import EntityAcquireType, BaseSentMessageType +from .typing import BaseSentMessageType, EntityAcquireType if TYPE_CHECKING: + from pathlib import Path + + from ..internal import OmegaEntity from .platform_interface.entity_target import BaseEntityTarget from .platform_interface.event_depend import EventDepend from .platform_interface.message_builder import Builder, Extractor - from ..internal import OmegaEntity - from ..message import Message as OmegaMessage -type SentOmegaMessage = BaseSentMessageType["OmegaMessage"] +type SentOmegaMessage = BaseSentMessageType['OmegaMessage'] -class OmegaEntityInterface(object): +class OmegaEntityInterface: """Omega 基于对象 (Entity) 的统一接口, 用于在 Event/Matcher 之外调用平台 Bot 相关方法""" __slots__ = ('_entity',) - def __init__(self, entity: "OmegaEntity") -> None: + def __init__(self, entity: 'OmegaEntity') -> None: self._entity = entity - @classmethod - def depend(cls, acquire_type: EntityAcquireType = 'event') -> Callable[[BaseBot, BaseEvent, AsyncSession], Self]: - """获取注入依赖, 用于 Event/Matcher 中初始化""" - - def _depend( - bot: BaseBot, - event: BaseEvent, - session: Annotated[AsyncSession, Depends(get_db_session)] - ) -> Self: - event_depend = event_depend_register.get_depend(target_event=event)(bot=bot, event=event) - match acquire_type: - case 'event': - entity_depend = event_depend.event_entity_depend - case 'user': - entity_depend = event_depend.user_entity_depend - case _: - raise ValueError(f'Not supported entity acquire_type: {acquire_type!r}') - return cls(entity_depend(session)) - - return _depend - @staticmethod - def check_implemented(func): - """装饰一个方法, 检查该方法调用的函数/方法是否实现, 如未实现则统一抛出 TargetNotSupported 异常""" + def check_target_implemented[** P, R]( + func: Callable[P, Coroutine[Any, Any, R]], + ) -> Callable[P, Coroutine[Any, Any, R]]: + """装饰一个调用平台 API 的异步方法, 检查该方法调用的函数/方法是否实现, 如未实现则统一抛出 TargetNotSupported 异常""" + if not inspect.iscoroutinefunction(func): + raise TypeError(f'{func.__name__} is not coroutine function') @wraps(func) - def _wrapper(self: Self, *args, **kwargs): + async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: try: - return func(self, *args, **kwargs) + return await func(*args, **kwargs) except NotImplementedError: + self: Self = cast(Self, args[0]) logger.warning(f'{self._entity} not support method {func.__name__!r}') - raise TargetNotSupported(self._entity.entity_type) + raise TargetNotSupported(self._entity.entity_type, f'method {func.__name__!r} is not implemented') return _wrapper - def get_entity_target(self) -> "BaseEntityTarget": + def get_entity_target(self) -> 'BaseEntityTarget': """获取 Entity 的中间件平台 API 适配器""" entity_target_t = entity_target_register.get_target(target_name=SupportedTarget(self._entity.entity_type)) return entity_target_t(entity=self._entity) - async def get_bot(self) -> "BaseBot": + async def get_bot(self) -> 'BaseBot': """获取 Entity 对应的 Bot 实例, 未在线则会抛出 BotNoFound 异常""" return await self.get_entity_target().get_bot() - async def get_message_builder(self) -> type["Builder"]: + async def get_message_builder(self) -> type['Builder']: """获取 Entity 对应平台的消息构造器""" bot = await self.get_bot() return message_builder_register.get_builder(platform_name=SupportedPlatform(bot.adapter.get_name())) - async def get_message_extractor(self) -> type["Extractor"]: + async def get_message_extractor(self) -> type['Extractor']: """获取 Entity 对应平台的消息解析器""" bot = await self.get_bot() return message_builder_register.get_extractor(platform_name=SupportedPlatform(bot.adapter.get_name())) """在 Event/Matcher 之外向目标 Entity 直接发送消息的相关方法""" - @check_implemented - async def send_entity_message(self, message: "SentOmegaMessage", **kwargs) -> Any: + @check_target_implemented + async def send_entity_message(self, message: 'SentOmegaMessage', **kwargs) -> Any: """向 Entity 直接发送消息""" bot = await self.get_bot() message_builder = await self.get_message_builder() @@ -111,10 +99,10 @@ async def send_entity_message(self, message: "SentOmegaMessage", **kwargs) -> An bot_api_params = {send_params.message_param_name: send_message, **send_params.params} return await getattr(bot, send_params.api)(**bot_api_params) - @check_implemented + @check_target_implemented async def send_entity_message_auto_revoke( self, - message: "SentOmegaMessage", + message: 'SentOmegaMessage', revoke_interval: int = 60, **kwargs ) -> Any: @@ -131,16 +119,26 @@ async def send_entity_message_auto_revoke( """Entity 相关信息获取方法""" - @check_implemented + @check_target_implemented async def get_entity_name(self) -> str: + """获取对象名称/昵称""" return await self.get_entity_target().call_api_get_entity_name() - @check_implemented + @check_target_implemented async def get_entity_profile_image_url(self) -> str: + """获取对象头像/图标""" return await self.get_entity_target().call_api_get_entity_profile_image_url() + @check_target_implemented + async def send_entity_file(self, file: 'Path', *, file_name: str | None = None) -> None: + """向对象发送本地文件""" + if file_name is None: + file_name = file.name -class OmegaMatcherInterface(object): + return await self.get_entity_target().call_api_send_file(file_path=file.as_posix(), file_name=file_name) + + +class OmegaMatcherInterface: """Omega 基于事件 (Event) 的统一接口, 用于在 Event/Matcher 中调用平台 Bot 相关方法和进行流程交互""" __slots__ = ('bot', 'event', 'matcher', 'session', 'entity',) @@ -166,7 +164,7 @@ def get_entity( event: BaseEvent, session: AsyncSession, acquire_type: EntityAcquireType = 'event', - ) -> "OmegaEntity": + ) -> 'OmegaEntity': """获取事件对应的 Entity 对象""" event_depend = event_depend_register.get_depend(target_event=event)(bot=bot, event=event) match acquire_type: @@ -196,16 +194,36 @@ def _depend( return _depend @staticmethod - def check_implemented(func): - """装饰一个方法, 检查该方法调用的函数/方法是否实现, 如未实现则统一抛出 AdapterNotSupported 异常""" + def check_adapter_implemented[** P, R]( + func: Callable[P, Coroutine[Any, Any, R]], + ) -> Callable[P, Coroutine[Any, Any, R]]: + """装饰一个调用平台 API 的异步方法, 检查该方法调用的函数/方法是否实现, 如未实现则统一抛出 AdapterNotSupported 异常""" + if not inspect.iscoroutinefunction(func): + raise TypeError(f'{func.__name__} is not coroutine function') @wraps(func) - def _wrapper(self: Self, *args, **kwargs): + async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: try: - return func(self, *args, **kwargs) + return await func(*args, **kwargs) except NotImplementedError: + self: Self = cast(Self, args[0]) logger.warning(f'{self.bot}/{self.event} not support method {func.__name__!r}') - raise AdapterNotSupported(self.bot.adapter.get_name()) + raise AdapterNotSupported(self.bot.adapter.get_name(), f'method {func.__name__!r} is not implemented') + + return _wrapper + + @staticmethod + def check_event_implemented[** P, R](func: Callable[P, R]) -> Callable[P, R]: + """装饰一个事件依赖的同步方法, 检查该方法调用的函数/方法是否实现, 如未实现则统一抛出 AdapterNotSupported 异常""" + + @wraps(func) + def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + try: + return func(*args, **kwargs) + except NotImplementedError: + self: Self = cast(Self, args[0]) + logger.warning(f'{self.bot}/{self.event} not support method {func.__name__!r}') + raise AdapterNotSupported(self.bot.adapter.get_name(), f'method {func.__name__!r} is not implemented') return _wrapper @@ -213,14 +231,14 @@ def get_entity_interface(self) -> OmegaEntityInterface: """获取事件 Entity 的 OmegaEntityInterface 实例""" return OmegaEntityInterface(entity=self.entity) - def get_event_depend(self) -> "EventDepend": + def get_event_depend(self) -> 'EventDepend': return event_depend_register.get_depend(target_event=self.event)(bot=self.bot, event=self.event) - def get_message_builder(self) -> type["Builder"]: + def get_message_builder(self) -> type['Builder']: """获取 Bot 对应平台的消息构造器""" return self.get_event_depend().get_omega_message_builder() - def get_message_extractor(self) -> type["Extractor"]: + def get_message_extractor(self) -> type['Extractor']: """获取 Bot 对应平台的消息解析器""" return self.get_event_depend().get_omega_message_extractor() @@ -231,44 +249,44 @@ def refresh_interface_state(self) -> None: """平台事件信息提取相关方法""" - @check_implemented + @check_event_implemented def get_event_user_nickname(self) -> str: """获取当前事件用户昵称""" return self.get_event_depend().get_user_nickname() - @check_implemented + @check_event_implemented def get_event_msg_image_urls(self) -> list[str]: """获取当前事件消息中的全部图片链接""" return self.get_event_depend().get_msg_image_urls() - @check_implemented + @check_event_implemented def get_event_reply_msg_image_urls(self) -> list[str]: """获取当前事件回复消息中的全部图片链接""" return self.get_event_depend().get_reply_msg_image_urls() - @check_implemented - def get_event_reply_msg_plain_text(self) -> Optional[str]: + @check_event_implemented + def get_event_reply_msg_plain_text(self) -> str | None: """获取当前事件回复消息的文本""" return self.get_event_depend().get_reply_msg_plain_text() """Matcher 及流程控制相关方法""" - @check_implemented - async def send(self, message: "SentOmegaMessage", **kwargs) -> Any: + @check_adapter_implemented + async def send(self, message: 'SentOmegaMessage', **kwargs) -> Any: return await self.get_event_depend().send(message=message, **kwargs) - @check_implemented - async def send_at_sender(self, message: "SentOmegaMessage", **kwargs) -> Any: + @check_adapter_implemented + async def send_at_sender(self, message: 'SentOmegaMessage', **kwargs) -> Any: return await self.get_event_depend().send_at_sender(message=message, **kwargs) - @check_implemented - async def send_reply(self, message: "SentOmegaMessage", **kwargs) -> Any: + @check_adapter_implemented + async def send_reply(self, message: 'SentOmegaMessage', **kwargs) -> Any: return await self.get_event_depend().send_reply(message=message, **kwargs) - @check_implemented + @check_adapter_implemented async def send_auto_revoke( self, - message: "SentOmegaMessage", + message: 'SentOmegaMessage', revoke_interval: int = 60, **revoke_kwargs ) -> asyncio.TimerHandle: @@ -281,10 +299,10 @@ async def send_auto_revoke( lambda: loop.create_task(self.get_event_depend().revoke(sent_return=sent_return, **revoke_kwargs)), ) - @check_implemented + @check_adapter_implemented async def send_reply_auto_revoke( self, - message: "SentOmegaMessage", + message: 'SentOmegaMessage', revoke_interval: int = 60, **revoke_kwargs ) -> asyncio.TimerHandle: @@ -297,84 +315,78 @@ async def send_reply_auto_revoke( lambda: loop.create_task(self.get_event_depend().revoke(sent_return=sent_return, **revoke_kwargs)), ) - @check_implemented - def send_background(self, message: "SentOmegaMessage", **kwargs) -> asyncio.Task[Any]: - """立即发送消息 (一般放在 IO 调用之前)""" - loop = asyncio.get_running_loop() - return loop.create_task(self.send(message=message, **kwargs)) - - @check_implemented - async def finish(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def finish(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send(message=message, **kwargs) raise FinishedException - @check_implemented - async def finish_at_sender(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def finish_at_sender(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_at_sender(message=message, **kwargs) raise FinishedException - @check_implemented - async def finish_reply(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def finish_reply(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_reply(message=message, **kwargs) raise FinishedException - @check_implemented - async def pause(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def pause(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send(message=message, **kwargs) raise PausedException - @check_implemented - async def pause_at_sender(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def pause_at_sender(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_at_sender(message=message, **kwargs) raise PausedException - @check_implemented - async def pause_reply(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def pause_reply(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_reply(message=message, **kwargs) raise PausedException - @check_implemented - async def reject(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send(message=message, **kwargs) raise RejectedException - @check_implemented - async def reject_at_sender(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_at_sender(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_at_sender(message=message, **kwargs) raise RejectedException - @check_implemented - async def reject_reply(self, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_reply(self, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_reply(message=message, **kwargs) raise RejectedException - @check_implemented - async def reject_arg(self, key: str, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_arg(self, key: str, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send(message=message, **kwargs) await self.matcher.reject_arg(key) - @check_implemented - async def reject_arg_at_sender(self, key: str, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_arg_at_sender(self, key: str, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_at_sender(message=message, **kwargs) await self.matcher.reject_arg(key) - @check_implemented - async def reject_arg_reply(self, key: str, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_arg_reply(self, key: str, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_reply(message=message, **kwargs) await self.matcher.reject_arg(key) - @check_implemented - async def reject_receive(self, key: str, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_receive(self, key: str, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send(message=message, **kwargs) await self.matcher.reject_receive(key) - @check_implemented - async def reject_receive_at_sender(self, key: str, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_receive_at_sender(self, key: str, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_at_sender(message=message, **kwargs) await self.matcher.reject_receive(key) - @check_implemented - async def reject_receive_reply(self, key: str, message: "SentOmegaMessage", **kwargs) -> NoReturn: + @check_adapter_implemented + async def reject_receive_reply(self, key: str, message: 'SentOmegaMessage', **kwargs) -> NoReturn: await self.send_reply(message=message, **kwargs) await self.matcher.reject_receive(key) diff --git a/src/service/omega_base/middlewares/models.py b/src/service/omega_base/middlewares/models.py index 2cbfa445..d12818cb 100644 --- a/src/service/omega_base/middlewares/models.py +++ b/src/service/omega_base/middlewares/models.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from typing import Any, Optional +from typing import Any from pydantic import BaseModel, ConfigDict @@ -25,8 +25,8 @@ class EntityInitParams(BaseMiddlewareModel): entity_type: str entity_id: str parent_id: str - entity_name: Optional[str] = None - entity_info: Optional[str] = None + entity_name: str | None = None + entity_info: str | None = None @property def kwargs(self) -> dict[str, Any]: diff --git a/src/service/omega_base/middlewares/platform_interface/entity_target.py b/src/service/omega_base/middlewares/platform_interface/entity_target.py index ad55de39..14ae8dfa 100644 --- a/src/service/omega_base/middlewares/platform_interface/entity_target.py +++ b/src/service/omega_base/middlewares/platform_interface/entity_target.py @@ -9,8 +9,9 @@ """ import abc -from dataclasses import field, dataclass -from typing import TYPE_CHECKING, Any, Callable +from collections.abc import Callable +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any from nonebot.log import logger @@ -20,33 +21,34 @@ if TYPE_CHECKING: from nonebot.internal.adapter import Bot as BaseBot - from ..models import EntityTargetSendParams, EntityTargetRevokeParams + from ...internal import OmegaEntity + from ..models import EntityTargetRevokeParams, EntityTargetSendParams class BaseEntityTarget(abc.ABC): """中间件平台 API 适配器: 平台 API 及 Entity 方法适配工具基类""" - def __init__(self, entity: "OmegaEntity") -> None: + def __init__(self, entity: 'OmegaEntity') -> None: self.entity = entity - async def get_bot(self) -> "BaseBot": + async def get_bot(self) -> 'BaseBot': """获取 Entity 对应 Bot 实例""" bot_self = await self.entity.query_bot_self() bot = get_online_bots().get(bot_self.bot_type, {}).get(bot_self.self_id) if not bot: - raise BotNoFound(f'{bot_self} not online') + raise BotNoFound(bot_self.self_id) return bot """平台发送消息 API 调用适配""" @abc.abstractmethod - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': """获取向 Entity 发送消息调用的 API 名称及参数""" raise NotImplementedError @abc.abstractmethod - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': """获取撤回已发送消息调用的 API 名称及参数""" raise NotImplementedError @@ -62,6 +64,11 @@ async def call_api_get_entity_profile_image_url(self) -> str: """调用平台 API: 获取对象头像/图标""" raise NotImplementedError + @abc.abstractmethod + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + """调用平台 API: 发送本地文件""" + raise NotImplementedError + @dataclass class EntityTargetRegister: diff --git a/src/service/omega_base/middlewares/platform_interface/event_depend.py b/src/service/omega_base/middlewares/platform_interface/event_depend.py index 11ad07ec..db5a253d 100644 --- a/src/service/omega_base/middlewares/platform_interface/event_depend.py +++ b/src/service/omega_base/middlewares/platform_interface/event_depend.py @@ -9,8 +9,9 @@ """ import abc -from dataclasses import field, dataclass -from typing import TYPE_CHECKING, Annotated, Any, Callable, Optional +from collections.abc import Callable +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Annotated, Any from nonebot.internal.adapter import Event as BaseEvent from nonebot.log import logger @@ -22,13 +23,14 @@ if TYPE_CHECKING: from nonebot.internal.adapter import Bot as BaseBot from sqlalchemy.ext.asyncio import AsyncSession - from .message_builder import BaseMessageBuilder + + from ...message import Message as OmegaMessage from ..models import EntityInitParams from ..typing import BaseMessageType, BaseSentMessageType - from ...message import Message as OmegaMessage + from .message_builder import BaseMessageBuilder -class BaseEventDepend[Bot_T: "BaseBot", Event_T: "BaseEvent", Message_T: "BaseMessageType[Any]"](abc.ABC): +class BaseEventDepend[Bot_T: 'BaseBot', Event_T: 'BaseEvent', Message_T: 'BaseMessageType[Any]'](abc.ABC): """中间件事件对象解析器: 平台事件及对象依赖适配基类""" def __init__(self, bot: Bot_T, event: Event_T) -> None: @@ -38,29 +40,29 @@ def __init__(self, bot: Bot_T, event: Event_T) -> None: """事件 Entity 对象依赖提取方法适配""" @abc.abstractmethod - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': """根据 Event 提取事件本身对应 Entity 实例化参数""" raise NotImplementedError @abc.abstractmethod - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': """根据 Event 提取触发事件用户 Entity 实例化参数""" raise NotImplementedError @property - def event_entity_depend(self) -> Callable[["AsyncSession"], OmegaEntity]: + def event_entity_depend(self) -> Callable[['AsyncSession'], OmegaEntity]: """获取事件本身对应 Entity 依赖""" - def _depend(session: Annotated["AsyncSession", Depends(get_db_session)]) -> OmegaEntity: + def _depend(session: Annotated['AsyncSession', Depends(get_db_session)]) -> OmegaEntity: return OmegaEntity(session=session, **self._extract_event_entity_params().kwargs) return _depend @property - def user_entity_depend(self) -> Callable[["AsyncSession"], OmegaEntity]: + def user_entity_depend(self) -> Callable[['AsyncSession'], OmegaEntity]: """获取触发事件用户 Entity 依赖""" - def _depend(session: Annotated["AsyncSession", Depends(get_db_session)]) -> OmegaEntity: + def _depend(session: Annotated['AsyncSession', Depends(get_db_session)]) -> OmegaEntity: return OmegaEntity(session=session, **self._extract_user_entity_params().kwargs) return _depend @@ -68,27 +70,27 @@ def _depend(session: Annotated["AsyncSession", Depends(get_db_session)]) -> Omeg """平台事件消息交互及流程处理方法适配""" @abc.abstractmethod - def get_omega_message_builder(self) -> type["BaseMessageBuilder[OmegaMessage, Message_T]"]: + def get_omega_message_builder(self) -> type['BaseMessageBuilder[OmegaMessage, Message_T]']: raise NotImplementedError @abc.abstractmethod - def get_omega_message_extractor(self) -> type["BaseMessageBuilder[Message_T, OmegaMessage]"]: + def get_omega_message_extractor(self) -> type['BaseMessageBuilder[Message_T, OmegaMessage]']: raise NotImplementedError - def build_platform_message(self, message: "BaseSentMessageType[OmegaMessage]") -> Message_T: + def build_platform_message(self, message: 'BaseSentMessageType[OmegaMessage]') -> Message_T: return self.get_omega_message_builder()(message=message).message - async def send(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: """发送消息""" return await self.bot.send(event=self.event, message=self.build_platform_message(message=message), **kwargs) @abc.abstractmethod - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: """发送消息并 @Sender""" raise NotImplementedError @abc.abstractmethod - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: """发送消息作为另一条消息的回复""" raise NotImplementedError @@ -120,22 +122,22 @@ def get_reply_msg_image_urls(self) -> list[str]: raise NotImplementedError @abc.abstractmethod - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: """获取回复消息的文本""" raise NotImplementedError -type EventDepend[B_T: "BaseBot", E_T: "BaseEvent", M_T: "BaseMessageType[Any]"] = BaseEventDepend[B_T, E_T, M_T] +type EventDepend[B_T: 'BaseBot', E_T: 'BaseEvent', M_T: 'BaseMessageType[Any]'] = BaseEventDepend[B_T, E_T, M_T] @dataclass class EventDependRegister: """中间件事件对象解析器的注册工具, 用于引入平台适配""" - _map: dict[type["BaseEvent"], type[EventDepend]] = field(default_factory=dict) + _map: dict[type['BaseEvent'], type[EventDepend]] = field(default_factory=dict) def register_depend[Depend_T: type[EventDepend]]( - self, target_event_type: type["BaseEvent"] + self, target_event_type: type['BaseEvent'] ) -> Callable[[Depend_T], Depend_T]: """注册对应事件的事件对象解析器""" @@ -150,7 +152,7 @@ def _decorator(depend: Depend_T) -> Depend_T: return _decorator - def get_depend(self, target_event: "BaseEvent") -> type[EventDepend]: + def get_depend(self, target_event: 'BaseEvent') -> type[EventDepend]: """从事件中提取对应的事件对象解析器""" for event_type in target_event.__class__.mro(): if event_type in self._map.keys(): diff --git a/src/service/omega_base/middlewares/platform_interface/message_builder.py b/src/service/omega_base/middlewares/platform_interface/message_builder.py index bfe095e0..9ee828c7 100644 --- a/src/service/omega_base/middlewares/platform_interface/message_builder.py +++ b/src/service/omega_base/middlewares/platform_interface/message_builder.py @@ -9,18 +9,21 @@ """ import abc +from collections.abc import Callable, Generator from copy import deepcopy -from dataclasses import field, dataclass -from typing import TYPE_CHECKING, Any, Callable, Generator +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any from nonebot.log import logger from ..const import SupportedPlatform from ..exception import AdapterNotSupported -from ..typing import BaseMessageType, BaseMessageSegType, BaseSentMessageType +from ..typing import BaseMessageSegType, BaseMessageType, BaseSentMessageType if TYPE_CHECKING: - from nonebot.internal.adapter import Message as BaseMessage, MessageSegment as BaseMessageSegment + from nonebot.internal.adapter import Message as BaseMessage + from nonebot.internal.adapter import MessageSegment as BaseMessageSegment + from ...message import Message as OmegaMessage @@ -98,10 +101,10 @@ def _build(cls, message: SourceSentMessage_T) -> TargetMessage_T: return target_message_type() -type Builder[TargetMessage_T: BaseMessageType[Any]] = BaseMessageBuilder["OmegaMessage", TargetMessage_T] +type Builder[TargetMessage_T: BaseMessageType[Any]] = BaseMessageBuilder['OmegaMessage', TargetMessage_T] """平台消息构造器, 从 Omega 中间件消息构建平台消息""" -type Extractor[SourceMessage_T: BaseMessageType[Any]] = BaseMessageBuilder[SourceMessage_T, "OmegaMessage"] +type Extractor[SourceMessage_T: BaseMessageType[Any]] = BaseMessageBuilder[SourceMessage_T, 'OmegaMessage'] """平台消息解析器, 将平台消息转换为 Omega 中间件消息""" @@ -146,7 +149,7 @@ def _decorator(extractor: T) -> T: return _decorator - def get_builder(self, platform_name: SupportedPlatform) -> type[Builder["BaseMessage[BaseMessageSegment]"]]: + def get_builder(self, platform_name: SupportedPlatform) -> type[Builder['BaseMessage[BaseMessageSegment]']]: """从适配器或事件中提取对应的适配器工具 target""" if platform_name not in SupportedPlatform.get_supported_adapter_names(): raise AdapterNotSupported(adapter_name=platform_name) @@ -157,7 +160,7 @@ def get_builder(self, platform_name: SupportedPlatform) -> type[Builder["BaseMes return self._builder_map[platform_name] - def get_extractor(self, platform_name: SupportedPlatform) -> type[Extractor["BaseMessage[BaseMessageSegment]"]]: + def get_extractor(self, platform_name: SupportedPlatform) -> type[Extractor['BaseMessage[BaseMessageSegment]']]: """从适配器或事件中提取对应的适配器工具 target""" if platform_name not in SupportedPlatform.get_supported_adapter_names(): raise AdapterNotSupported(adapter_name=platform_name) diff --git a/src/service/omega_base/middlewares/platforms/__init__.py b/src/service/omega_base/middlewares/platforms/__init__.py index d375aa43..ffd97c0a 100644 --- a/src/service/omega_base/middlewares/platforms/__init__.py +++ b/src/service/omega_base/middlewares/platforms/__init__.py @@ -13,5 +13,4 @@ from . import qq as qq from . import telegram as telegram - __all__ = [] diff --git a/src/service/omega_base/middlewares/platforms/console.py b/src/service/omega_base/middlewares/platforms/console.py index 458aab16..fbb7197a 100644 --- a/src/service/omega_base/middlewares/platforms/console.py +++ b/src/service/omega_base/middlewares/platforms/console.py @@ -8,27 +8,23 @@ @Software : PyCharm """ -from typing import Any, Optional +from typing import Any -from nonebot.adapters.console import ( - Bot as ConsoleBot, - Message as ConsoleMessage, - MessageSegment as ConsoleMessageSegment, - Event as ConsoleEvent, - MessageEvent as ConsoleMessageEvent -) +from nonebot.adapters.console import Bot as ConsoleBot +from nonebot.adapters.console import Event as ConsoleEvent +from nonebot.adapters.console import Message as ConsoleMessage +from nonebot.adapters.console import MessageEvent as ConsoleMessageEvent +from nonebot.adapters.console import MessageSegment as ConsoleMessageSegment from ..const import SupportedPlatform, SupportedTarget -from ..models import EntityInitParams, EntityTargetSendParams, EntityTargetRevokeParams +from ..models import EntityInitParams, EntityTargetRevokeParams, EntityTargetSendParams from ..platform_interface.entity_target import BaseEntityTarget, entity_target_register from ..platform_interface.event_depend import BaseEventDepend, event_depend_register from ..platform_interface.message_builder import BaseMessageBuilder, message_builder_register from ..typing import BaseSentMessageType -from ...message import ( - MessageSegmentType, - Message as OmegaMessage, - MessageSegment as OmegaMessageSegment -) +from ...message import Message as OmegaMessage +from ...message import MessageSegment as OmegaMessageSegment +from ...message import MessageSegmentType @message_builder_register.register_builder(SupportedPlatform.console) @@ -45,7 +41,7 @@ def _get_target_base_segment_type() -> type[ConsoleMessageSegment]: @staticmethod def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> ConsoleMessageSegment: match seg_type: - case MessageSegmentType.text.value: + case MessageSegmentType.text: return ConsoleMessageSegment.text(text=seg_data.get('text', '')) case _: return ConsoleMessageSegment.text(text='') @@ -70,13 +66,13 @@ def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> Omeg case 'text': return OmegaMessageSegment.text(text=seg_data.get('text', '')) case _: - return OmegaMessageSegment.text(text='') + return OmegaMessageSegment.other(type_=seg_type, data=seg_data) @entity_target_register.register_target(SupportedTarget.console_user) class ConsoleEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': return EntityTargetSendParams( api='send_msg', message_param_name='message', @@ -85,7 +81,7 @@ def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": } ) - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError async def call_api_get_entity_name(self) -> str: @@ -94,30 +90,33 @@ async def call_api_get_entity_name(self) -> str: async def call_api_get_entity_profile_image_url(self) -> str: return '' + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError + @event_depend_register.register_depend(ConsoleEvent) class ConsoleEventDepend[Event_T: ConsoleEvent](BaseEventDepend[ConsoleBot, Event_T, ConsoleMessage]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return self._extract_user_entity_params() - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='console_user', entity_id=self.event.user.id, parent_id=self.bot.self_id, entity_name=self.event.user.nickname, entity_info=self.event.user.avatar ) - def get_omega_message_builder(self) -> type["BaseMessageBuilder[OmegaMessage, ConsoleMessage]"]: + def get_omega_message_builder(self) -> type['BaseMessageBuilder[OmegaMessage, ConsoleMessage]']: return ConsoleMessageBuilder - def get_omega_message_extractor(self) -> type["BaseMessageBuilder[ConsoleMessage, OmegaMessage]"]: + def get_omega_message_extractor(self) -> type['BaseMessageBuilder[ConsoleMessage, OmegaMessage]']: return ConsoleMessageExtractor - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError async def revoke(self, sent_return: Any, **kwargs) -> Any: @@ -132,17 +131,17 @@ def get_msg_image_urls(self) -> list[str]: def get_reply_msg_image_urls(self) -> list[str]: raise NotImplementedError - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: raise NotImplementedError @event_depend_register.register_depend(ConsoleMessageEvent) class ConsoleMessageEventDepend(ConsoleEventDepend[ConsoleMessageEvent]): - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: return await self.send(message=message, **kwargs) - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: return await self.send(message=message, **kwargs) def get_user_nickname(self) -> str: @@ -154,7 +153,7 @@ def get_msg_image_urls(self) -> list[str]: def get_reply_msg_image_urls(self) -> list[str]: return [] - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: return None diff --git a/src/service/omega_base/middlewares/platforms/onebot_v11.py b/src/service/omega_base/middlewares/platforms/onebot_v11.py index 364bcb8e..445cbdaa 100644 --- a/src/service/omega_base/middlewares/platforms/onebot_v11.py +++ b/src/service/omega_base/middlewares/platforms/onebot_v11.py @@ -9,33 +9,29 @@ """ from pathlib import Path -from typing import Any, Optional +from typing import Any from urllib.parse import urlparse -from nonebot.adapters.onebot.v11 import ( - Bot as OneBotV11Bot, - Message as OneBotV11Message, - MessageSegment as OneBotV11MessageSegment, - Event as OneBotV11Event, - MessageEvent as OneBotV11MessageEvent, - GroupMessageEvent as OneBotV11GroupMessageEvent, - PrivateMessageEvent as OneBotV11PrivateMessageEvent, -) - -from src.service.gocqhttp_guild_patch import ( - GuildMessageEvent as OneBotV11GuildMessageEvent -) +from nonebot.adapters.onebot.v11 import Bot as OneBotV11Bot +from nonebot.adapters.onebot.v11 import Event as OneBotV11Event +from nonebot.adapters.onebot.v11 import GroupMessageEvent as OneBotV11GroupMessageEvent +from nonebot.adapters.onebot.v11 import Message as OneBotV11Message +from nonebot.adapters.onebot.v11 import MessageEvent as OneBotV11MessageEvent +from nonebot.adapters.onebot.v11 import MessageSegment as OneBotV11MessageSegment +from nonebot.adapters.onebot.v11 import NoticeEvent as OneBotV11NoticeEvent +from nonebot.adapters.onebot.v11 import NotifyEvent as OneBotV11NotifyEvent +from nonebot.adapters.onebot.v11 import PokeNotifyEvent as OneBotV11PokeNotifyEvent +from nonebot.adapters.onebot.v11 import PrivateMessageEvent as OneBotV11PrivateMessageEvent + from ..const import SupportedPlatform, SupportedTarget -from ..models import EntityInitParams, EntityTargetSendParams, EntityTargetRevokeParams +from ..models import EntityInitParams, EntityTargetRevokeParams, EntityTargetSendParams from ..platform_interface.entity_target import BaseEntityTarget, entity_target_register from ..platform_interface.event_depend import BaseEventDepend, event_depend_register from ..platform_interface.message_builder import BaseMessageBuilder, message_builder_register from ..typing import BaseSentMessageType -from ...message import ( - MessageSegmentType, - Message as OmegaMessage, - MessageSegment as OmegaMessageSegment -) +from ...message import Message as OmegaMessage +from ...message import MessageSegment as OmegaMessageSegment +from ...message import MessageSegmentType @message_builder_register.register_builder(SupportedPlatform.onebot_v11) @@ -52,26 +48,29 @@ def _get_target_base_segment_type() -> type[OneBotV11MessageSegment]: @staticmethod def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> OneBotV11MessageSegment: match seg_type: - case MessageSegmentType.at.value: + case MessageSegmentType.at: return OneBotV11MessageSegment.at(user_id=seg_data.get('user_id', 0)) - case MessageSegmentType.at_all.value: + case MessageSegmentType.at_all: return OneBotV11MessageSegment.at(user_id='all') - case MessageSegmentType.forward_id.value: + case MessageSegmentType.emoji: + return OneBotV11MessageSegment.face(id_=seg_data.get('id', 0)) + case MessageSegmentType.audio | MessageSegmentType.voice: + return OneBotV11MessageSegment.record(file=_parse_url_to_path(str(seg_data.get('url', '')))) + case MessageSegmentType.video: + return OneBotV11MessageSegment.video(file=_parse_url_to_path(str(seg_data.get('url', '')))) + case MessageSegmentType.image: + return OneBotV11MessageSegment.image(file=_parse_url_to_path(str(seg_data.get('url', '')))) + case MessageSegmentType.image_file: + return OneBotV11MessageSegment.image(file=Path(seg_data.get('file', ''))) + case MessageSegmentType.ref_node: return OneBotV11MessageSegment.node(id_=seg_data.get('id', 0)) - case MessageSegmentType.custom_node.value: + case MessageSegmentType.custom_node: return OneBotV11MessageSegment.node_custom( user_id=seg_data.get('user_id', 0), nickname=seg_data.get('nickname', ''), - content=seg_data.get('content', '') + content=OneBotV11Message(seg_data.get('content', [])) ) - case MessageSegmentType.image.value: - url = str(seg_data.get('url', '')) - if urlparse(url).scheme not in ['http', 'https']: - url = Path(url) - return OneBotV11MessageSegment.image(file=url) - case MessageSegmentType.image_file.value: - return OneBotV11MessageSegment.image(file=Path(seg_data.get('file', ''))) - case MessageSegmentType.text.value: + case MessageSegmentType.text: return OneBotV11MessageSegment.text(text=seg_data.get('text', '')) case _: return OneBotV11MessageSegment.text(text='') @@ -92,30 +91,33 @@ def _get_target_base_segment_type() -> type[OmegaMessageSegment]: def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> OmegaMessageSegment: match seg_type: case 'at': - return OmegaMessageSegment.at(user_id=seg_data.get('qq', 0)) - case 'forward': - return OmegaMessageSegment.forward_id(id_=seg_data.get('id', 0)) - case 'node': - return OmegaMessageSegment.custom_node( - user_id=seg_data.get('user_id', 0), - nickname=seg_data.get('nickname', ''), - content=seg_data.get('content', '') - ) + user_id = seg_data.get('qq', 0) + if user_id == 'all': + return OmegaMessageSegment.at_all() + return OmegaMessageSegment.at(user_id=user_id) + case 'face': + return OmegaMessageSegment.emoji(id_=seg_data.get('id', '0')) + case 'record': + url = str(seg_data.get('url', '') if seg_data.get('url') else seg_data.get('file', '')) + return OmegaMessageSegment.audio(url=_parse_url_to_path(url)) + case 'video': + url = str(seg_data.get('url', '') if seg_data.get('url') else seg_data.get('file', '')) + return OmegaMessageSegment.video(url=_parse_url_to_path(url)) case 'image': url = str(seg_data.get('url', '') if seg_data.get('url') else seg_data.get('file', '')) - if urlparse(url).scheme not in ['http', 'https']: - url = Path(url) - return OmegaMessageSegment.image(url=url) + return OmegaMessageSegment.image(url=_parse_url_to_path(url)) + case 'forward' | 'node': + return OmegaMessageSegment.ref_node(id_=seg_data.get('id', 0)) case 'text': return OmegaMessageSegment.text(text=seg_data.get('text', '')) case _: - return OmegaMessageSegment.text(text='') + return OmegaMessageSegment.other(type_=seg_type, data=seg_data) @entity_target_register.register_target(SupportedTarget.onebot_v11_user) class OneBotV11UserEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': return EntityTargetSendParams( api='send_private_msg', message_param_name='message', @@ -124,7 +126,7 @@ def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": } ) - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': return EntityTargetRevokeParams(api='delete_msg', params={'message_id': sent_return['message_id']}) async def call_api_get_entity_name(self) -> str: @@ -147,11 +149,15 @@ async def call_api_get_entity_profile_image_url(self) -> str: url = f'https://q1.qlogo.cn/g?b=qq&nk={self.entity.entity_id}&s={head_img_size}' return url + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + bot = await self.get_bot() + await bot.call_api('upload_private_file', user_id=self.entity.entity_id, file=file_path, name=file_name) + @entity_target_register.register_target(SupportedTarget.onebot_v11_group) class OneBotV11GroupEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': return EntityTargetSendParams( api='send_group_msg', message_param_name='message', @@ -160,7 +166,7 @@ def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": } ) - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': return EntityTargetRevokeParams(api='delete_msg', params={'message_id': sent_return['message_id']}) async def call_api_get_entity_name(self) -> str: @@ -174,14 +180,18 @@ async def call_api_get_entity_profile_image_url(self) -> str: head_img_size = 640 return f'https://p.qlogo.cn/gh/{self.entity.entity_id}/{self.entity.entity_id}/{head_img_size}/' + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + bot = await self.get_bot() + await bot.call_api('upload_group_file', group_id=self.entity.entity_id, file=file_path, name=file_name) + @entity_target_register.register_target(SupportedTarget.onebot_v11_guild) class OneBotV11GuildEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': raise NotImplementedError # 非标准 API, 协议端未实现 - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError # 非标准 API, 协议端未实现 async def call_api_get_entity_name(self) -> str: @@ -193,11 +203,14 @@ async def call_api_get_entity_name(self) -> str: async def call_api_get_entity_profile_image_url(self) -> str: raise NotImplementedError # 非标准 API, 协议端未实现 + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError # 非标准 API, 协议端未实现 + @entity_target_register.register_target(SupportedTarget.onebot_v11_guild_channel) class OneBotV11GuildChannelEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': return EntityTargetSendParams( api='send_guild_channel_msg', message_param_name='message', @@ -207,7 +220,7 @@ def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": } ) - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError # 非标准 API, 协议端未实现 async def call_api_get_entity_name(self) -> str: @@ -216,14 +229,17 @@ async def call_api_get_entity_name(self) -> str: async def call_api_get_entity_profile_image_url(self) -> str: raise NotImplementedError # 非标准 API, 协议端未实现 + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError # 非标准 API, 协议端未实现 + @entity_target_register.register_target(SupportedTarget.onebot_v11_guild_user) class OneBotV11GuildUserEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': raise NotImplementedError # 非标准 API, 协议端未实现 - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError # 非标准 API, 协议端未实现 async def call_api_get_entity_name(self) -> str: @@ -242,35 +258,45 @@ async def call_api_get_entity_profile_image_url(self) -> str: avatar_url = guild_user_data.get('avatar_url', '') return str(avatar_url) + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError # 非标准 API, 协议端未实现 + @event_depend_register.register_depend(OneBotV11Event) class OneBotV11EventDepend[Event_T: OneBotV11Event](BaseEventDepend[OneBotV11Bot, Event_T, OneBotV11Message]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': + if group_id := getattr(self.event, 'group_id', None): + return EntityInitParams( + bot_id=self.bot.self_id, + entity_type='onebot_v11_group', + entity_id=str(group_id), + parent_id=self.bot.self_id + ) + return self._extract_user_entity_params() - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': bot_self_id = self.bot.self_id + entity_type = 'onebot_v11_user' if user_id := getattr(self.event, 'user_id', None): - entity_type = 'onebot_v11_user' entity_id = str(user_id) else: - entity_type = 'onebot_v11_user' entity_id = bot_self_id return EntityInitParams(bot_id=bot_self_id, entity_type=entity_type, entity_id=entity_id, parent_id=bot_self_id) - def get_omega_message_builder(self) -> type["BaseMessageBuilder[OmegaMessage, OneBotV11Message]"]: + def get_omega_message_builder(self) -> type['BaseMessageBuilder[OmegaMessage, OneBotV11Message]']: return OneBotV11MessageBuilder - def get_omega_message_extractor(self) -> type["BaseMessageBuilder[OneBotV11Message, OmegaMessage]"]: + def get_omega_message_extractor(self) -> type['BaseMessageBuilder[OneBotV11Message, OmegaMessage]']: return OneBotV11MessageExtractor - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError async def revoke(self, sent_return: Any, **kwargs) -> Any: @@ -285,17 +311,61 @@ def get_msg_image_urls(self) -> list[str]: def get_reply_msg_image_urls(self) -> list[str]: raise NotImplementedError - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: raise NotImplementedError +@event_depend_register.register_depend(OneBotV11NoticeEvent) +class OneBotV11NoticeEventDepend[Event_T: OneBotV11NoticeEvent](OneBotV11EventDepend[Event_T]): + + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: + return await self.send(message=message, at_sender=True, **kwargs) + + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: + return await self.send(message=message, reply_message=True, **kwargs) + + +@event_depend_register.register_depend(OneBotV11NotifyEvent) +class OneBotV11NotifyEventDepend[Event_T: OneBotV11NotifyEvent](OneBotV11NoticeEventDepend[Event_T]): + + def _extract_event_entity_params(self) -> 'EntityInitParams': + return EntityInitParams( + bot_id=self.bot.self_id, + entity_type='onebot_v11_group', + entity_id=str(self.event.group_id), + parent_id=self.bot.self_id + ) + + def _extract_user_entity_params(self) -> 'EntityInitParams': + return EntityInitParams( + bot_id=self.bot.self_id, + entity_type='onebot_v11_user', + entity_id=str(self.event.user_id), + parent_id=self.bot.self_id, + ) + + +@event_depend_register.register_depend(OneBotV11PokeNotifyEvent) +class OneBotV11PokeNotifyEventDepend(OneBotV11NotifyEventDepend[OneBotV11PokeNotifyEvent]): + def _extract_event_entity_params(self) -> 'EntityInitParams': + if self.event.group_id is None: + return self._extract_user_entity_params() + + return EntityInitParams( + bot_id=self.bot.self_id, + entity_type='onebot_v11_group', + entity_id=str(self.event.group_id), + parent_id=self.bot.self_id + ) + + @event_depend_register.register_depend(OneBotV11MessageEvent) class OneBotV11MessageEventDepend[Event_T: OneBotV11MessageEvent](OneBotV11EventDepend[Event_T]): - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: return await self.send(message=message, at_sender=True, **kwargs) - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: return await self.send(message=message, reply_message=True, **kwargs) async def revoke(self, sent_return: Any, **kwargs) -> Any: @@ -314,7 +384,7 @@ def get_reply_msg_image_urls(self) -> list[str]: else: return [] - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: if self.event.reply: return self.event.reply.message.extract_plain_text() else: @@ -324,7 +394,7 @@ def get_reply_msg_plain_text(self) -> Optional[str]: @event_depend_register.register_depend(OneBotV11GroupMessageEvent) class OneBotV11GroupMessageEventDepend(OneBotV11MessageEventDepend[OneBotV11GroupMessageEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='onebot_v11_group', @@ -332,7 +402,7 @@ def _extract_event_entity_params(self) -> "EntityInitParams": parent_id=self.bot.self_id ) - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='onebot_v11_user', @@ -346,10 +416,10 @@ def _extract_user_entity_params(self) -> "EntityInitParams": @event_depend_register.register_depend(OneBotV11PrivateMessageEvent) class OneBotV11PrivateMessageEventDepend(OneBotV11MessageEventDepend[OneBotV11PrivateMessageEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return self._extract_user_entity_params() - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='onebot_v11_user', @@ -360,26 +430,10 @@ def _extract_user_entity_params(self) -> "EntityInitParams": ) -@event_depend_register.register_depend(OneBotV11GuildMessageEvent) -class OneBotV11GuildMessageEventDepend(OneBotV11MessageEventDepend[OneBotV11GuildMessageEvent]): - - def _extract_event_entity_params(self) -> "EntityInitParams": - return EntityInitParams( - bot_id=self.bot.self_id, - entity_type='onebot_v11_guild_channel', - entity_id=str(self.event.channel_id), - parent_id=str(self.event.guild_id) - ) - - def _extract_user_entity_params(self) -> "EntityInitParams": - return EntityInitParams( - bot_id=self.bot.self_id, - entity_type='onebot_v11_guild_user', - entity_id=str(self.event.user_id), - parent_id=str(self.event.guild_id), - entity_name=self.event.sender.nickname, - entity_info=self.event.sender.card - ) +def _parse_url_to_path(url: str) -> str | Path: + if urlparse(url).scheme not in ['http', 'https']: + return Path(url) + return url __all__ = [] diff --git a/src/service/omega_base/middlewares/platforms/qq.py b/src/service/omega_base/middlewares/platforms/qq.py index fedfeab6..6bd93c2a 100644 --- a/src/service/omega_base/middlewares/platforms/qq.py +++ b/src/service/omega_base/middlewares/platforms/qq.py @@ -9,32 +9,30 @@ """ from pathlib import Path -from typing import Any, Optional +from typing import Any from urllib.parse import urlparse +from nonebot.adapters.qq import Bot as QQBot +from nonebot.adapters.qq import C2CMessageCreateEvent as QQC2CMessageCreateEvent +from nonebot.adapters.qq import Event as QQEvent +from nonebot.adapters.qq import GroupAtMessageCreateEvent as QQGroupAtMessageCreateEvent from nonebot.adapters.qq import ( - Bot as QQBot, - Message as QQMessage, - MessageSegment as QQMessageSegment, - Event as QQEvent, GuildMessageEvent as QQGuildMessageEvent, # DirectMessageCreateEvent 是 GuildMessageEvent 的子类, 直接共用相同逻辑 - GroupAtMessageCreateEvent as QQGroupAtMessageCreateEvent, - C2CMessageCreateEvent as QQC2CMessageCreateEvent, ) -from nonebot.adapters.qq.models import MessageReference, Message +from nonebot.adapters.qq import Message as QQMessage +from nonebot.adapters.qq import MessageSegment as QQMessageSegment +from nonebot.adapters.qq.models import Message, MessageReference from nonebot.matcher import current_event from ..const import SupportedPlatform, SupportedTarget -from ..models import EntityInitParams, EntityTargetSendParams, EntityTargetRevokeParams +from ..models import EntityInitParams, EntityTargetRevokeParams, EntityTargetSendParams from ..platform_interface.entity_target import BaseEntityTarget, entity_target_register from ..platform_interface.event_depend import BaseEventDepend, event_depend_register from ..platform_interface.message_builder import BaseMessageBuilder, message_builder_register from ..typing import BaseSentMessageType -from ...message import ( - MessageSegmentType, - Message as OmegaMessage, - MessageSegment as OmegaMessageSegment -) +from ...message import Message as OmegaMessage +from ...message import MessageSegment as OmegaMessageSegment +from ...message import MessageSegmentType @message_builder_register.register_builder(SupportedPlatform.qq) @@ -51,19 +49,34 @@ def _get_target_base_segment_type() -> type[QQMessageSegment]: @staticmethod def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> QQMessageSegment: match seg_type: - case MessageSegmentType.at.value: + case MessageSegmentType.at: return QQMessageSegment.mention_user(user_id=seg_data.get('user_id', '')) - case MessageSegmentType.forward_id.value: - return QQMessageSegment.reference(reference=seg_data.get('id', '')) - case MessageSegmentType.image.value: - url = str(seg_data.get('url')) - if urlparse(url).scheme not in ['http', 'https']: - return QQMessageSegment.file_image(data=Path(url)) - else: - return QQMessageSegment.image(url=url) - case MessageSegmentType.image_file.value: + case MessageSegmentType.at_all: + return QQMessageSegment.mention_everyone() + case MessageSegmentType.emoji: + return QQMessageSegment.emoji(id=seg_data.get('id', '0')) + case MessageSegmentType.audio | MessageSegmentType.voice: + file = _parse_url_to_path(str(seg_data.get('url', ''))) + if isinstance(file, Path): + return QQMessageSegment.file_audio(data=file) + return QQMessageSegment.audio(url=file) + case MessageSegmentType.video: + file = _parse_url_to_path(str(seg_data.get('url', ''))) + if isinstance(file, Path): + return QQMessageSegment.file_video(data=file) + return QQMessageSegment.video(url=file) + case MessageSegmentType.image: + file = _parse_url_to_path(str(seg_data.get('url', ''))) + if isinstance(file, Path): + return QQMessageSegment.file_image(data=file) + return QQMessageSegment.image(url=file) + case MessageSegmentType.image_file: return QQMessageSegment.file_image(data=Path(seg_data.get('file', ''))) - case MessageSegmentType.text.value: + case MessageSegmentType.file: + return QQMessageSegment.file_file(data=Path(seg_data.get('file', ''))) + case MessageSegmentType.reply: + return QQMessageSegment.reference(reference=seg_data.get('id', '')) + case MessageSegmentType.text: return QQMessageSegment.text(content=seg_data.get('text', '')) case _: return QQMessageSegment.text(content='') @@ -86,24 +99,33 @@ def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> Omeg match seg_type: case 'mention_user': return OmegaMessageSegment.at(user_id=seg_data.get('user_id', '')) - case 'reference': - return OmegaMessageSegment.forward_id(id_=seg_data.get('reference', {}).get('message_id')) - case 'attachment': + case 'mention_everyone': + return OmegaMessageSegment.at_all() + case 'emoji': + return OmegaMessageSegment.emoji(id_=seg_data.get('id', '')) + case 'audio': + url = 'https://' + str(seg_data.get('url')).removeprefix('http://').removeprefix('https://') + return OmegaMessageSegment.audio(url=url) + case 'video': + url = 'https://' + str(seg_data.get('url')).removeprefix('http://').removeprefix('https://') + return OmegaMessageSegment.video(url=url) + case 'image': url = 'https://' + str(seg_data.get('url')).removeprefix('http://').removeprefix('https://') return OmegaMessageSegment.image(url=url) + case 'reference': + return OmegaMessageSegment.reply(id_=seg_data.get('reference', {}).get('message_id')) case 'text': return OmegaMessageSegment.text(text=seg_data.get('text', '')) case _: - return OmegaMessageSegment.text(text='') - + return OmegaMessageSegment.other(type_=seg_type, data=seg_data) @entity_target_register.register_target(SupportedTarget.qq_guild) class QQGuildEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': raise NotImplementedError - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError async def call_api_get_entity_name(self) -> str: @@ -118,11 +140,13 @@ async def call_api_get_entity_profile_image_url(self) -> str: url = getattr(guild_data, 'icon', '') return str(url) + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError @entity_target_register.register_target(SupportedTarget.qq_channel) class QQChannelEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': params = {'channel_id': self.entity.entity_id} if 'msg_id' in kwargs: params['msg_id'] = kwargs['msg_id'] @@ -142,7 +166,7 @@ def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": params=params ) - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': if not isinstance(sent_return, Message): raise ValueError(f'Sent message({sent_return!r}) can not be revoked') return EntityTargetRevokeParams( @@ -159,14 +183,16 @@ async def call_api_get_entity_name(self) -> str: async def call_api_get_entity_profile_image_url(self) -> str: raise NotImplementedError + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError # TODO @entity_target_register.register_target(SupportedTarget.qq_group) class QQGroupEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': raise NotImplementedError # TODO send_to_group - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError # TODO async def call_api_get_entity_name(self) -> str: @@ -175,14 +201,16 @@ async def call_api_get_entity_name(self) -> str: async def call_api_get_entity_profile_image_url(self) -> str: raise NotImplementedError # TODO + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError # TODO post_group_files @entity_target_register.register_target(SupportedTarget.qq_user) class QQUserEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': raise NotImplementedError # TODO send_to_c2c - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError # TODO async def call_api_get_entity_name(self) -> str: @@ -191,11 +219,13 @@ async def call_api_get_entity_name(self) -> str: async def call_api_get_entity_profile_image_url(self) -> str: raise NotImplementedError # TODO + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError # TODO post_c2c_files @entity_target_register.register_target(SupportedTarget.qq_guild_user) class QQGuildUserEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': params = {'guild_id': self.entity.parent_id} if 'msg_id' in kwargs: params['msg_id'] = kwargs['msg_id'] @@ -215,7 +245,7 @@ def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": params=params ) - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': raise NotImplementedError # 暂不支持主动撤回 dms 私聊消息 async def call_api_get_entity_name(self) -> str: @@ -234,28 +264,30 @@ async def call_api_get_entity_profile_image_url(self) -> str: url = getattr(getattr(guild_user_data, 'user', object()), 'avatar', '') return str(url) + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + raise NotImplementedError # TODO @event_depend_register.register_depend(QQEvent) class QQEventDepend[Event_T: QQEvent](BaseEventDepend[QQBot, Event_T, QQMessage]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return self._extract_user_entity_params() - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='qq_user', entity_id=self.bot.self_id, parent_id=self.bot.self_id ) - def get_omega_message_builder(self) -> type["BaseMessageBuilder[OmegaMessage, QQMessage]"]: + def get_omega_message_builder(self) -> type['BaseMessageBuilder[OmegaMessage, QQMessage]']: return QQMessageBuilder - def get_omega_message_extractor(self) -> type["BaseMessageBuilder[QQMessage, OmegaMessage]"]: + def get_omega_message_extractor(self) -> type['BaseMessageBuilder[QQMessage, OmegaMessage]']: return QQMessageExtractor - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError async def revoke(self, sent_return: Any, **kwargs) -> Any: @@ -270,32 +302,32 @@ def get_msg_image_urls(self) -> list[str]: def get_reply_msg_image_urls(self) -> list[str]: raise NotImplementedError - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: raise NotImplementedError @event_depend_register.register_depend(QQGuildMessageEvent) class QQGuildMessageEventDepend(QQEventDepend[QQGuildMessageEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='qq_channel', entity_id=self.event.channel_id, parent_id=self.event.guild_id ) - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='qq_guild_user', entity_id=self.event.author.id, parent_id=self.event.guild_id, entity_name=self.event.author.username, entity_info=self.event.author.avatar ) - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = QQMessageSegment.mention_user(user_id=self.event.author.id) + built_message return await self.bot.send(event=self.event, message=send_message, **kwargs) - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = QQMessageSegment.reference(reference=MessageReference(message_id=self.event.id)) + built_message return await self.bot.send(event=self.event, message=send_message, **kwargs) @@ -319,7 +351,7 @@ def get_reply_msg_image_urls(self) -> list[str]: else: return [] - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: if self.event.reply: return QQMessage.from_guild_message(self.event.reply).extract_plain_text() else: @@ -329,22 +361,22 @@ def get_reply_msg_plain_text(self) -> Optional[str]: @event_depend_register.register_depend(QQC2CMessageCreateEvent) class QQC2CMessageCreateEventDepend(QQEventDepend[QQC2CMessageCreateEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return self._extract_user_entity_params() - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='qq_user', entity_id=self.event.author.user_openid, parent_id=self.bot.self_id, entity_info=f'id: {self.event.author.id}, openid: {self.event.author.user_openid}' ) - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = QQMessageSegment.mention_user(user_id=self.event.author.user_openid) + built_message return await self.bot.send(event=self.event, message=send_message, **kwargs) - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = QQMessageSegment.reference(reference=MessageReference(message_id=self.event.id)) + built_message return await self.bot.send(event=self.event, message=send_message, **kwargs) @@ -361,33 +393,33 @@ def get_msg_image_urls(self) -> list[str]: def get_reply_msg_image_urls(self) -> list[str]: raise NotImplementedError # QQ 协议消息只有回复序列 id, 不支持获取回复消息内容 - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: raise NotImplementedError # QQ 协议消息只有回复序列 id, 不支持获取回复消息内容 @event_depend_register.register_depend(QQGroupAtMessageCreateEvent) class QQGroupAtMessageCreateEventDepend(QQEventDepend[QQGroupAtMessageCreateEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='qq_group', entity_id=self.event.group_openid, parent_id=self.bot.self_id, entity_info=f'group_openid: {self.event.group_openid}' ) - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='qq_user', entity_id=self.event.author.member_openid, parent_id=self.bot.self_id, entity_info=f'id: {self.event.author.id}, member_openid: {self.event.author.member_openid}' ) - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = QQMessageSegment.mention_user(user_id=self.event.author.member_openid) + built_message return await self.bot.send(event=self.event, message=send_message, **kwargs) - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = QQMessageSegment.reference(reference=MessageReference(message_id=self.event.id)) + built_message return await self.bot.send(event=self.event, message=send_message, **kwargs) @@ -404,8 +436,14 @@ def get_msg_image_urls(self) -> list[str]: def get_reply_msg_image_urls(self) -> list[str]: raise NotImplementedError # QQ 协议消息只有回复序列 id, 不支持获取回复消息内容 - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: raise NotImplementedError # QQ 协议消息只有回复序列 id, 不支持获取回复消息内容 +def _parse_url_to_path(url: str) -> str | Path: + if urlparse(url).scheme not in ['http', 'https']: + return Path(url) + return url + + __all__ = [] diff --git a/src/service/omega_base/middlewares/platforms/telegram.py b/src/service/omega_base/middlewares/platforms/telegram.py index e2da4475..39dd7746 100644 --- a/src/service/omega_base/middlewares/platforms/telegram.py +++ b/src/service/omega_base/middlewares/platforms/telegram.py @@ -8,35 +8,30 @@ @Software : PyCharm """ +from collections.abc import Sequence from pathlib import Path -from typing import Any, Optional, Sequence, cast -from urllib.parse import urlparse, quote - -from nonebot.adapters.telegram import ( - Bot as TelegramBot, - Message as TelegramMessage, - MessageSegment as TelegramMessageSegment, - Event as TelegramEvent -) -from nonebot.adapters.telegram.event import ( - MessageEvent as TelegramMessageEvent, - GroupMessageEvent as TelegramGroupMessageEvent, - PrivateMessageEvent as TelegramPrivateMessageEvent, - ChannelPostEvent as TelegramChannelPostEvent -) +from typing import Any, cast +from urllib.parse import quote + +from nonebot.adapters.telegram import Bot as TelegramBot +from nonebot.adapters.telegram import Event as TelegramEvent +from nonebot.adapters.telegram import Message as TelegramMessage +from nonebot.adapters.telegram import MessageSegment as TelegramMessageSegment +from nonebot.adapters.telegram.event import ChannelPostEvent as TelegramChannelPostEvent +from nonebot.adapters.telegram.event import GroupMessageEvent as TelegramGroupMessageEvent +from nonebot.adapters.telegram.event import MessageEvent as TelegramMessageEvent +from nonebot.adapters.telegram.event import PrivateMessageEvent as TelegramPrivateMessageEvent from nonebot.adapters.telegram.message import Entity, File from ..const import SupportedPlatform, SupportedTarget -from ..models import EntityInitParams, EntityTargetSendParams, EntityTargetRevokeParams +from ..models import EntityInitParams, EntityTargetRevokeParams, EntityTargetSendParams from ..platform_interface.entity_target import BaseEntityTarget, entity_target_register from ..platform_interface.event_depend import BaseEventDepend, event_depend_register from ..platform_interface.message_builder import BaseMessageBuilder, message_builder_register from ..typing import BaseSentMessageType -from ...message import ( - MessageSegmentType, - Message as OmegaMessage, - MessageSegment as OmegaMessageSegment -) +from ...message import Message as OmegaMessage +from ...message import MessageSegment as OmegaMessageSegment +from ...message import MessageSegmentType @message_builder_register.register_builder(SupportedPlatform.telegram) @@ -53,18 +48,23 @@ def _get_target_base_segment_type() -> type[TelegramMessageSegment]: @staticmethod def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> TelegramMessageSegment: match seg_type: - case MessageSegmentType.at.value: + case MessageSegmentType.at: return Entity.mention(text='@' + seg_data.get('user_id', '')) - case MessageSegmentType.image.value: - url = str(seg_data.get('url')) - if urlparse(url).scheme not in ['http', 'https']: - url = Path(url).as_posix() - return File.photo(file=url) - case MessageSegmentType.image_file.value: - return File.document(file=Path(seg_data.get('file', '')).as_posix()) - case MessageSegmentType.file.value: - return File.document(file=Path(seg_data.get('file', '')).as_posix()) - case MessageSegmentType.text.value: + case MessageSegmentType.emoji: + return Entity.custom_emoji(text=seg_data.get('name', ''), custom_emoji_id=seg_data.get('id', '')) + case MessageSegmentType.audio: + return File.audio(file=seg_data.get('url', '')) + case MessageSegmentType.voice: + return File.voice(file=seg_data.get('url', '')) + case MessageSegmentType.video: + return File.video(file=seg_data.get('url', '')) + case MessageSegmentType.image: + return File.photo(file=seg_data.get('url', '')) + case MessageSegmentType.image_file: + return File.document(file=seg_data.get('file', '')) + case MessageSegmentType.file: + return File.document(file=seg_data.get('file', '')) + case MessageSegmentType.text: return Entity.text(text=seg_data.get('text', '')) case _: return Entity.text(text='') @@ -86,17 +86,25 @@ def _construct_platform_segment(seg_type: str, seg_data: dict[str, Any]) -> Omeg match seg_type: case 'mention': return OmegaMessageSegment.at(user_id=str(seg_data.get('text')).removeprefix('@')) + case 'custom_emoji': + return OmegaMessageSegment.emoji(id_=seg_data.get('custom_emoji_id', ''), name=seg_data.get('text', '')) + case 'audio': + return OmegaMessageSegment.audio(url=seg_data.get('file', '')) + case 'voice': + return OmegaMessageSegment.voice(url=seg_data.get('file', '')) + case 'video': + return OmegaMessageSegment.video(url=seg_data.get('file', '')) case 'photo': return OmegaMessageSegment.image(url=seg_data.get('file', '')) case 'text': return OmegaMessageSegment.text(text=seg_data.get('text', '')) case _: - return OmegaMessageSegment.text(text='') + return OmegaMessageSegment.other(type_=seg_type, data=seg_data) class BaseTelegramEntityTarget(BaseEntityTarget): - def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": + def get_api_to_send_msg(self, **kwargs) -> 'EntityTargetSendParams': return EntityTargetSendParams( api='send_to', message_param_name='message', @@ -105,7 +113,7 @@ def get_api_to_send_msg(self, **kwargs) -> "EntityTargetSendParams": } ) - def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> "EntityTargetRevokeParams": + def get_api_to_revoke_msgs(self, sent_return: Any, **kwargs) -> 'EntityTargetRevokeParams': if isinstance(sent_return, Sequence): chat_id = sent_return[0].chat.id message_ids = [x.message_id for x in sent_return] @@ -136,7 +144,13 @@ async def call_api_get_entity_profile_image_url(self) -> str: raise ValueError('chat has no photo') file = await bot.call_api('get_file', file_id=getattr(photo, 'big_file_id', '')) - return f"https://api.telegram.org/file/bot{quote(bot.bot_config.token)}/{quote(file.file_path)}" + return f'https://api.telegram.org/file/bot{quote(bot.bot_config.token)}/{quote(file.file_path)}' + + async def call_api_send_file(self, file_path: str, file_name: str) -> None: + bot = cast(TelegramBot, await self.get_bot()) + file_message = File.document(file=Path(file_path).as_posix()) + + await bot.send_to(chat_id=self.entity.entity_id, message=file_message) @entity_target_register.register_target(SupportedTarget.telegram_user) @@ -157,26 +171,26 @@ class TelegramChannelEntityTarget(BaseTelegramEntityTarget): @event_depend_register.register_depend(TelegramEvent) class TelegramEventDepend[Event_T: TelegramEvent](BaseEventDepend[TelegramBot, Event_T, TelegramMessage]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='telegram_user', entity_id=self.bot.self_id, parent_id=self.bot.self_id ) - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='telegram_user', entity_id=self.bot.self_id, parent_id=self.bot.self_id ) - def get_omega_message_builder(self) -> type["BaseMessageBuilder[OmegaMessage, TelegramMessage]"]: + def get_omega_message_builder(self) -> type['BaseMessageBuilder[OmegaMessage, TelegramMessage]']: return TelegramMessageBuilder - def get_omega_message_extractor(self) -> type["BaseMessageBuilder[TelegramMessage, OmegaMessage]"]: + def get_omega_message_extractor(self) -> type['BaseMessageBuilder[TelegramMessage, OmegaMessage]']: return TelegramMessageExtractor - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: raise NotImplementedError async def revoke(self, sent_return: Any, **kwargs) -> Any: @@ -191,17 +205,17 @@ def get_msg_image_urls(self) -> list[str]: def get_reply_msg_image_urls(self) -> list[str]: raise NotImplementedError - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: raise NotImplementedError @event_depend_register.register_depend(TelegramMessageEvent) class TelegramMessageEventDepend[Event_T: TelegramMessageEvent](TelegramEventDepend[Event_T]): - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: return await self.send(message=message, **kwargs) - async def send_reply(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_reply(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: return await self.send(message=message, reply_to_message_id=self.event.message_id, **kwargs) async def revoke(self, sent_return: Any, **kwargs) -> Any: @@ -231,7 +245,7 @@ def get_reply_msg_image_urls(self) -> list[str]: else: return [] - def get_reply_msg_plain_text(self) -> Optional[str]: + def get_reply_msg_plain_text(self) -> str | None: if self.event.reply_to_message: return self.event.reply_to_message.get_plaintext() @@ -239,14 +253,14 @@ def get_reply_msg_plain_text(self) -> Optional[str]: @event_depend_register.register_depend(TelegramGroupMessageEvent) class TelegramGroupMessageEventDepend(TelegramMessageEventDepend[TelegramGroupMessageEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='telegram_group', entity_id=str(self.event.chat.id), parent_id=self.bot.self_id, entity_name=self.event.chat.title, entity_info=self.event.chat.type ) - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='telegram_user', entity_id=str(self.event.from_.id), parent_id=self.bot.self_id, @@ -254,7 +268,7 @@ def _extract_user_entity_params(self) -> "EntityInitParams": entity_info=f'{self.event.from_.first_name}/{self.event.from_.last_name}, @{self.event.from_.username}' ) - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = TelegramMessage() send_message += Entity.mention(f'@{self.event.from_.username}') @@ -269,10 +283,10 @@ def get_user_nickname(self) -> str: @event_depend_register.register_depend(TelegramPrivateMessageEvent) class TelegramPrivateMessageEventDepend(TelegramMessageEventDepend[TelegramPrivateMessageEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return self._extract_user_entity_params() - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='telegram_user', entity_id=str(self.event.from_.id), parent_id=self.bot.self_id, @@ -280,7 +294,7 @@ def _extract_user_entity_params(self) -> "EntityInitParams": entity_info=f'{self.event.from_.first_name}/{self.event.from_.last_name}, @{self.event.from_.username}' ) - async def send_at_sender(self, message: "BaseSentMessageType[OmegaMessage]", **kwargs) -> Any: + async def send_at_sender(self, message: 'BaseSentMessageType[OmegaMessage]', **kwargs) -> Any: built_message = self.build_platform_message(message=message) send_message = TelegramMessage() send_message += Entity.mention(f'@{self.event.from_.username}') @@ -295,14 +309,14 @@ def get_user_nickname(self) -> str: @event_depend_register.register_depend(TelegramChannelPostEvent) class TelegramChannelPostEventDepend(TelegramMessageEventDepend[TelegramChannelPostEvent]): - def _extract_event_entity_params(self) -> "EntityInitParams": + def _extract_event_entity_params(self) -> 'EntityInitParams': return EntityInitParams( bot_id=self.bot.self_id, entity_type='telegram_channel', entity_id=str(self.event.chat.id), parent_id=self.bot.self_id, entity_name=self.event.chat.title, entity_info=self.event.chat.type ) - def _extract_user_entity_params(self) -> "EntityInitParams": + def _extract_user_entity_params(self) -> 'EntityInitParams': return self._extract_event_entity_params() diff --git a/src/service/omega_base/middlewares/typing.py b/src/service/omega_base/middlewares/typing.py index 9a4b446f..fed8cc4c 100644 --- a/src/service/omega_base/middlewares/typing.py +++ b/src/service/omega_base/middlewares/typing.py @@ -8,9 +8,10 @@ @Software : PyCharm """ -from typing import Literal, Union +from typing import Literal -from nonebot.internal.adapter import Message as BaseMessage, MessageSegment as BaseMessageSegment +from nonebot.internal.adapter import Message as BaseMessage +from nonebot.internal.adapter import MessageSegment as BaseMessageSegment type EntityAcquireType = Literal['event', 'user'] """从 Event 提取 Entity 对象的类型, event: 事件本身所在场景的对象(群组频道等), user: 触发事件的用户对象""" @@ -21,7 +22,7 @@ type BaseMessageSegType[Msg_T: BaseMessageType] = BaseMessageSegment[Msg_T] """Nonebot 消息段基类类型""" -type BaseSentMessageType[Msg_T: BaseMessageType] = Union[str, BaseMessageSegType[Msg_T], Msg_T] +type BaseSentMessageType[Msg_T: BaseMessageType] = str | BaseMessageSegType[Msg_T] | Msg_T """Nonebot 可发送消息基类类型""" __all__ = [ diff --git a/src/service/omega_global_cache/__init__.py b/src/service/omega_global_cache/__init__.py new file mode 100644 index 00000000..31edc3d1 --- /dev/null +++ b/src/service/omega_global_cache/__init__.py @@ -0,0 +1,89 @@ +""" +@Author : Ailitonia +@Date : 2024/11/13 17:53:20 +@FileName : omega_global_cache.py +@Project : omega-miya +@Description : Omega 全局缓存 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from datetime import datetime, timedelta + +from sqlalchemy.exc import NoResultFound + +from src.database import GlobalCacheDAL, begin_db_session + + +class OmegaGlobalCache: + """Omega 全局缓存""" + + def __init__(self, cache_name: str, *, ttl: int = 86400): + self._cache_name = cache_name + self._ttl = ttl + + # 内存级缓存, 对象存续期间永不失效 + self._cache: dict[str, str] = {} + + @property + def expired_at(self) -> datetime: + return datetime.now() + timedelta(seconds=self._ttl) + + async def _query_db_unique(self, key: str) -> str: + async with begin_db_session() as session: + result = await GlobalCacheDAL(session).query_unique(cache_name=self._cache_name, cache_key=key) + return result.cache_value + + async def _query_db_series(self) -> dict[str, str]: + async with begin_db_session() as session: + result = await GlobalCacheDAL(session).query_series(cache_name=self._cache_name) + return {x.cache_key: x.cache_value for x in result} + + async def _clean_db_expired(self) -> None: + async with begin_db_session() as session: + await GlobalCacheDAL(session).delete_series_expired(cache_name=self._cache_name) + + async def _save_db(self, key: str, value: str) -> None: + if len(value) > 4096: + raise ValueError('the length of value must less than 4096') + + async with begin_db_session() as session: + await GlobalCacheDAL(session).upsert( + cache_name=self._cache_name, cache_key=key, cache_value=value, expired_time=self.expired_at + ) + + async def load(self, key: str) -> str | None: + """读取缓存""" + if (value := self._cache.get(key, None)) is not None: + return value + + try: + result = await self._query_db_unique(key=key) + self._cache.update({key: result}) + return result + except NoResultFound: + return None + + async def save(self, key: str, value: str) -> None: + """更新内部内存缓存及数据库缓存""" + self._cache.update({key: value}) + await self._save_db(key=key, value=value) + + def update_internal(self, key: str, value: str) -> None: + """仅更新内部内存缓存""" + self._cache.update({key: value}) + + def clear_internal(self): + """仅清空内存缓存""" + self._cache.clear() + + async def sync_internal(self): + """同步内部内存缓存与数据库缓存一致""" + await self._clean_db_expired() + self._cache.clear() + self._cache.update(await self._query_db_series()) + + +__all__ = [ + 'OmegaGlobalCache' +] diff --git a/src/service/omega_multibot_support/onebot_v11.py b/src/service/omega_multibot_support/onebot_v11.py index 40f7644d..9f1848d7 100644 --- a/src/service/omega_multibot_support/onebot_v11.py +++ b/src/service/omega_multibot_support/onebot_v11.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from typing import Annotated, Optional +from typing import Annotated from nonebot.adapters.onebot.v11.bot import Bot from nonebot.adapters.onebot.v11.event import Event @@ -16,11 +16,12 @@ from nonebot.log import logger from nonebot.message import event_preprocessor, run_preprocessor from nonebot.params import Depends -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, ValidationError from sqlalchemy.exc import NoResultFound from sqlalchemy.ext.asyncio import AsyncSession -from src.compat import AnyHttpUrlStr as AnyHttpUrl, parse_obj_as +from src.compat import AnyHttpUrlStr as AnyHttpUrl +from src.compat import parse_obj_as from src.database import BotSelfDAL, EntityDAL, get_db_session from src.service.omega_base.event import BotConnectEvent, BotDisconnectEvent @@ -67,13 +68,13 @@ class GroupInfo(BaseOneBotModel): group_name: str member_count: int max_member_count: int - group_memo: Optional[str] = '' + group_memo: str | None = '' group_create_time: int = 0 group_level: int = 0 class GuildServiceProfile(BaseOneBotModel): - """Api /get_guild_service_profile 频道系统内BOT的资料 返回值 + """API /get_guild_service_profile 频道系统内BOT的资料 返回值 - nickname: 昵称 - tiny_id: 自身的ID @@ -85,7 +86,7 @@ class GuildServiceProfile(BaseOneBotModel): class GuildInfo(BaseOneBotModel): - """Api /get_guild_list 频道列表 + """API /get_guild_list 频道列表 正常情况下响应 GuildInfo 数组, 未加入任何频道响应 null - guild_id, 频道ID @@ -98,7 +99,7 @@ class GuildInfo(BaseOneBotModel): class ChannelInfo(BaseOneBotModel): - """Api /get_guild_channel_list 子频道信息 + """API /get_guild_channel_list 子频道信息 - owner_guild_id: 所属频道ID - channel_id: 子频道ID @@ -165,17 +166,17 @@ class VersionInfo(BaseOneBotModel): app_name: str app_version: str protocol_version: str - app_full_name: Optional[str] = None - coolq_edition: Optional[str] = None - coolq_directory: Optional[str] = None + app_full_name: str | None = None + coolq_edition: str | None = None + coolq_directory: str | None = None is_go_cqhttp: bool = Field(default=False, alias='go-cqhttp') - protocol: Optional[int] = Field(None, alias='protocol_name') - plugin_version: Optional[str] = None - plugin_build_number: Optional[int] = None - plugin_build_configuration: Optional[str] = None - runtime_version: Optional[str] = None - runtime_os: Optional[str] = None - version: Optional[str] = None + protocol: int | None = Field(None, alias='protocol_name') + plugin_version: str | None = None + plugin_build_number: int | None = None + plugin_build_configuration: str | None = None + runtime_version: str | None = None + runtime_os: str | None = None + version: str | None = None model_config = ConfigDict(extra='ignore') @@ -300,8 +301,16 @@ async def __obv11_bot_connect( except AdapterException as e: logger.warning( - f'{event.bot_type}: {bot.self_id}, Upgrade guild/channel data failed, guild api not supported, {e}' + f'{event.bot_type}: {bot.self_id}, Upgrade guild/channel data failed, ' + f'the OneBot V11 client does not support the guild API, {e}' ) + except ValidationError as e: + logger.warning( + f'{event.bot_type}: {bot.self_id}, Upgrade guild/channel data failed, ' + f'the OneBot V11 client guild API returns data out of expected, {e}' + ) + except Exception as e: + logger.error(f'{event.bot_type}: {bot.self_id}, Upgrade guild/channel data failed, {e}') logger.opt(colors=True).success(f'{event.bot_type}: {bot.self_id} 已连接, All entity data upgraded Success') diff --git a/src/service/omega_multibot_support/qq.py b/src/service/omega_multibot_support/qq.py index 1db2c512..a2fd0937 100644 --- a/src/service/omega_multibot_support/qq.py +++ b/src/service/omega_multibot_support/qq.py @@ -21,7 +21,7 @@ from src.service.omega_base.event import BotConnectEvent, BotDisconnectEvent if TYPE_CHECKING: - from nonebot.adapters.qq.models import Guild, Channel + from nonebot.adapters.qq.models import Channel, Guild @event_preprocessor @@ -51,7 +51,7 @@ async def __qq_bot_connect( logger.opt(colors=True).success(f'{event.bot_type}: {bot.self_id} 已连接, Bot status added Success') # 更新频道相关信息 - guilds: list["Guild"] = await bot.guilds() + guilds: list[Guild] = await bot.guilds() for guild in guilds: guild_query_data = { 'bot_index_id': exist_bot.id, @@ -74,7 +74,7 @@ async def __qq_bot_connect( # 更新子频道相关信息 for guild in guilds: - channels: list["Channel"] = await bot.get_channels(guild_id=guild.id) + channels: list[Channel] = await bot.get_channels(guild_id=guild.id) for channel in channels: channel_query_data = { 'bot_index_id': exist_bot.id, diff --git a/src/service/omega_multibot_support/universal.py b/src/service/omega_multibot_support/universal.py index 6c624114..350a9ca7 100644 --- a/src/service/omega_multibot_support/universal.py +++ b/src/service/omega_multibot_support/universal.py @@ -11,16 +11,16 @@ import asyncio from nonebot import get_driver, logger -from nonebot.adapters import Bot, Event from nonebot.exception import IgnoredException +from nonebot.internal.adapter import Bot as BaseBot +from nonebot.internal.adapter import Event as BaseEvent from nonebot.matcher import Matcher from nonebot.message import handle_event, run_preprocessor from nonebot.permission import Permission from src.service.omega_base.event import BotConnectEvent, BotDisconnectEvent - -__ONLINE_BOTS: dict[str, Bot] = {} +__ONLINE_BOTS: dict[str, BaseBot] = {} """当前在线的 Bot""" lock = asyncio.Lock() driver = get_driver() @@ -42,7 +42,7 @@ def __init__(self, sessions: tuple[str, ...], original: str | None = None, perm: self.original = original self.perm = perm - async def __call__(self, bot: Bot, event: Event) -> bool: + async def __call__(self, bot: BaseBot, event: BaseEvent) -> bool: return bool( event.get_session_id() in self.sessions and (self.original is None or bot.self_id == self.original) @@ -50,7 +50,7 @@ async def __call__(self, bot: Bot, event: Event) -> bool: ) -async def __original_responding_permission_updater(bot: Bot, event: Event, matcher: Matcher) -> Permission: +async def __original_responding_permission_updater(bot: BaseBot, event: BaseEvent, matcher: Matcher) -> Permission: """匹配当前事件是否属于由最初响应的 Bot 发起的指定会话""" return Permission( __OriginalResponding( @@ -66,7 +66,7 @@ async def __original_responding_permission_updater(bot: Bot, event: Event, match @run_preprocessor -async def __unique_bot_responding_limit(bot: Bot, event: Event): +async def __unique_bot_responding_limit(bot: BaseBot, event: BaseEvent): # 对于多协议端同时接入, 各个bot之间不能相互响应, 避免形成死循环 try: event_user_id = event.get_user_id() @@ -80,7 +80,7 @@ async def __unique_bot_responding_limit(bot: Bot, event: Event): @driver.on_bot_connect -async def __init_bot_connect(bot: Bot): +async def __init_bot_connect(bot: BaseBot): """在 Bot 连接时执行初始化操作""" async with lock: __ONLINE_BOTS.update({str(bot.self_id): bot}) @@ -88,14 +88,14 @@ async def __init_bot_connect(bot: Bot): @driver.on_bot_disconnect -async def __dispose_bot_disconnect(bot: Bot): +async def __dispose_bot_disconnect(bot: BaseBot): """在 Bot 断开连接时执行后续处理""" async with lock: __ONLINE_BOTS.pop(str(bot.self_id), None) await handle_event(bot=bot, event=BotDisconnectEvent(bot_id=bot.self_id, bot_type=bot.type)) -def get_online_bots() -> dict[str, dict[str, Bot]]: +def get_online_bots() -> dict[str, dict[str, BaseBot]]: """获取当前在线的 bot (根据 Adapter 分类)""" online_bots = {} for self_id, bot in __ONLINE_BOTS.items(): @@ -107,5 +107,5 @@ def get_online_bots() -> dict[str, dict[str, Bot]]: __all__ = [ - 'get_online_bots' + 'get_online_bots', ] diff --git a/src/service/omega_processor/onebot/__init__.py b/src/service/omega_processor/onebot/__init__.py index c60615d6..fd068a6a 100644 --- a/src/service/omega_processor/onebot/__init__.py +++ b/src/service/omega_processor/onebot/__init__.py @@ -10,5 +10,4 @@ from . import v11 as v11 - __all__ = [] diff --git a/src/service/omega_processor/onebot/v11/__init__.py b/src/service/omega_processor/onebot/v11/__init__.py index 02ec807c..32e525c9 100644 --- a/src/service/omega_processor/onebot/v11/__init__.py +++ b/src/service/omega_processor/onebot/v11/__init__.py @@ -8,8 +8,8 @@ @Software : PyCharm """ -from nonebot.message import event_preprocessor from nonebot.adapters.onebot.v11 import Bot, Event, MessageEvent +from nonebot.message import event_preprocessor from .v11_replace_ntqq_image_url import handle_replace_image_url_event_preprocessor diff --git a/src/service/omega_processor/onebot/v11/v11_replace_ntqq_image_url.py b/src/service/omega_processor/onebot/v11/v11_replace_ntqq_image_url.py index 533929da..813f7eeb 100644 --- a/src/service/omega_processor/onebot/v11/v11_replace_ntqq_image_url.py +++ b/src/service/omega_processor/onebot/v11/v11_replace_ntqq_image_url.py @@ -8,7 +8,8 @@ @Software : PyCharm """ -from typing import Callable, Literal, Optional +from collections.abc import Callable +from typing import Literal from nonebot import get_plugin_config, logger from nonebot.adapters.onebot.v11 import Message, MessageEvent, MessageSegment @@ -19,12 +20,12 @@ class OneBotV11ImageUrlReplacerConfig(BaseModel): """OneBot V11 图片 URL 替换处理配置""" - onebot_v11_image_url_replacer: Literal['http', 'domain'] | None = 'http' + onebot_v11_image_url_replacer: Literal['http', 'domain'] | None = None model_config = ConfigDict(extra='ignore') -def _ger_image_url_replacer(replacer: Optional[str]) -> SegReplacerType: +def _ger_image_url_replacer(replacer: str | None) -> SegReplacerType: match replacer: case 'http': old_ = 'https://' @@ -32,7 +33,7 @@ def _ger_image_url_replacer(replacer: Optional[str]) -> SegReplacerType: case 'domain': old_ = 'https://multimedia.nt.qq.com.cn' new_ = 'https://gchat.qpic.cn' - case _: + case None | _: old_ = '' new_ = '' @@ -68,7 +69,7 @@ def _get_confined_replacer() -> SegReplacerType: _REPLACER: SegReplacerType = _get_confined_replacer() -def _parse_message(message: Message) -> Message: +def _replace_message_image(message: Message) -> Message: output_message = Message() for seg in message: if seg.type == 'image': @@ -86,7 +87,9 @@ def _parse_message(message: Message) -> Message: async def handle_replace_image_url_event_preprocessor(event: MessageEvent): """事件预处理, 替换 image 消息段中的图片 url 域名""" - event.message = _parse_message(message=event.message.copy()) + event.message = _replace_message_image(message=event.message.copy()) + if event.reply is not None: + event.reply.message = _replace_message_image(message=event.reply.message) __all__ = [ diff --git a/src/service/omega_processor/plugin_utils/__init__.py b/src/service/omega_processor/plugin_utils/__init__.py index 390be3b3..9449bcc9 100644 --- a/src/service/omega_processor/plugin_utils/__init__.py +++ b/src/service/omega_processor/plugin_utils/__init__.py @@ -5,10 +5,10 @@ @Project : nonebot2_miya @Description : 插件 processor 引入工具 @GitHub : https://github.com/Ailitonia -@Software : PyCharm +@Software : PyCharm """ -from typing import Literal, Optional +from typing import Literal from nonebot.typing import T_State from pydantic import BaseModel, ConfigDict @@ -52,8 +52,8 @@ def enable_processor_state( enable_processor: bool = True, *, level: int = 2**31-1, - auth_node: Optional[str] = None, - extra_auth_node: Optional[set[str]] = None, + auth_node: str | None = None, + extra_auth_node: set[str] | None = None, cooldown: int = 0, cooldown_type: Literal['event', 'user'] = 'event', cost: float = 0, diff --git a/src/service/omega_processor/telegram/__init__.py b/src/service/omega_processor/telegram/__init__.py index ffc26688..69251abf 100644 --- a/src/service/omega_processor/telegram/__init__.py +++ b/src/service/omega_processor/telegram/__init__.py @@ -8,9 +8,9 @@ @Software : PyCharm """ -from nonebot.message import event_preprocessor from nonebot.adapters.telegram.bot import Bot from nonebot.adapters.telegram.event import Event, MessageEvent +from nonebot.message import event_preprocessor from .image_parser import handle_parse_message_image_event_preprocessor diff --git a/src/service/omega_processor/telegram/image_parser.py b/src/service/omega_processor/telegram/image_parser.py index 977ba350..f7b86d27 100644 --- a/src/service/omega_processor/telegram/image_parser.py +++ b/src/service/omega_processor/telegram/image_parser.py @@ -13,11 +13,11 @@ from nonebot import get_plugin_config, logger from nonebot.adapters.telegram.bot import Bot from nonebot.adapters.telegram.event import MessageEvent -from nonebot.adapters.telegram.message import Message, MessageSegment, File +from nonebot.adapters.telegram.message import File, Message, MessageSegment from pydantic import BaseModel, ConfigDict -from ...omega_requests import OmegaRequests -from ....resource import TemporaryResource +from src.resource import TemporaryResource +from src.utils import OmegaRequests _TMP_IMG_PATH = TemporaryResource('telegram', 'tmp', 'images') @@ -43,7 +43,7 @@ async def _parse_photo_segment(bot: Bot, seg: MessageSegment) -> MessageSegment: if file.file_path is None: return seg - url = f"https://api.telegram.org/file/bot{quote(bot.bot_config.token)}/{quote(file.file_path)}" + url = f'https://api.telegram.org/file/bot{quote(bot.bot_config.token)}/{quote(file.file_path)}' # 该链接不能直接作为向 Telegram 平台发送图片的 url, 会返回错误: "wrong file identifier/HTTP URL specified" if not _plugin_config.telegram_processor_parse_photo_replace_as_local: diff --git a/src/service/omega_processor/universal/__init__.py b/src/service/omega_processor/universal/__init__.py index 018f54b6..8b045c42 100644 --- a/src/service/omega_processor/universal/__init__.py +++ b/src/service/omega_processor/universal/__init__.py @@ -11,7 +11,7 @@ from nonebot import get_driver, logger from nonebot.internal.adapter import Bot, Event from nonebot.matcher import Matcher -from nonebot.message import event_preprocessor, run_preprocessor, run_postprocessor, event_postprocessor +from nonebot.message import event_postprocessor, event_preprocessor, run_postprocessor, run_preprocessor from .cancellation import preprocessor_cancellation from .cooldown import preprocessor_global_cooldown, preprocessor_plugin_cooldown @@ -19,7 +19,7 @@ from .friendship import postprocessor_friendship from .history import postprocessor_history from .permission import preprocessor_global_permission, preprocessor_plugin_permission -from .plugin import startup_init_plugins, preprocessor_plugin_manager +from .plugin import preprocessor_plugin_manager, startup_init_plugins from .rate_limiting import preprocessor_rate_limiting from .statistic import postprocessor_statistic diff --git a/src/service/omega_processor/universal/cancellation.py b/src/service/omega_processor/universal/cancellation.py index daff5bb5..d868bfef 100644 --- a/src/service/omega_processor/universal/cancellation.py +++ b/src/service/omega_processor/universal/cancellation.py @@ -9,7 +9,6 @@ """ import re -from typing import Union from nonebot import logger from nonebot.exception import IgnoredException @@ -17,12 +16,12 @@ from nonebot.matcher import Matcher CANCEL_PROMPT: str = '操作取消,已退出命令交互' -CHINESE_CANCELLATION_WORDS = {"算", "别", "不", "停", "取消"} -CHINESE_CANCELLATION_REGEX_1 = re.compile(r"^那?[算别不停]\w{0,3}了?吧?$") -CHINESE_CANCELLATION_REGEX_2 = re.compile(r"^那?(?:[给帮]我)?取消了?吧?$") +CHINESE_CANCELLATION_WORDS = {'算', '别', '不', '停', '取消'} +CHINESE_CANCELLATION_REGEX_1 = re.compile(r'^那?[算别不停]\w{0,3}了?吧?$') +CHINESE_CANCELLATION_REGEX_2 = re.compile(r'^那?(?:[给帮]我)?取消了?吧?$') -def is_cancellation(message: Union[Message, str]) -> bool: +def is_cancellation(message: Message | str) -> bool: """判断消息是否表示取消 :param message: 消息对象或消息文本 diff --git a/src/service/omega_processor/universal/history.py b/src/service/omega_processor/universal/history.py index 8605ed38..32938964 100644 --- a/src/service/omega_processor/universal/history.py +++ b/src/service/omega_processor/universal/history.py @@ -13,48 +13,48 @@ from nonebot import logger from nonebot.internal.adapter import Bot, Event, Message +from src.compat import dump_json_as from src.database import HistoryDAL, begin_db_session from src.service import OmegaMatcherInterface -LOG_PREFIX: str = 'Event History | ' +LOG_PREFIX: str = 'Message History | ' async def postprocessor_history(bot: Bot, event: Event, message: Message): """事件后处理, 消息历史记录""" - self_id = bot.self_id - time = round(datetime.now().timestamp()) - - event_type = event.get_type() - try: - event_id = f'{event.get_event_name()}_{event.get_session_id()}' - except (NotImplementedError, ValueError): - event_id = f'{event_type}_{self_id}_{time}' - - raw_data = event.model_dump_json() - raw_data = str(raw_data) if not isinstance(raw_data, str) else raw_data - msg_data = str(message) - - if len(raw_data) > 4096: - logger.opt(colors=True).debug(f'{LOG_PREFIX}raw_data is longer than field limiting to be reduce, {raw_data!r}') - raw_data = raw_data[:4096] - if len(msg_data) > 4096: - logger.opt(colors=True).debug(f'{LOG_PREFIX}msg_data is longer than field limiting to be reduce, {msg_data!r}') - msg_data = msg_data[:4096] + if (message_id := getattr(event, 'message_id', None)) is not None: + message_id = str(message_id) + elif (message_id := getattr(event, 'id', None)) is not None: + message_id = str(message_id) + else: + message_id = str(hash(message)) + + message_raw = dump_json_as(Message, message, encoding='utf-8') + message_text = message.extract_plain_text() + if len(message_raw) > 4096: + logger.opt(colors=True).debug(f'{LOG_PREFIX}message_raw reduced by exceeding field limiting, {message_raw!r}') + message_raw = message_raw[:4096] + if len(message_text) > 4096: + logger.opt(colors=True).debug(f'{LOG_PREFIX}message_text reduced by exceeding field limiting, {message_text!r}') + message_text = message_text[:4096] try: async with begin_db_session() as session: - entity = OmegaMatcherInterface.get_entity(bot=bot, event=event, session=session, acquire_type='user') - parent_entity_id = entity.parent_id - entity_id = entity.entity_id - - dal = HistoryDAL(session=session) - await dal.add( - time=time, bot_self_id=self_id, parent_entity_id=parent_entity_id, entity_id=entity_id, - event_type=event_type, event_id=event_id, raw_data=raw_data, msg_data=msg_data + event_entity = OmegaMatcherInterface.get_entity(bot, event, session, acquire_type='event') + user_entity = OmegaMatcherInterface.get_entity(bot, event, session, acquire_type='user') + await HistoryDAL(session=session).add( + message_id=message_id, + bot_self_id=bot.self_id, + event_entity_id=event_entity.entity_id, + user_entity_id=user_entity.entity_id, + received_time=int(datetime.now().timestamp()), + message_type=f'{event_entity.entity_type}.{event.get_event_name()}', + message_raw=message_raw, + message_text=message_text, ) - logger.opt(colors=True).trace(f'{LOG_PREFIX}Recording event({event_id}) succeed') + logger.opt(colors=True).trace(f'{LOG_PREFIX}Message(id={message_id!r}, text={message_text!r}) recorded') except Exception as e: - logger.opt(colors=True).error(f'{LOG_PREFIX}Recording failed, {e!r}, event: {event.model_dump_json()}') + logger.opt(colors=True).error(f'{LOG_PREFIX}Recording message failed, {e!r}, {message_raw!r}') __all__ = [ diff --git a/src/service/omega_processor/universal/permission.py b/src/service/omega_processor/universal/permission.py index f1eef2b8..2dc12c5a 100644 --- a/src/service/omega_processor/universal/permission.py +++ b/src/service/omega_processor/universal/permission.py @@ -109,10 +109,10 @@ async def preprocessor_plugin_permission(matcher: Matcher, bot: Bot, event: Even echo_message = '权限不足! 需要' if processor_state.level <= 100: echo_message += f'权限等级 Level-{processor_state.level} 或' - echo_message += f'权限节点 "{processor_state.name}.{processor_state.auth_node}", ' + echo_message += f'权限节点 "{plugin_name}.{processor_state.auth_node}", ' echo_message += f'请联系管理员使用 "/SetOmegaLevel {processor_state.level}" 提升权限等级或配置插件对应权限节点' else: - echo_message += f'权限节点 "{processor_state.name}.{processor_state.auth_node}", ' + echo_message += f'权限节点 "{plugin_name}.{processor_state.auth_node}", ' echo_message += '请联系管理员配置插件对应权限节点' await matcher.send(message=echo_message) except Exception as e: diff --git a/src/service/omega_processor/universal/plugin.py b/src/service/omega_processor/universal/plugin.py index c49bacb5..7013dea7 100644 --- a/src/service/omega_processor/universal/plugin.py +++ b/src/service/omega_processor/universal/plugin.py @@ -8,47 +8,55 @@ @Software : PyCharm """ +from collections.abc import Iterable + from nonebot import get_driver, get_loaded_plugins, logger -from nonebot.adapters import Event from nonebot.exception import IgnoredException +from nonebot.internal.adapter import Event as BaseEvent from nonebot.matcher import Matcher from nonebot.plugin import Plugin from sqlalchemy.exc import NoResultFound from src.database import PluginDAL, begin_db_session -from src.utils.process_utils import semaphore_gather LOG_PREFIX: str = 'Plugin Manager | ' SUPERUSERS = get_driver().config.superusers -async def _add_update_plugin(plugin: Plugin) -> None: +async def _upsert_plugins(plugins: Iterable[Plugin]) -> None: """更新数据库中插件信息""" async with begin_db_session() as session: dal = PluginDAL(session=session) - try: - _plugin = await dal.query_unique(plugin_name=plugin.name, module_name=plugin.module_name) - await dal.update(id_=_plugin.id, info=plugin.metadata.name if plugin.metadata else None) - except NoResultFound: - await dal.add(plugin_name=plugin.name, module_name=plugin.module_name, - enabled=1, info=plugin.metadata.name if plugin.metadata else None) + for plugin in plugins: + try: + await dal.query_unique(plugin_name=plugin.name, module_name=plugin.module_name) + await dal.update( + plugin_name=plugin.name, + module_name=plugin.module_name, + info=plugin.metadata.name if plugin.metadata else None, + ) + except NoResultFound: + await dal.add( + plugin_name=plugin.name, + module_name=plugin.module_name, + enabled=1, + info=plugin.metadata.name if plugin.metadata else None, + ) async def startup_init_plugins(): """初始化已加载的插件到数据库""" - tasks = [_add_update_plugin(plugin=plugin) for plugin in get_loaded_plugins()] - plugins_init_result = await semaphore_gather(tasks=tasks, semaphore_num=10) - - for result in plugins_init_result: - if isinstance(result, Exception): - import sys - logger.opt(colors=True).critical(f'{LOG_PREFIX}初始化插件信息失败, {result}') - sys.exit(f'初始化插件信息失败, {result}') + try: + await _upsert_plugins(plugins=get_loaded_plugins()) + except Exception as e: + import sys + logger.opt(colors=True).critical(f'{LOG_PREFIX}初始化插件信息失败, {e}') + sys.exit(f'初始化插件信息失败, {e}') logger.opt(colors=True).success(f'{LOG_PREFIX}插件信息初始化已完成.') -async def preprocessor_plugin_manager(matcher: Matcher, event: Event): +async def preprocessor_plugin_manager(matcher: Matcher, event: BaseEvent): """运行预处理, 处理插件管理器""" try: user_id = event.get_user_id() diff --git a/src/service/omega_processor/universal/rate_limiting.py b/src/service/omega_processor/universal/rate_limiting.py index 7d67e2aa..b4a0fb6b 100644 --- a/src/service/omega_processor/universal/rate_limiting.py +++ b/src/service/omega_processor/universal/rate_limiting.py @@ -10,11 +10,11 @@ import time from datetime import datetime, timedelta -from typing import Union, Dict from nonebot import get_driver, logger -from nonebot.adapters import Bot, Event from nonebot.exception import IgnoredException +from nonebot.internal.adapter import Bot as BaseBot +from nonebot.internal.adapter import Event as BaseEvent SUPERUSERS = get_driver().config.superusers LOG_PREFIX: str = 'Rate Limiting | ' @@ -27,14 +27,14 @@ # 触发速率限制时为用户设置的流控冷却时间, 单位秒 RATE_LIMITING_COOL_DOWN: int = 1800 # 记录用户上次消息的时间戳, 作为对比依据 -USER_LAST_MSG_TIME: Dict[str, Union[int, float]] = {} +USER_LAST_MSG_TIME: dict[str, int | float] = {} # 记录用户消息在速率限制时间阈值内触发的次数 -RATE_LIMITING_COUNT: Dict[str, int] = {} +RATE_LIMITING_COUNT: dict[str, int] = {} # 已被限制的用户id及到期时间 -RATE_LIMITING_USER_TEMP: Dict[str, datetime] = {} +RATE_LIMITING_USER_TEMP: dict[str, datetime] = {} -async def preprocessor_rate_limiting(bot: Bot, event: Event): +async def preprocessor_rate_limiting(bot: BaseBot, event: BaseEvent): """事件预处理, 针对用户的速率限制处理""" try: _ = event.get_message() diff --git a/src/service/omega_processor/universal/statistic.py b/src/service/omega_processor/universal/statistic.py index 76af80e1..038f04d1 100644 --- a/src/service/omega_processor/universal/statistic.py +++ b/src/service/omega_processor/universal/statistic.py @@ -50,17 +50,17 @@ async def postprocessor_statistic(matcher: Matcher, bot: Bot, event: Event): logger.opt(colors=True).debug(f'{LOG_PREFIX}Plugin({custom_plugin_name}) ignored with disable processor') return - # 跳过不需要 processor 交互的 matcher (一般来说这样的都是后台或响应式的不用展示统计信息) - if not processor_state.echo_processor_result: - logger.opt(colors=True).debug(f'{LOG_PREFIX}Plugin({custom_plugin_name}) ignored with disable echo') - return + # [Deactivated] 跳过不需要 processor 交互的 matcher (一般来说这样的都是后台或响应式的不用展示统计信息) + # if not processor_state.echo_processor_result: + # logger.opt(colors=True).debug(f'{LOG_PREFIX}Plugin({custom_plugin_name}) ignored with disable echo') + # return try: async with begin_db_session() as session: entity = OmegaMatcherInterface.get_entity(bot=bot, event=event, session=session) parent_entity_id = entity.parent_id entity_id = entity.entity_id - call_info = f'{custom_plugin_name!r} called by {entity!r} in event {event}' + call_info = f'{custom_plugin_name!r} called by {entity!r} in Event: {event}' dal = StatisticDAL(session=session) await dal.add(module_name=module_name, plugin_name=custom_plugin_name, diff --git a/src/service/qq_guild_audit_patch/__init__.py b/src/service/qq_guild_audit_patch/__init__.py index d486b319..9337582a 100644 --- a/src/service/qq_guild_audit_patch/__init__.py +++ b/src/service/qq_guild_audit_patch/__init__.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from typing import Any, Dict, Optional +from typing import Any from nonebot.adapters.qq.bot import Bot as QQBot from nonebot.adapters.qq.event import MessageAuditPassEvent @@ -21,9 +21,9 @@ @QQBot.on_called_api async def handle_api_result( bot: QQBot, - exception: Optional[Exception], + exception: Exception | None, api: str, - data: Dict[str, Any], + data: dict[str, Any], result: Any ): """获取消息发送后审核状态并自动处理 AuditException 事件""" diff --git a/src/utils/__init__.py b/src/utils/__init__.py index 0c4f46d6..e105d299 100644 --- a/src/utils/__init__.py +++ b/src/utils/__init__.py @@ -7,3 +7,14 @@ @GitHub : https://github.com/Ailitonia @Software : PyCharm """ + +from .omega_common_api import BaseCommonAPI +from .omega_requests import OmegaRequests +from .process_utils import run_async_delay, semaphore_gather + +__all__ = [ + 'BaseCommonAPI', + 'OmegaRequests', + 'run_async_delay', + 'semaphore_gather', +] diff --git a/src/utils/bilibili_api/__init__.py b/src/utils/bilibili_api/__init__.py index 06546f6d..0f929a0d 100644 --- a/src/utils/bilibili_api/__init__.py +++ b/src/utils/bilibili_api/__init__.py @@ -3,18 +3,21 @@ @Date : 2022/04/11 20:25 @FileName : bilibili.py @Project : nonebot2_miya -@Description : Bilibili +@Description : bilibili API @GitHub : https://github.com/Ailitonia @Software : PyCharm """ -from .bilibili import BilibiliUser, BilibiliDynamic, BilibiliLiveRoom -from .credential_helpers import BilibiliCredential - +from .future import ( + BilibiliCredential, + BilibiliDynamic, + BilibiliLive, + BilibiliUser, +) __all__ = [ - 'BilibiliUser', 'BilibiliDynamic', - 'BilibiliLiveRoom', 'BilibiliCredential', + 'BilibiliLive', + 'BilibiliUser', ] diff --git a/src/utils/bilibili_api/_legacy/__init__.py b/src/utils/bilibili_api/_legacy/__init__.py new file mode 100644 index 00000000..c3f5d516 --- /dev/null +++ b/src/utils/bilibili_api/_legacy/__init__.py @@ -0,0 +1,9 @@ +""" +@Author : Ailitonia +@Date : 2024/10/25 10:53:24 +@FileName : legacy +@Project : omega-miya +@Description : [Deactivated]Legacy bilibili API +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" diff --git a/src/utils/bilibili_api/_legacy/api_base.py b/src/utils/bilibili_api/_legacy/api_base.py new file mode 100644 index 00000000..76fe9182 --- /dev/null +++ b/src/utils/bilibili_api/_legacy/api_base.py @@ -0,0 +1,102 @@ +""" +@Author : Ailitonia +@Date : 2024/5/30 上午12:37 +@FileName : api_base +@Project : nonebot2_miya +@Description : bilibili api 基类 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import TYPE_CHECKING, Any + +from src.utils import BaseCommonAPI +from .config import bilibili_config, bilibili_resource_config +from .exclimbwuzhi import gen_buvid_fp, gen_uuid_infoc, get_payload +from .model import BilibiliWebInterfaceNav, BilibiliWebInterfaceSpi +from .verify_utils import sign_wbi_params + +if TYPE_CHECKING: + from nonebot.internal.driver import CookieTypes + + from src.resource import TemporaryResource + + +class BilibiliCommon(BaseCommonAPI): + """Bilibili API 基类""" + + @classmethod + def _get_root_url(cls, *args, **kwargs) -> str: + return 'https://www.bilibili.com' + + @classmethod + async def _async_get_root_url(cls, *args, **kwargs) -> str: + return cls._get_root_url(*args, **kwargs) + + @classmethod + def _load_cloudflare_clearance(cls) -> bool: + return False + + @classmethod + def _get_default_headers(cls) -> dict[str, str]: + headers = cls._get_omega_requests_default_headers() + headers.update({ + 'origin': 'https://www.bilibili.com', + 'referer': 'https://www.bilibili.com/' + }) + return headers + + @classmethod + def _get_default_cookies(cls) -> 'CookieTypes': + return bilibili_config.bili_cookies + + @classmethod + async def download_resource(cls, url: str) -> 'TemporaryResource': + """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" + return await cls._download_resource( + save_folder=bilibili_resource_config.default_download_folder, url=url, + ) + + @classmethod + async def update_wbi_params(cls, params: dict[str, Any] | None = None) -> dict: + """为 wbi 接口请求参数进行 wbi 签名""" + _wbi_nav_url: str = 'https://api.bilibili.com/x/web-interface/nav' + + response = await cls._get_json(url=_wbi_nav_url) + return sign_wbi_params(nav_data=BilibiliWebInterfaceNav.model_validate(response), params=params) + + @classmethod + async def update_buvid_params(cls) -> dict[str, Any]: + """为接口激活 buvid""" + _spi_url: str = 'https://api.bilibili.com/x/frontend/finger/spi' + _exclimbwuzhi_url: str = 'https://api.bilibili.com/x/internal/gaia-gateway/ExClimbWuzhi' + + # get buvid3, buvid4 + spi_response = await cls._get_json(url=_spi_url) + spi_data = BilibiliWebInterfaceSpi.model_validate(spi_response) + + # active buvid + uuid = gen_uuid_infoc() + payload = get_payload() + + headers = cls._get_default_headers() + headers.update({ + 'origin': 'https://www.bilibili.com', + 'referer': 'https://www.bilibili.com/', + 'Content-Type': 'application/json' + }) + + cookies = bilibili_config.update_cookies_cache( + buvid3=spi_data.data.b_3, + buvid4=spi_data.data.b_4, + buvid_fp=gen_buvid_fp(payload, 31), + _uuid=uuid + ) + + await cls._post_json(url=_exclimbwuzhi_url, headers=headers, json=payload, cookies=cookies) + return cookies + + +__all__ = [ + 'BilibiliCommon', +] diff --git a/src/utils/bilibili_api/bilibili.py b/src/utils/bilibili_api/_legacy/bilibili.py similarity index 68% rename from src/utils/bilibili_api/bilibili.py rename to src/utils/bilibili_api/_legacy/bilibili.py index 72509fe4..b86919d8 100644 --- a/src/utils/bilibili_api/bilibili.py +++ b/src/utils/bilibili_api/_legacy/bilibili.py @@ -9,66 +9,28 @@ """ import warnings -from typing import Any, Literal, Optional, Sequence +from collections.abc import Sequence +from typing import Literal +from urllib.parse import unquote +from lxml import etree +from nonebot.utils import run_sync + +from src.compat import parse_json_as from .api_base import BilibiliCommon -from .config import bilibili_config -from .exclimbwuzhi import gen_buvid_fp, gen_uuid_infoc, get_payload from .model import ( - BilibiliUserModel, - BilibiliUserDynamicModel, BilibiliDynamicModel, BilibiliLiveRoomModel, + BilibiliUserDynamicModel, + BilibiliUserModel, BilibiliUsersLiveRoomModel, - BilibiliWebInterfaceNav, - BilibiliWebInterfaceSpi ) from .model.search import BaseBilibiliSearchingModel, UserSearchingModel -from .verify_utils import sign_wbi_params class Bilibili(BilibiliCommon): """Bilibili 主站方法""" - @classmethod - async def update_wbi_params(cls, params: Optional[dict[str, Any]] = None) -> dict: - """为 wbi 接口请求参数进行 wbi 签名""" - _wbi_nav_url: str = 'https://api.bilibili.com/x/web-interface/nav' - - response = await cls._get_json(url=_wbi_nav_url) - return sign_wbi_params(nav_data=BilibiliWebInterfaceNav.model_validate(response), params=params) - - @classmethod - async def update_buvid_params(cls) -> dict[str, Any]: - """为接口激活 buvid""" - _spi_url: str = 'https://api.bilibili.com/x/frontend/finger/spi' - _exclimbwuzhi_url: str = 'https://api.bilibili.com/x/internal/gaia-gateway/ExClimbWuzhi' - - # get buvid3, buvid4 - spi_response = await cls._get_json(url=_spi_url) - spi_data = BilibiliWebInterfaceSpi.model_validate(spi_response) - - # active buvid - uuid = gen_uuid_infoc() - payload = get_payload() - - headers = cls._get_default_headers() - headers.update({ - 'origin': 'https://www.bilibili.com', - 'referer': 'https://www.bilibili.com/', - 'Content-Type': 'application/json' - }) - - cookies = bilibili_config.update_bili_cookies( - buvid3=spi_data.data.b_3, - buvid4=spi_data.data.b_4, - buvid_fp=gen_buvid_fp(payload, 31), - _uuid=uuid - ) - - await cls._post_json(url=_exclimbwuzhi_url, headers=headers, json=payload, cookies=cookies) - return cookies - @classmethod async def _global_search( cls, @@ -145,13 +107,9 @@ async def _global_search( class BilibiliUser(Bilibili): - # _data_api_url = 'https://api.bilibili.com/x/space/acc/info' # Deactivated - _data_api_url = 'https://api.bilibili.com/x/space/wbi/acc/info' - _dynamic_api_url = 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history' # TODO - warnings.warn( - "The bilibili user dynamic old API seems to be deprecated and will be removed in the near future, " - "future version should change to new API instead.", + 'The bilibili user dynamic old API seems to be deprecated and will be removed in the near future, ' + 'future version should change to new API instead.', PendingDeprecationWarning, stacklevel=2, ) @@ -161,7 +119,7 @@ def __init__(self, uid: int): self.space_url = f'https://space.bilibili.com/{uid}' # 实例缓存 - self.user_model: Optional[BilibiliUserModel] = None + self.user_model: BilibiliUserModel | None = None def __repr__(self) -> str: return f'{self.__class__.__name__}(uid={self.uid})' @@ -183,18 +141,35 @@ async def search( searching_result = await cls._global_search( search_type='bili_user', page_size=36, keyword=user_name, order=order, order_sort=order_sort ) - return UserSearchingModel.model_validate(searching_result) + return UserSearchingModel.model_validate(searching_result.model_dump()) @property def mid(self) -> str: return str(self.uid) + @staticmethod + @run_sync + def _parse_user_space_w_webid(content: str) -> dict[str, str]: + """解析用户页面 __RENDER_DATA__ 内容""" + html = etree.HTML(content) + render_data = html.xpath('/html/head/script[@id="__RENDER_DATA__"]').pop(0).text + return parse_json_as(dict[str, str], unquote(render_data)) + + @classmethod + async def _query_user_space_w_webid(cls, mid: int | str) -> dict[str, str]: + """获取并解析用户页面 __RENDER_DATA__ 内容""" + user_space_url = f'https://space.bilibili.com/{mid}' + user_space_page = await cls._get_resource_as_text(url=user_space_url) + return await cls._parse_user_space_w_webid(content=user_space_page) + async def query_user_data(self) -> BilibiliUserModel: """获取并初始化用户对应 BilibiliUserModel""" + api_url = 'https://api.bilibili.com/x/space/wbi/acc/info' + if not isinstance(self.user_model, BilibiliUserModel): - params = await self.update_wbi_params({'mid': self.mid}) - cookies = await self.update_buvid_params() - user_data = await self._get_json(url=self._data_api_url, params=params, cookies=cookies) + render_data = await self._query_user_space_w_webid(mid=self.mid) + params = await self.update_wbi_params({'mid': self.mid, 'w_webid': render_data['access_id']}) + user_data = await self._get_json(url=api_url, params=params) self.user_model = BilibiliUserModel.model_validate(user_data) if not isinstance(self.user_model, BilibiliUserModel): @@ -212,43 +187,41 @@ async def query_dynamics( :param offset_dynamic_id: 获取动态起始位置 :param need_top: 是否获取置顶动态 """ + api_url = 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history' # TODO: Update to New API headers = self._get_default_headers() headers.update({ 'origin': 'https://t.bilibili.com', 'referer': 'https://t.bilibili.com/' }) params = {'host_uid': self.uid, 'offset_dynamic_id': offset_dynamic_id, 'need_top': need_top, 'platform': 'web'} - # if bilibili_config.bili_cookies: - # params.update({'csrf': bilibili_config.bili_jct, 'visitor_uid': bilibili_config.bili_dedeuserid}) - # params = await self.update_wbi_params(params) - cookies = await self.update_buvid_params() - data = await self._get_json(url=self._dynamic_api_url, params=params, headers=headers, cookies=cookies) + # 这里是风控玄学, 虽然加不加这个好像也没啥影响, 但请求数量多了好像是有点用, 就是不知道有多大用 + params = await self.update_wbi_params(params) + + data = await self._get_json(url=api_url, params=params, headers=headers) return BilibiliUserDynamicModel.model_validate(data) class BilibiliDynamic(BilibiliCommon): """Bilibili 动态""" - _dynamic_root_url = 'https://t.bilibili.com/' - _dynamic_detail_api_url = 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail' # TODO warnings.warn( - f"The bilibili user dynamic old API seems to be deprecated and will be removed in the near future, " - "future version should change to new API instead.", + 'The bilibili user dynamic old API seems to be deprecated and will be removed in the near future, ' + 'future version should change to new API instead.', PendingDeprecationWarning, stacklevel=2, ) def __init__(self, dynamic_id: int): self.dynamic_id = dynamic_id - self.dynamic_url = f'{self._dynamic_root_url}{dynamic_id}' + self.dynamic_url = f'{self.get_dynamic_root_url()}/{dynamic_id}' # 实例缓存 - self.dynamic_model: Optional[BilibiliDynamicModel] = None + self.dynamic_model: BilibiliDynamicModel | None = None @classmethod def get_dynamic_root_url(cls) -> str: - return cls._dynamic_root_url + return 'https://t.bilibili.com' @property def dy_id(self) -> str: @@ -259,6 +232,8 @@ def __repr__(self) -> str: async def query_dynamic_data(self) -> BilibiliDynamicModel: """获取并初始化动态对应 BilibiliDynamicModel""" + api_url = 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/get_dynamic_detail' # TODO: Update to New API + if not isinstance(self.dynamic_model, BilibiliDynamicModel): headers = self._get_default_headers() headers.update({ @@ -266,7 +241,7 @@ async def query_dynamic_data(self) -> BilibiliDynamicModel: 'referer': 'https://t.bilibili.com/' }) params = {'dynamic_id': self.dy_id} - dynamic_data = await self._get_json(url=self._dynamic_detail_api_url, params=params, headers=headers) + dynamic_data = await self._get_json(url=api_url, params=params, headers=headers) self.dynamic_model = BilibiliDynamicModel.model_validate(dynamic_data) if not isinstance(self.dynamic_model, BilibiliDynamicModel): @@ -276,16 +251,13 @@ async def query_dynamic_data(self) -> BilibiliDynamicModel: class BilibiliLiveRoom(BilibiliCommon): """Bilibili 直播间""" - _live_root_url = 'https://live.bilibili.com/' - _live_api_url = 'https://api.live.bilibili.com/room/v1/Room/get_info' - _live_by_uids_api_url = 'https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids' def __init__(self, room_id: int): self.room_id = room_id - self.live_room_url = f'{self._live_root_url}{room_id}' + self.live_room_url = f'{self.get_live_root_url()}/{room_id}' # 实例缓存 - self.live_room_model: Optional[BilibiliLiveRoomModel] = None + self.live_room_model: BilibiliLiveRoomModel | None = None def __repr__(self) -> str: return f'{self.__class__.__name__}(room_id={self.room_id})' @@ -294,20 +266,27 @@ def __repr__(self) -> str: def rid(self) -> str: return str(self.room_id) + @classmethod + def get_live_root_url(cls) -> str: + return 'https://live.bilibili.com' + @classmethod async def query_live_room_by_uid_list(cls, uid_list: Sequence[int | str]) -> BilibiliUsersLiveRoomModel: """根据用户 uid 列表获取这些用户的直播间信息(这个 api 没有认证方法,请不要在标头中添加 cookie)""" + api_url = 'https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids' payload = {'uids': uid_list} - live_room_data = await cls._post_json( - url=cls._live_by_uids_api_url, json=payload, no_headers=True, no_cookies=True # 该接口无需鉴权 - ) + + # 该接口无需鉴权 + live_room_data = await cls._post_json(url=api_url, json=payload, no_headers=True, no_cookies=True) return BilibiliUsersLiveRoomModel.model_validate(live_room_data) async def query_live_room_data(self) -> BilibiliLiveRoomModel: """获取并初始化直播间对应 Model""" + api_url = 'https://api.live.bilibili.com/room/v1/Room/get_info' + if not isinstance(self.live_room_model, BilibiliLiveRoomModel): params = {'id': self.rid} - live_room_data = await self._get_json(url=self._live_api_url, params=params) + live_room_data = await self._get_json(url=api_url, params=params) self.live_room_model = BilibiliLiveRoomModel.model_validate(live_room_data) if not isinstance(self.live_room_model, BilibiliLiveRoomModel): diff --git a/src/utils/bilibili_api/config.py b/src/utils/bilibili_api/_legacy/config.py similarity index 70% rename from src/utils/bilibili_api/config.py rename to src/utils/bilibili_api/_legacy/config.py index 5190766a..34907355 100644 --- a/src/utils/bilibili_api/config.py +++ b/src/utils/bilibili_api/_legacy/config.py @@ -9,7 +9,7 @@ """ from dataclasses import dataclass -from typing import Any +from typing import Any, Literal from urllib.parse import quote from nonebot import get_plugin_config, logger @@ -19,6 +19,9 @@ from src.database import SystemSettingDAL, begin_db_session from src.resource import TemporaryResource +_BILI_SETTING_NAME: Literal['bilibili_api_legacy'] = 'bilibili_api_legacy' +"""数据库系统配置表固定字段""" + class BilibiliConfig(BaseModel): """Bilibili 配置 @@ -44,28 +47,29 @@ def bili_cookies(self) -> dict[str, Any]: sessdata = ( None if self.bili_sessdata is None - else self.bili_sessdata if self.bili_sessdata.find("%") != -1 else quote(self.bili_sessdata) + else self.bili_sessdata if self.bili_sessdata.find('%') != -1 else quote(self.bili_sessdata) ) cookies = { - "SESSDATA": sessdata, - "buvid3": self.bili_buvid3, - "bili_jct": self.bili_jct, - "ac_time_value": self.bili_ac_time_value, + 'SESSDATA': sessdata, + 'buvid3': self.bili_buvid3, + 'bili_jct': self.bili_jct, + 'ac_time_value': self.bili_ac_time_value, } if self.bili_dedeuserid: - cookies.update({"DedeUserID": self.bili_dedeuserid}) + cookies.update({'DedeUserID': self.bili_dedeuserid}) + + if self._cookies_cache is not None: + cookies.update(self._cookies_cache) return cookies - def update_bili_cookies(self, **kwargs) -> dict[str, str]: + def update_cookies_cache(self, **kwargs) -> dict[str, str]: if self._cookies_cache is not None and not kwargs: return self._cookies_cache cookies = self.bili_cookies - for key, value in kwargs.items(): - if key not in cookies and value is not None: - cookies[key] = value + cookies.update(**kwargs) self._cookies_cache = cookies return cookies @@ -85,19 +89,15 @@ def clear_all(self) -> None: self.clear_cookies_cache() @staticmethod - async def _save_config_to_db(dal: SystemSettingDAL, setting_name: str, value: str | None) -> None: + async def _save_config_to_db(dal: SystemSettingDAL, setting_key: str, value: str | None) -> None: if value is None: return - try: - setting = await dal.query_unique(setting_name=setting_name) - await dal.update(id_=setting.id, setting_value=value) - except NoResultFound: - await dal.add(setting_name=setting_name, setting_value=value) + await dal.upsert(setting_name=_BILI_SETTING_NAME, setting_key=setting_key, setting_value=value) @staticmethod - async def _load_config_from_db(dal: SystemSettingDAL, setting_name: str) -> str | None: + async def _load_config_from_db(dal: SystemSettingDAL, setting_key: str) -> str | None: try: - setting = await dal.query_unique(setting_name=setting_name) + setting = await dal.query_unique(setting_name=_BILI_SETTING_NAME, setting_key=setting_key) return setting.setting_value except NoResultFound: return None @@ -105,32 +105,32 @@ async def _load_config_from_db(dal: SystemSettingDAL, setting_name: str) -> str async def save_to_database(self) -> None: async with begin_db_session() as session: dal = SystemSettingDAL(session=session) - await self._save_config_to_db(dal=dal, setting_name='bili_sessdata', value=self.bili_sessdata) - await self._save_config_to_db(dal=dal, setting_name='bili_jct', value=self.bili_jct) - await self._save_config_to_db(dal=dal, setting_name='bili_buvid3', value=self.bili_buvid3) - await self._save_config_to_db(dal=dal, setting_name='bili_dedeuserid', value=self.bili_dedeuserid) - await self._save_config_to_db(dal=dal, setting_name='bili_ac_time_value', value=self.bili_ac_time_value) + await self._save_config_to_db(dal=dal, setting_key='bili_sessdata', value=self.bili_sessdata) + await self._save_config_to_db(dal=dal, setting_key='bili_jct', value=self.bili_jct) + await self._save_config_to_db(dal=dal, setting_key='bili_buvid3', value=self.bili_buvid3) + await self._save_config_to_db(dal=dal, setting_key='bili_dedeuserid', value=self.bili_dedeuserid) + await self._save_config_to_db(dal=dal, setting_key='bili_ac_time_value', value=self.bili_ac_time_value) - async def load_from_database(self) -> "BilibiliConfig": + async def load_from_database(self) -> 'BilibiliConfig': async with begin_db_session() as session: dal = SystemSettingDAL(session=session) - bili_sessdata = await self._load_config_from_db(dal=dal, setting_name='bili_sessdata') + bili_sessdata = await self._load_config_from_db(dal=dal, setting_key='bili_sessdata') if bili_sessdata is not None: self.bili_sessdata = bili_sessdata - bili_jct = await self._load_config_from_db(dal=dal, setting_name='bili_jct') + bili_jct = await self._load_config_from_db(dal=dal, setting_key='bili_jct') if bili_jct is not None: self.bili_jct = bili_jct - bili_buvid3 = await self._load_config_from_db(dal=dal, setting_name='bili_buvid3') + bili_buvid3 = await self._load_config_from_db(dal=dal, setting_key='bili_buvid3') if bili_buvid3 is not None: self.bili_buvid3 = bili_buvid3 - bili_dedeuserid = await self._load_config_from_db(dal=dal, setting_name='bili_dedeuserid') + bili_dedeuserid = await self._load_config_from_db(dal=dal, setting_key='bili_dedeuserid') if bili_dedeuserid is not None: self.bili_dedeuserid = bili_dedeuserid - bili_ac_time_value = await self._load_config_from_db(dal=dal, setting_name='bili_ac_time_value') + bili_ac_time_value = await self._load_config_from_db(dal=dal, setting_key='bili_ac_time_value') if bili_ac_time_value is not None: self.bili_ac_time_value = bili_ac_time_value @@ -149,10 +149,10 @@ class BilibiliLocalResourceConfig: bilibili_config = get_plugin_config(BilibiliConfig) except ValidationError as e: import sys + logger.opt(colors=True).critical(f'Bilibili 配置格式验证失败, 错误信息:\n{e}') sys.exit(f'Bilibili 配置格式验证失败, {e}') - __all__ = [ 'bilibili_config', 'bilibili_resource_config', diff --git a/src/utils/bilibili_api/credential_helpers.py b/src/utils/bilibili_api/_legacy/credential_helpers.py similarity index 90% rename from src/utils/bilibili_api/credential_helpers.py rename to src/utils/bilibili_api/_legacy/credential_helpers.py index cd686d56..b49a12df 100644 --- a/src/utils/bilibili_api/credential_helpers.py +++ b/src/utils/bilibili_api/_legacy/credential_helpers.py @@ -15,36 +15,35 @@ import binascii import re import time -from urllib.parse import urlparse, parse_qs +from urllib.parse import parse_qs, urlparse import qrcode from Cryptodome.Cipher import PKCS1_OAEP from Cryptodome.Hash import SHA256 from Cryptodome.PublicKey import RSA from lxml import etree -from nonebot import logger, get_driver +from nonebot import get_driver, logger from nonebot.utils import run_sync from src.resource import TemporaryResource -from src.service import scheduler from .api_base import BilibiliCommon from .config import bilibili_config, bilibili_resource_config from .model import ( - BilibiliWebInterfaceNav, + BilibiliWebConfirmRefreshInfo, BilibiliWebCookieInfo, + BilibiliWebCookieRefreshInfo, + BilibiliWebInterfaceNav, BilibiliWebQrcodeGenerateInfo, BilibiliWebQrcodePollInfo, - BilibiliWebCookieRefreshInfo, - BilibiliWebConfirmRefreshInfo ) -_PUB_KEY = RSA.importKey('''\ +_PUB_KEY = RSA.importKey("""\ -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLgd2OAkcGVtoE3ThUREbio0Eg Uc/prcajMKXvkCKFCWhJYJcLkcM2DKKcSeFpD/j6Boy538YXnR6VhcuUJOhH2x71 nzPjfdTcqMz7djHum0qSZA0AyCBDABUqCrfNgCiJ00Ra7GmRj+YCK1NJEuewlb40 JNrRuoEUXpabUzGB8QIDAQAB ------END PUBLIC KEY-----''') +-----END PUBLIC KEY-----""") class BilibiliCredential(BilibiliCommon): @@ -156,14 +155,16 @@ async def check_valid(cls) -> bool: if verify.code != 0 or not verify.data.isLogin: bilibili_config.clear_all() - logger.opt(colors=True).warning(f'Bilibili | Cookie 验证失败, 登录状态异常, {verify.message}') + logger.opt(colors=True).warning( + f'Bilibili | Cookie 验证失败, 登录状态异常, {verify.message}') return False elif verify.data.mid != bilibili_config.bili_dedeuserid: bilibili_config.clear_all() logger.opt(colors=True).warning('Bilibili | Cookie 验证失败, 登录状态异常, 用户 UID 不匹配') return False else: - logger.opt(colors=True).success(f'Bilibili | Cookie 已验证, 登录用户: {verify.data.uname}') + logger.opt(colors=True).success( + f'Bilibili | Cookie 已验证, 登录用户: {verify.data.uname}') return True @classmethod @@ -199,10 +200,10 @@ async def refresh_cookies(cls) -> bool: refresh_csrf = await cls.get_refresh_csrf() old_refresh_token = bilibili_config.bili_ac_time_value params = { - "csrf": bilibili_config.bili_jct, - "refresh_csrf": refresh_csrf, - "refresh_token": old_refresh_token, - "source": "main_web", + 'csrf': bilibili_config.bili_jct, + 'refresh_csrf': refresh_csrf, + 'refresh_token': old_refresh_token, + 'source': 'main_web', } response = await cls._request_post(url=url, params=params, cookies=bilibili_config.bili_cookies) @@ -226,7 +227,12 @@ async def refresh_cookies(cls) -> bool: if new_cookies['DedeUserID']: bilibili_config.bili_dedeuserid = new_cookies['DedeUserID'] bilibili_config.bili_ac_time_value = refresh_info.data.refresh_token + bilibili_config.update_cookies_cache(**new_cookies) + + # 激活 buvid + await cls.update_buvid_params() + # 确认更新并注销旧 token confirm_result = await cls.confirm_cookies_refresh( csrf=new_cookies['bili_jct'], refresh_token=old_refresh_token ) @@ -246,7 +252,9 @@ async def _refresh_bilibili_login_status() -> None: is_valid = await bc.check_valid() if not is_valid: logger.opt(colors=True).warning('Bilibili | 用户 Cookies 未配置或验证失败, 部分功能可能不可用') + return + await bc.update_buvid_params() need_refresh = await bc.check_need_refresh() if need_refresh: logger.opt(colors=True).warning('Bilibili | 用户 Cookies 需要刷新, 正在尝试刷新中') @@ -268,22 +276,6 @@ async def _load_and_refresh_bilibili_login_status() -> None: logger.opt(colors=True).error(f'Bilibili | 用户 Cookies 刷新失败, 请尝试重新登录, {e}') -@scheduler.scheduled_job( - 'cron', - hour='*/8', - minute='23', - second='23', - id='bilibili_login_status_refresh_monitor', - coalesce=True, - misfire_grace_time=120 -) -async def _bilibili_login_status_refresh_monitor() -> None: - try: - await _refresh_bilibili_login_status() - except Exception as e: - logger.opt(colors=True).error(f'Bilibili | 用户 Cookies 刷新失败, 请尝试重新登录, {e}') - - __all__ = [ 'BilibiliCredential', ] diff --git a/src/utils/bilibili_api/_legacy/exclimbwuzhi.py b/src/utils/bilibili_api/_legacy/exclimbwuzhi.py new file mode 100644 index 00000000..0677703d --- /dev/null +++ b/src/utils/bilibili_api/_legacy/exclimbwuzhi.py @@ -0,0 +1,323 @@ +""" +@Author : Ailitonia +@Date : 2024/3/25 0:30 +@FileName : exclimbwuzhi +@Project : nonebot2_miya +@Description : 激活 buvid3 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm + +Web 风控相关问题: buvid3, buvid4 获取及激活(ExClimbWuzhi 上传设备指纹消息)见: +https://github.com/SocialSisterYi/bilibili-API-collect/issues/933 + +相关风控问题见: +https://github.com/SocialSisterYi/bilibili-API-collect/issues/686 +https://github.com/SocialSisterYi/bilibili-API-collect/issues/868 + +Reference: https://github.com/Nemo2011/bilibili-api/commit/f7de473bc42d60604372f80d06244e45a08bdbb4 +From: https://github.com/Nemo2011/bilibili-api/blob/f7de473bc42d60604372f80d06244e45a08bdbb4/bilibili_api/utils/exclimbwuzhi.py +""" + +import io +import random +import struct +import time + +import ujson as json + +MOD = 1 << 64 + + +def get_time_milli() -> int: + return int(time.time() * 1000) + + +def rotate_left(x: int, k: int) -> int: + bin_str = bin(x)[2:].rjust(64, '0') + return int(bin_str[k:] + bin_str[:k], base=2) + + +def gen_uuid_infoc() -> str: + t = get_time_milli() % 100000 + mp = list('123456789ABCDEF') + ['10'] + pck = [8, 4, 4, 4, 12] + + def gen_part(x) -> str: + return ''.join([random.choice(mp) for _ in range(x)]) + + return '-'.join([gen_part(x) for x in pck]) + str(t).ljust(5, '0') + 'infoc' + + +def gen_b_lsid() -> str: + ret = '' + for _ in range(8): + ret += hex(random.randint(0, 15))[2:].upper() + ret = f'{ret}_{hex(get_time_milli())[2:].upper()}' + return ret + + +def gen_buvid_fp(key: str, seed: int): + source = io.BytesIO(bytes(key, 'ascii')) + m = murmur3_x64_128(source, seed) + return f'{hex(m & (MOD - 1))[2:]}{hex(m >> 64)[2:]}' + + +def murmur3_x64_128(source: io.BufferedIOBase, seed: int) -> int: # type: ignore + C1 = 0x87C3_7B91_1142_53D5 + C2 = 0x4CF5_AD43_2745_937F + C3 = 0x52DC_E729 + C4 = 0x3849_5AB5 + R1, R2, R3, M = 27, 31, 33, 5 + h1, h2 = seed, seed + processed = 0 + while 1: + read = source.read(16) + processed += len(read) + if len(read) == 16: + k1 = struct.unpack('= 15: + k2 ^= int(read[14]) << 48 + if len(read) >= 14: + k2 ^= int(read[13]) << 40 + if len(read) >= 13: + k2 ^= int(read[12]) << 32 + if len(read) >= 12: + k2 ^= int(read[11]) << 24 + if len(read) >= 11: + k2 ^= int(read[10]) << 16 + if len(read) >= 10: + k2 ^= int(read[9]) << 8 + if len(read) >= 9: + k2 ^= int(read[8]) + k2 = rotate_left(k2 * C2 % MOD, R3) * C1 % MOD + h2 ^= k2 + if len(read) >= 8: + k1 ^= int(read[7]) << 56 + if len(read) >= 7: + k1 ^= int(read[6]) << 48 + if len(read) >= 6: + k1 ^= int(read[5]) << 40 + if len(read) >= 5: + k1 ^= int(read[4]) << 32 + if len(read) >= 4: + k1 ^= int(read[3]) << 24 + if len(read) >= 3: + k1 ^= int(read[2]) << 16 + if len(read) >= 2: + k1 ^= int(read[1]) << 8 + if len(read) >= 1: + k1 ^= int(read[0]) + k1 = rotate_left(k1 * C1 % MOD, R2) * C2 % MOD + h1 ^= k1 + + +def fmix64(k: int) -> int: + C1 = 0xFF51_AFD7_ED55_8CCD + C2 = 0xC4CE_B9FE_1A85_EC53 + R = 33 + tmp = k + tmp ^= tmp >> R + tmp = tmp * C1 % MOD + tmp ^= tmp >> R + tmp = tmp * C2 % MOD + tmp ^= tmp >> R + return tmp + + +def get_payload() -> str: + content = { + '3064': 1, + '5062': get_time_milli(), + '03bf': 'https%3A%2F%2Fwww.bilibili.com%2F', + '39c8': '333.788.fp.risk', + '34f1': '', + 'd402': '', + '654a': '', + '6e7c': '839x959', + '3c43': { + '2673': 0, + '5766': 24, + '6527': 0, + '7003': 1, + '807e': 1, + 'b8ce': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15', + '641c': 0, + '07a4': 'en-US', + '1c57': 'not available', + '0bd0': 8, + '748e': [900, 1440], + 'd61f': [875, 1440], + 'fc9d': -480, + '6aa9': 'Asia/Shanghai', + '75b8': 1, + '3b21': 1, + '8a1c': 0, + 'd52f': 'not available', + 'adca': 'MacIntel', + '80c9': [ + [ + 'PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'Chrome PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'Chromium PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'Microsoft Edge PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'WebKit built-in PDF', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + ], + '13ab': '0dAAAAAASUVORK5CYII=', + 'bfe9': 'QgAAEIQAACEIAABCCQN4FXANGq7S8KTZayAAAAAElFTkSuQmCC', + 'a3c1': [ + 'extensions:ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_compression_bptc;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_astc;WEBGL_compressed_texture_etc;WEBGL_compressed_texture_etc1;WEBGL_compressed_texture_pvrtc;WEBKIT_WEBGL_compressed_texture_pvrtc;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw', + 'webgl aliased line width range:[1, 1]', + 'webgl aliased point size range:[1, 511]', + 'webgl alpha bits:8', + 'webgl antialiasing:yes', + 'webgl blue bits:8', + 'webgl depth bits:24', + 'webgl green bits:8', + 'webgl max anisotropy:16', + 'webgl max combined texture image units:32', + 'webgl max cube map texture size:16384', + 'webgl max fragment uniform vectors:1024', + 'webgl max render buffer size:16384', + 'webgl max texture image units:16', + 'webgl max texture size:16384', + 'webgl max varying vectors:30', + 'webgl max vertex attribs:16', + 'webgl max vertex texture image units:16', + 'webgl max vertex uniform vectors:1024', + 'webgl max viewport dims:[16384, 16384]', + 'webgl red bits:8', + 'webgl renderer:WebKit WebGL', + 'webgl shading language version:WebGL GLSL ES 1.0 (1.0)', + 'webgl stencil bits:0', + 'webgl vendor:WebKit', + 'webgl version:WebGL 1.0', + 'webgl unmasked vendor:Apple Inc.', + 'webgl unmasked renderer:Apple GPU', + 'webgl vertex shader high float precision:23', + 'webgl vertex shader high float precision rangeMin:127', + 'webgl vertex shader high float precision rangeMax:127', + 'webgl vertex shader medium float precision:23', + 'webgl vertex shader medium float precision rangeMin:127', + 'webgl vertex shader medium float precision rangeMax:127', + 'webgl vertex shader low float precision:23', + 'webgl vertex shader low float precision rangeMin:127', + 'webgl vertex shader low float precision rangeMax:127', + 'webgl fragment shader high float precision:23', + 'webgl fragment shader high float precision rangeMin:127', + 'webgl fragment shader high float precision rangeMax:127', + 'webgl fragment shader medium float precision:23', + 'webgl fragment shader medium float precision rangeMin:127', + 'webgl fragment shader medium float precision rangeMax:127', + 'webgl fragment shader low float precision:23', + 'webgl fragment shader low float precision rangeMin:127', + 'webgl fragment shader low float precision rangeMax:127', + 'webgl vertex shader high int precision:0', + 'webgl vertex shader high int precision rangeMin:31', + 'webgl vertex shader high int precision rangeMax:30', + 'webgl vertex shader medium int precision:0', + 'webgl vertex shader medium int precision rangeMin:31', + 'webgl vertex shader medium int precision rangeMax:30', + 'webgl vertex shader low int precision:0', + 'webgl vertex shader low int precision rangeMin:31', + 'webgl vertex shader low int precision rangeMax:30', + 'webgl fragment shader high int precision:0', + 'webgl fragment shader high int precision rangeMin:31', + 'webgl fragment shader high int precision rangeMax:30', + 'webgl fragment shader medium int precision:0', + 'webgl fragment shader medium int precision rangeMin:31', + 'webgl fragment shader medium int precision rangeMax:30', + 'webgl fragment shader low int precision:0', + 'webgl fragment shader low int precision rangeMin:31', + 'webgl fragment shader low int precision rangeMax:30', + ], + '6bc5': 'Apple Inc.~Apple GPU', + 'ed31': 0, + '72bd': 0, + '097b': 0, + '52cd': [0, 0, 0], + 'a658': [ + 'Andale Mono', + 'Arial', + 'Arial Black', + 'Arial Hebrew', + 'Arial Narrow', + 'Arial Rounded MT Bold', + 'Arial Unicode MS', + 'Comic Sans MS', + 'Courier', + 'Courier New', + 'Geneva', + 'Georgia', + 'Helvetica', + 'Helvetica Neue', + 'Impact', + 'LUCIDA GRANDE', + 'Microsoft Sans Serif', + 'Monaco', + 'Palatino', + 'Tahoma', + 'Times', + 'Times New Roman', + 'Trebuchet MS', + 'Verdana', + 'Wingdings', + 'Wingdings 2', + 'Wingdings 3', + ], + 'd02f': '124.04345259929687', + }, + '54ef': '{"in_new_ab":true,"ab_version":{"remove_back_version":"REMOVE","login_dialog_version":"V_PLAYER_PLAY_TOAST","open_recommend_blank":"SELF","storage_back_btn":"HIDE","call_pc_app":"FORBID","clean_version_old":"GO_NEW","optimize_fmp_version":"LOADED_METADATA","for_ai_home_version":"V_OTHER","bmg_fallback_version":"DEFAULT","ai_summary_version":"SHOW","weixin_popup_block":"ENABLE","rcmd_tab_version":"DISABLE","in_new_ab":true},"ab_split_num":{"remove_back_version":11,"login_dialog_version":43,"open_recommend_blank":90,"storage_back_btn":87,"call_pc_app":47,"clean_version_old":46,"optimize_fmp_version":28,"for_ai_home_version":38,"bmg_fallback_version":86,"ai_summary_version":466,"weixin_popup_block":45,"rcmd_tab_version":90,"in_new_ab":0},"pageVersion":"new_video","videoGoOldVersion":-1}', + '8b94': 'https%3A%2F%2Fwww.bilibili.com%2F', + 'df35': '2D9BA3CF-B1ED-1674-2492-CF103D9EFACFE46196infoc', + '07a4': 'en-US', + '5f45': None, + 'db46': 0, + } + return json.dumps( + {'payload': json.dumps(content, separators=(',', ':'))}, + separators=(',', ':'), + ) + + +__all__ = [ + 'gen_buvid_fp', + 'gen_uuid_infoc', + 'get_payload', +] diff --git a/src/utils/bilibili_api/model/__init__.py b/src/utils/bilibili_api/_legacy/model/__init__.py similarity index 89% rename from src/utils/bilibili_api/model/__init__.py rename to src/utils/bilibili_api/_legacy/model/__init__.py index 6dd53804..d2340b24 100644 --- a/src/utils/bilibili_api/model/__init__.py +++ b/src/utils/bilibili_api/_legacy/model/__init__.py @@ -8,21 +8,20 @@ @Software : PyCharm """ -from .dynamic import BilibiliDynamicCard, BilibiliUserDynamicModel, BilibiliDynamicModel +from .dynamic import BilibiliDynamicCard, BilibiliDynamicModel, BilibiliUserDynamicModel from .interface import ( + BilibiliWebConfirmRefreshInfo, + BilibiliWebCookieInfo, + BilibiliWebCookieRefreshInfo, BilibiliWebInterfaceNav, BilibiliWebInterfaceSpi, - BilibiliWebCookieInfo, BilibiliWebQrcodeGenerateInfo, BilibiliWebQrcodePollInfo, - BilibiliWebCookieRefreshInfo, - BilibiliWebConfirmRefreshInfo ) from .live_room import BilibiliLiveRoomModel, BilibiliUsersLiveRoomModel from .search import UserSearchingModel from .user import BilibiliUserModel - __all__ = [ 'BilibiliDynamicCard', 'BilibiliDynamicModel', diff --git a/src/utils/bilibili_api/model/base_model.py b/src/utils/bilibili_api/_legacy/model/base_model.py similarity index 100% rename from src/utils/bilibili_api/model/base_model.py rename to src/utils/bilibili_api/_legacy/model/base_model.py diff --git a/src/utils/bilibili_api/model/dynamic.py b/src/utils/bilibili_api/_legacy/model/dynamic.py similarity index 87% rename from src/utils/bilibili_api/model/dynamic.py rename to src/utils/bilibili_api/_legacy/model/dynamic.py index c7f9b3e2..a2c03f81 100644 --- a/src/utils/bilibili_api/model/dynamic.py +++ b/src/utils/bilibili_api/_legacy/model/dynamic.py @@ -8,9 +8,9 @@ @Software : PyCharm """ -from typing import Any, Literal, Optional +from typing import Any, Literal -from pydantic import Json, field_validator, model_validator +from pydantic import Field, Json, field_validator, model_validator from src.compat import AnyHttpUrlStr as AnyHttpUrl from .base_model import BaseBilibiliModel @@ -52,14 +52,14 @@ class BilibiliDynamicCardDesc(BaseBilibiliModel): orig_type: int pre_dy_id: int = 0 orig_dy_id: int = 0 - origin: Optional[BilibiliDynamicCardDescOrigin] = None + origin: BilibiliDynamicCardDescOrigin | None = None class _StdCardOutputData(BaseBilibiliModel): """用于外部模块使用的标准化动态 Card 导出数据""" content: str # 动态主体文本内容 text: str # 输出文本内容 - img_urls: list[AnyHttpUrl] = [] + img_urls: list[AnyHttpUrl] = Field(default_factory=list) class _BaseCardType(BaseBilibiliModel): @@ -87,6 +87,7 @@ class _UserInfo(BaseBilibiliModel): class _Item(BaseBilibiliModel): """内部内容信息字段""" + class _Picture(BaseBilibiliModel): img_width: int img_height: int @@ -94,11 +95,11 @@ class _Picture(BaseBilibiliModel): img_src: AnyHttpUrl id: int - category: Optional[str] = None # Deactivated + category: str | None = None # Deactivated description: str # 为文字内容 pictures: list[_Picture] # 图片内容 pictures_count: int - title: Optional[str] = None # Deactivated + title: str | None = None # Deactivated verify_type: int = 2 user: _UserInfo @@ -131,7 +132,7 @@ class _Item(BaseBilibiliModel): rp_id: int uid: int content: str - timestamp: Optional[int] = None # Deactivated + timestamp: int | None = None # Deactivated ctrl: Any reply: Any @@ -160,13 +161,13 @@ class _Owner(BaseBilibiliModel): verify_type: int = 8 aid: int # 视频avid cid: int # 视频cid - copyright: Optional[int] = None # [Deactivated] 原创信息, 1为原创, 2为转载 + copyright: int | None = None # [Deactivated] 原创信息, 1为原创, 2为转载 dynamic: str # 动态文字内容 title: str # 视频标题 tname: str # 视频分区名称 desc: str # 视频简介 owner: _Owner - first_frame: Optional[AnyHttpUrl | str] = None # 视频第一帧图片 + first_frame: AnyHttpUrl | str | None = None # 视频第一帧图片 pic: AnyHttpUrl # 视频封面 videos: int # 视频数 @@ -233,16 +234,16 @@ class _Author(BaseBilibiliModel): verify_type: int = 64 id: int - category: Optional[_Category] = None # Deactivated - categories: Optional[list[_Category]] = None # Deactivated + category: _Category | None = None # Deactivated + categories: list[_Category] | None = None # Deactivated title: str summary: str - banner_url: Optional[AnyHttpUrl | str] = None # [Deactivated] 是否原创 + banner_url: AnyHttpUrl | str | None = None # [Deactivated] 是否原创 author: _Author image_urls: list[AnyHttpUrl] publish_time: int origin_image_urls: list[AnyHttpUrl] # 源图片地址(这里才是真·头图) - original: Optional[int] = None # [Deactivated] 是否原创 + original: int | None = None # [Deactivated] 是否原创 @property def user_name(self) -> str: @@ -302,7 +303,7 @@ class CardType2048Active(_BaseCardType): class _Sketch(BaseBilibiliModel): title: str - desc_text: Optional[str] = None + desc_text: str | None = None class _Vest(BaseBilibiliModel): content: str @@ -389,7 +390,7 @@ class _LivePlayInfo(BaseBilibiliModel): verify_type: int = 4308 live_play_info: _LivePlayInfo - live_record_info: Optional[str] = None + live_record_info: str | None = None style: int type: int @@ -420,32 +421,21 @@ class _Item(BaseBilibiliModel): orig_dy_id: int pre_dy_id: int orig_type: int - timestamp: Optional[int] = None # Deactivated + timestamp: int | None = None # Deactivated ctrl: Any reply: Any - miss: Optional[int] = None - tips: Optional[str] = None + miss: int | None = None + tips: str | None = None verify_type: int = 1 user: _UserInfo # 转发者用户信息 item: _Item # 转发相关信息 # 被转发动态信息, 套娃, (注意多次转发后原动态一直是最开始的那个, 所以源动态类型不可能也是转发) - origin: Optional[ - Json[CardType2OriginalWithImage] - | Json[CardType4OriginalWithoutImage] - | Json[CardType8Video] - # | Json[CardType16ShortVideo] - # | Json[CardType32Anime] - | Json[CardType64Article] - | Json[CardType256Music] - | Json[CardType512Anime] - | Json[CardType2048Active] - | Json[CardType4200LiveRoom] - | Json[CardType4300MediaListShare] - | Json[CardType4308LiveRoom] - | Literal['源动态已被作者删除', '源动态不见了', '直播结束了', ''] - ] # 原动态被删 origin 字段返回 message 是谁整出来的傻逼玩意儿 - origin_user: Optional[BilibiliDynamicCardDescUserProfile] = None # 被转发用户信息 + origin: Json[CardType2OriginalWithImage] | Json[CardType4OriginalWithoutImage] | Json[CardType8Video] | Json[ + CardType64Article] | Json[CardType256Music] | Json[CardType512Anime] | Json[CardType2048Active] | Json[ + CardType4200LiveRoom] | Json[CardType4300MediaListShare] | Json[CardType4308LiveRoom] | Literal[ + '源动态已被作者删除', '源动态不见了', '直播结束了', ''] | None # 原动态被删 origin 字段返回 message 是谁整出来的傻逼玩意儿 + origin_user: BilibiliDynamicCardDescUserProfile | None = None # 被转发用户信息 @property def user_name(self) -> str: @@ -472,19 +462,19 @@ class BilibiliDynamicCard(BaseBilibiliModel): """Bilibili 动态 Card""" desc: BilibiliDynamicCardDesc card: ( - Json[CardType1Forward] - | Json[CardType2OriginalWithImage] - | Json[CardType4OriginalWithoutImage] - | Json[CardType8Video] - # | Json[CardType16ShortVideo] - # | Json[CardType32Anime] - | Json[CardType64Article] - | Json[CardType256Music] - | Json[CardType512Anime] - | Json[CardType2048Active] - | Json[CardType4200LiveRoom] - | Json[CardType4300MediaListShare] - | Json[CardType4308LiveRoom] + Json[CardType1Forward] + | Json[CardType2OriginalWithImage] + | Json[CardType4OriginalWithoutImage] + | Json[CardType8Video] + # | Json[CardType16ShortVideo] + # | Json[CardType32Anime] + | Json[CardType64Article] + | Json[CardType256Music] + | Json[CardType512Anime] + | Json[CardType2048Active] + | Json[CardType4200LiveRoom] + | Json[CardType4300MediaListShare] + | Json[CardType4308LiveRoom] ) @property @@ -499,7 +489,7 @@ def output_img_urls(self) -> list[AnyHttpUrl]: class BilibiliUserDynamicData(BaseBilibiliModel): """Bilibili 用户动态 Data""" has_more: int - cards: list[BilibiliDynamicCard] = [] + cards: list[BilibiliDynamicCard] = Field(default_factory=list) next_offset: int @model_validator(mode='before') @@ -527,7 +517,7 @@ def desc_type_must_equal_card_type(cls, v): class BilibiliUserDynamicModel(BaseBilibiliModel): """Bilibili 用户动态 Model""" code: int - data: Optional[BilibiliUserDynamicData] = None + data: BilibiliUserDynamicData | None = None message: str = '' msg: str = '' @@ -558,7 +548,7 @@ def desc_type_must_equal_card_type(cls, v): class BilibiliDynamicModel(BaseBilibiliModel): """Bilibili 单个动态 Model""" code: int - data: Optional[BilibiliDynamicData] = None + data: BilibiliDynamicData | None = None message: str = '' msg: str = '' diff --git a/src/utils/bilibili_api/model/interface.py b/src/utils/bilibili_api/_legacy/model/interface.py similarity index 97% rename from src/utils/bilibili_api/model/interface.py rename to src/utils/bilibili_api/_legacy/model/interface.py index 98bc535e..7092fb98 100644 --- a/src/utils/bilibili_api/model/interface.py +++ b/src/utils/bilibili_api/_legacy/model/interface.py @@ -8,8 +8,6 @@ @Software : PyCharm """ -from typing import Optional - from src.compat import AnyHttpUrlStr as AnyHttpUrl from .base_model import BaseBilibiliModel @@ -23,8 +21,8 @@ class WbiImg(BaseBilibiliModel): class BilibiliWebInterfaceNavData(BaseBilibiliModel): isLogin: bool wbi_img: WbiImg - uname: Optional[str] = None - mid: Optional[str] = None + uname: str | None = None + mid: str | None = None class BilibiliWebInterfaceNav(BaseBilibiliModel): diff --git a/src/utils/bilibili_api/model/live_room.py b/src/utils/bilibili_api/_legacy/model/live_room.py similarity index 95% rename from src/utils/bilibili_api/model/live_room.py rename to src/utils/bilibili_api/_legacy/model/live_room.py index 907f6696..6b1083e4 100644 --- a/src/utils/bilibili_api/model/live_room.py +++ b/src/utils/bilibili_api/_legacy/model/live_room.py @@ -9,7 +9,6 @@ """ from datetime import datetime -from typing import Optional from pydantic import field_validator from pytz import timezone @@ -48,7 +47,7 @@ def time_zone_conversion(cls, v): class BilibiliLiveRoomModel(BaseBilibiliModel): """Bilibili 直播间 Model""" code: int - data: Optional[BilibiliLiveRoomDataModel] = None + data: BilibiliLiveRoomDataModel | None = None msg: str = '' message: str = '' diff --git a/src/utils/bilibili_api/model/search.py b/src/utils/bilibili_api/_legacy/model/search.py similarity index 72% rename from src/utils/bilibili_api/model/search.py rename to src/utils/bilibili_api/_legacy/model/search.py index 3f3a8652..01053663 100644 --- a/src/utils/bilibili_api/model/search.py +++ b/src/utils/bilibili_api/_legacy/model/search.py @@ -8,20 +8,16 @@ @Software : PyCharm """ -from typing import Generic, TypeVar, Optional - -from pydantic import BaseModel +from pydantic import Field from .base_model import BaseBilibiliModel -T = TypeVar("T") - -class BaseBilibiliSearchingDataModel(BaseBilibiliModel, BaseModel, Generic[T]): +class BaseBilibiliSearchingDataModel[T](BaseBilibiliModel): """Bilibili 搜索结果 Data 基类""" cost_time: dict egg_hit: int - exp_list: Optional[dict] = None + exp_list: dict | None = None numPages: int numResults: int page: int @@ -30,13 +26,13 @@ class BaseBilibiliSearchingDataModel(BaseBilibiliModel, BaseModel, Generic[T]): seid: str show_column: int suggest_keyword: str - result: list[T] = [] + result: list[T] = Field(default_factory=list) -class BaseBilibiliSearchingModel(BaseBilibiliModel, BaseModel, Generic[T]): +class BaseBilibiliSearchingModel[T: BaseBilibiliSearchingDataModel](BaseBilibiliModel): """Bilibili 搜索结果 Model 基类""" code: int - data: Optional[BaseBilibiliSearchingDataModel[T]] = None + data: T | None = None message: str @property @@ -67,15 +63,15 @@ class UserSearchingResult(BaseBilibiliModel): class UserSearchingData(BaseBilibiliSearchingDataModel[UserSearchingResult]): """用户搜索 Data""" - result: list[UserSearchingResult] = [] + result: list[UserSearchingResult] = Field(default_factory=list) -class UserSearchingModel(BaseBilibiliSearchingModel[UserSearchingResult]): +class UserSearchingModel(BaseBilibiliSearchingModel[UserSearchingData]): """用户搜索 Model""" - data: Optional[UserSearchingData] = None + data: UserSearchingData | None = None __all__ = [ 'BaseBilibiliSearchingModel', - 'UserSearchingModel' + 'UserSearchingModel', ] diff --git a/src/utils/bilibili_api/model/user.py b/src/utils/bilibili_api/_legacy/model/user.py similarity index 91% rename from src/utils/bilibili_api/model/user.py rename to src/utils/bilibili_api/_legacy/model/user.py index fde75ca9..ef73c8cd 100644 --- a/src/utils/bilibili_api/model/user.py +++ b/src/utils/bilibili_api/_legacy/model/user.py @@ -8,9 +8,8 @@ @Software : PyCharm """ -from typing import Optional - from src.compat import AnyHttpUrlStr as AnyHttpUrl + from .base_model import BaseBilibiliModel @@ -35,14 +34,14 @@ class BilibiliUserDataModel(BaseBilibiliModel): sign: str level: int top_photo: AnyHttpUrl - live_room: Optional[BilibiliUserLiveRoom] = None + live_room: BilibiliUserLiveRoom | None = None is_senior_member: int class BilibiliUserModel(BaseBilibiliModel): """Bilibili 用户 Model""" code: int - data: Optional[BilibiliUserDataModel] = None + data: BilibiliUserDataModel | None = None message: str @property diff --git a/src/utils/bilibili_api/verify_utils.py b/src/utils/bilibili_api/_legacy/verify_utils.py similarity index 89% rename from src/utils/bilibili_api/verify_utils.py rename to src/utils/bilibili_api/_legacy/verify_utils.py index c50d2988..0519a7e5 100644 --- a/src/utils/bilibili_api/verify_utils.py +++ b/src/utils/bilibili_api/_legacy/verify_utils.py @@ -12,7 +12,7 @@ import urllib.parse from functools import reduce from hashlib import md5 -from typing import Any, Optional +from typing import Any from .model import BilibiliWebInterfaceNav @@ -29,7 +29,7 @@ def get_mixin_key(orig: str) -> str: return reduce(lambda s, i: s + orig[i], __MIXIN_KEY_ENC_TAB, '')[:32] -def enc_wbi(params: Optional[dict[str, Any]], img_key: str, sub_key: str) -> dict[str, Any]: +def enc_wbi(params: dict[str, Any] | None, img_key: str, sub_key: str) -> dict[str, Any]: """为请求参数进行 wbi 签名""" mixin_key = get_mixin_key(img_key + sub_key) curr_time = round(time.time()) @@ -56,7 +56,7 @@ def extract_key_from_wbi_image(url: Any) -> str: return str(url).rsplit('/', 1)[-1].split('.')[0] -def sign_wbi_params(nav_data: BilibiliWebInterfaceNav, params: Optional[dict[str, Any]]) -> dict[str, Any]: +def sign_wbi_params(nav_data: BilibiliWebInterfaceNav, params: dict[str, Any] | None) -> dict[str, Any]: img_key = extract_key_from_wbi_image(url=nav_data.data.wbi_img.img_url) sub_key = extract_key_from_wbi_image(url=nav_data.data.wbi_img.sub_url) return enc_wbi(params=params, img_key=img_key, sub_key=sub_key) diff --git a/src/utils/bilibili_api/api_base.py b/src/utils/bilibili_api/api_base.py deleted file mode 100644 index ba3199fe..00000000 --- a/src/utils/bilibili_api/api_base.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2024/5/30 上午12:37 -@FileName : api_base -@Project : nonebot2_miya -@Description : bilibili api 基类 -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from typing import TYPE_CHECKING - -from src.utils.common_api import BaseCommonAPI -from .config import bilibili_config, bilibili_resource_config - -if TYPE_CHECKING: - from nonebot.internal.driver import CookieTypes - from src.resource import TemporaryResource - - -class BilibiliCommon(BaseCommonAPI): - """Bilibili API 基类""" - - @classmethod - def _get_root_url(cls, *args, **kwargs) -> str: - return 'https://www.bilibili.com' - - @classmethod - async def _async_get_root_url(cls, *args, **kwargs) -> str: - return cls._get_root_url(*args, **kwargs) - - @classmethod - def _load_cloudflare_clearance(cls) -> bool: - return False - - @classmethod - def _get_default_headers(cls) -> dict[str, str]: - headers = cls._get_omega_requests_default_headers() - headers.update({ - 'origin': 'https://www.bilibili.com', - 'referer': 'https://www.bilibili.com/' - }) - return headers - - @classmethod - def _get_default_cookies(cls) -> "CookieTypes": - return bilibili_config.bili_cookies - - @classmethod - async def download_resource(cls, url: str) -> "TemporaryResource": - """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" - return await cls._download_resource( - save_folder=bilibili_resource_config.default_download_folder, url=url, - ) - - -__all__ = [ - 'BilibiliCommon', -] diff --git a/src/utils/bilibili_api/exclimbwuzhi.py b/src/utils/bilibili_api/exclimbwuzhi.py deleted file mode 100644 index d03d9dfa..00000000 --- a/src/utils/bilibili_api/exclimbwuzhi.py +++ /dev/null @@ -1,323 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2024/3/25 0:30 -@FileName : exclimbwuzhi -@Project : nonebot2_miya -@Description : 激活 buvid3 -@GitHub : https://github.com/Ailitonia -@Software : PyCharm - -Web 风控相关问题: buvid3, buvid4 获取及激活(ExClimbWuzhi 上传设备指纹消息)见: -https://github.com/SocialSisterYi/bilibili-API-collect/issues/933 - -相关风控问题见: -https://github.com/SocialSisterYi/bilibili-API-collect/issues/686 -https://github.com/SocialSisterYi/bilibili-API-collect/issues/868 - -Reference: https://github.com/Nemo2011/bilibili-api/commit/f7de473bc42d60604372f80d06244e45a08bdbb4 -From: https://github.com/Nemo2011/bilibili-api/blob/f7de473bc42d60604372f80d06244e45a08bdbb4/bilibili_api/utils/exclimbwuzhi.py -""" - -import io -import random -import struct -import time - -import ujson as json - -MOD = 1 << 64 - - -def get_time_milli() -> int: - return int(time.time() * 1000) - - -def rotate_left(x: int, k: int) -> int: - bin_str = bin(x)[2:].rjust(64, "0") - return int(bin_str[k:] + bin_str[:k], base=2) - - -def gen_uuid_infoc() -> str: - t = get_time_milli() % 100000 - mp = list("123456789ABCDEF") + ["10"] - pck = [8, 4, 4, 4, 12] - - def gen_part(x) -> str: - return "".join([random.choice(mp) for _ in range(x)]) - - return "-".join([gen_part(x) for x in pck]) + str(t).ljust(5, "0") + "infoc" - - -def gen_b_lsid() -> str: - ret = "" - for _ in range(8): - ret += hex(random.randint(0, 15))[2:].upper() - ret = f"{ret}_{hex(get_time_milli())[2:].upper()}" - return ret - - -def gen_buvid_fp(key: str, seed: int): - source = io.BytesIO(bytes(key, "ascii")) - m = murmur3_x64_128(source, seed) - return "{}{}".format(hex(m & (MOD - 1))[2:], hex(m >> 64)[2:]) - - -def murmur3_x64_128(source: io.BufferedIOBase, seed: int) -> int: # type: ignore - C1 = 0x87C3_7B91_1142_53D5 - C2 = 0x4CF5_AD43_2745_937F - C3 = 0x52DC_E729 - C4 = 0x3849_5AB5 - R1, R2, R3, M = 27, 31, 33, 5 - h1, h2 = seed, seed - processed = 0 - while 1: - read = source.read(16) - processed += len(read) - if len(read) == 16: - k1 = struct.unpack("= 15: - k2 ^= int(read[14]) << 48 - if len(read) >= 14: - k2 ^= int(read[13]) << 40 - if len(read) >= 13: - k2 ^= int(read[12]) << 32 - if len(read) >= 12: - k2 ^= int(read[11]) << 24 - if len(read) >= 11: - k2 ^= int(read[10]) << 16 - if len(read) >= 10: - k2 ^= int(read[9]) << 8 - if len(read) >= 9: - k2 ^= int(read[8]) - k2 = rotate_left(k2 * C2 % MOD, R3) * C1 % MOD - h2 ^= k2 - if len(read) >= 8: - k1 ^= int(read[7]) << 56 - if len(read) >= 7: - k1 ^= int(read[6]) << 48 - if len(read) >= 6: - k1 ^= int(read[5]) << 40 - if len(read) >= 5: - k1 ^= int(read[4]) << 32 - if len(read) >= 4: - k1 ^= int(read[3]) << 24 - if len(read) >= 3: - k1 ^= int(read[2]) << 16 - if len(read) >= 2: - k1 ^= int(read[1]) << 8 - if len(read) >= 1: - k1 ^= int(read[0]) - k1 = rotate_left(k1 * C1 % MOD, R2) * C2 % MOD - h1 ^= k1 - - -def fmix64(k: int) -> int: - C1 = 0xFF51_AFD7_ED55_8CCD - C2 = 0xC4CE_B9FE_1A85_EC53 - R = 33 - tmp = k - tmp ^= tmp >> R - tmp = tmp * C1 % MOD - tmp ^= tmp >> R - tmp = tmp * C2 % MOD - tmp ^= tmp >> R - return tmp - - -def get_payload() -> str: - content = { - "3064": 1, - "5062": get_time_milli(), - "03bf": "https%3A%2F%2Fwww.bilibili.com%2F", - "39c8": "333.788.fp.risk", - "34f1": "", - "d402": "", - "654a": "", - "6e7c": "839x959", - "3c43": { - "2673": 0, - "5766": 24, - "6527": 0, - "7003": 1, - "807e": 1, - "b8ce": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15", - "641c": 0, - "07a4": "en-US", - "1c57": "not available", - "0bd0": 8, - "748e": [900, 1440], - "d61f": [875, 1440], - "fc9d": -480, - "6aa9": "Asia/Shanghai", - "75b8": 1, - "3b21": 1, - "8a1c": 0, - "d52f": "not available", - "adca": "MacIntel", - "80c9": [ - [ - "PDF Viewer", - "Portable Document Format", - [["application/pdf", "pdf"], ["text/pdf", "pdf"]], - ], - [ - "Chrome PDF Viewer", - "Portable Document Format", - [["application/pdf", "pdf"], ["text/pdf", "pdf"]], - ], - [ - "Chromium PDF Viewer", - "Portable Document Format", - [["application/pdf", "pdf"], ["text/pdf", "pdf"]], - ], - [ - "Microsoft Edge PDF Viewer", - "Portable Document Format", - [["application/pdf", "pdf"], ["text/pdf", "pdf"]], - ], - [ - "WebKit built-in PDF", - "Portable Document Format", - [["application/pdf", "pdf"], ["text/pdf", "pdf"]], - ], - ], - "13ab": "0dAAAAAASUVORK5CYII=", - "bfe9": "QgAAEIQAACEIAABCCQN4FXANGq7S8KTZayAAAAAElFTkSuQmCC", - "a3c1": [ - "extensions:ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_compression_bptc;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_astc;WEBGL_compressed_texture_etc;WEBGL_compressed_texture_etc1;WEBGL_compressed_texture_pvrtc;WEBKIT_WEBGL_compressed_texture_pvrtc;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw", - "webgl aliased line width range:[1, 1]", - "webgl aliased point size range:[1, 511]", - "webgl alpha bits:8", - "webgl antialiasing:yes", - "webgl blue bits:8", - "webgl depth bits:24", - "webgl green bits:8", - "webgl max anisotropy:16", - "webgl max combined texture image units:32", - "webgl max cube map texture size:16384", - "webgl max fragment uniform vectors:1024", - "webgl max render buffer size:16384", - "webgl max texture image units:16", - "webgl max texture size:16384", - "webgl max varying vectors:30", - "webgl max vertex attribs:16", - "webgl max vertex texture image units:16", - "webgl max vertex uniform vectors:1024", - "webgl max viewport dims:[16384, 16384]", - "webgl red bits:8", - "webgl renderer:WebKit WebGL", - "webgl shading language version:WebGL GLSL ES 1.0 (1.0)", - "webgl stencil bits:0", - "webgl vendor:WebKit", - "webgl version:WebGL 1.0", - "webgl unmasked vendor:Apple Inc.", - "webgl unmasked renderer:Apple GPU", - "webgl vertex shader high float precision:23", - "webgl vertex shader high float precision rangeMin:127", - "webgl vertex shader high float precision rangeMax:127", - "webgl vertex shader medium float precision:23", - "webgl vertex shader medium float precision rangeMin:127", - "webgl vertex shader medium float precision rangeMax:127", - "webgl vertex shader low float precision:23", - "webgl vertex shader low float precision rangeMin:127", - "webgl vertex shader low float precision rangeMax:127", - "webgl fragment shader high float precision:23", - "webgl fragment shader high float precision rangeMin:127", - "webgl fragment shader high float precision rangeMax:127", - "webgl fragment shader medium float precision:23", - "webgl fragment shader medium float precision rangeMin:127", - "webgl fragment shader medium float precision rangeMax:127", - "webgl fragment shader low float precision:23", - "webgl fragment shader low float precision rangeMin:127", - "webgl fragment shader low float precision rangeMax:127", - "webgl vertex shader high int precision:0", - "webgl vertex shader high int precision rangeMin:31", - "webgl vertex shader high int precision rangeMax:30", - "webgl vertex shader medium int precision:0", - "webgl vertex shader medium int precision rangeMin:31", - "webgl vertex shader medium int precision rangeMax:30", - "webgl vertex shader low int precision:0", - "webgl vertex shader low int precision rangeMin:31", - "webgl vertex shader low int precision rangeMax:30", - "webgl fragment shader high int precision:0", - "webgl fragment shader high int precision rangeMin:31", - "webgl fragment shader high int precision rangeMax:30", - "webgl fragment shader medium int precision:0", - "webgl fragment shader medium int precision rangeMin:31", - "webgl fragment shader medium int precision rangeMax:30", - "webgl fragment shader low int precision:0", - "webgl fragment shader low int precision rangeMin:31", - "webgl fragment shader low int precision rangeMax:30", - ], - "6bc5": "Apple Inc.~Apple GPU", - "ed31": 0, - "72bd": 0, - "097b": 0, - "52cd": [0, 0, 0], - "a658": [ - "Andale Mono", - "Arial", - "Arial Black", - "Arial Hebrew", - "Arial Narrow", - "Arial Rounded MT Bold", - "Arial Unicode MS", - "Comic Sans MS", - "Courier", - "Courier New", - "Geneva", - "Georgia", - "Helvetica", - "Helvetica Neue", - "Impact", - "LUCIDA GRANDE", - "Microsoft Sans Serif", - "Monaco", - "Palatino", - "Tahoma", - "Times", - "Times New Roman", - "Trebuchet MS", - "Verdana", - "Wingdings", - "Wingdings 2", - "Wingdings 3", - ], - "d02f": "124.04345259929687", - }, - "54ef": '{"in_new_ab":true,"ab_version":{"remove_back_version":"REMOVE","login_dialog_version":"V_PLAYER_PLAY_TOAST","open_recommend_blank":"SELF","storage_back_btn":"HIDE","call_pc_app":"FORBID","clean_version_old":"GO_NEW","optimize_fmp_version":"LOADED_METADATA","for_ai_home_version":"V_OTHER","bmg_fallback_version":"DEFAULT","ai_summary_version":"SHOW","weixin_popup_block":"ENABLE","rcmd_tab_version":"DISABLE","in_new_ab":true},"ab_split_num":{"remove_back_version":11,"login_dialog_version":43,"open_recommend_blank":90,"storage_back_btn":87,"call_pc_app":47,"clean_version_old":46,"optimize_fmp_version":28,"for_ai_home_version":38,"bmg_fallback_version":86,"ai_summary_version":466,"weixin_popup_block":45,"rcmd_tab_version":90,"in_new_ab":0},"pageVersion":"new_video","videoGoOldVersion":-1}', - "8b94": "https%3A%2F%2Fwww.bilibili.com%2F", - "df35": "2D9BA3CF-B1ED-1674-2492-CF103D9EFACFE46196infoc", - "07a4": "en-US", - "5f45": None, - "db46": 0, - } - return json.dumps( - {"payload": json.dumps(content, separators=(",", ":"))}, - separators=(",", ":"), - ) - - -__all__ = [ - 'gen_buvid_fp', - 'gen_uuid_infoc', - 'get_payload', -] diff --git a/src/utils/bilibili_api/future/__init__.py b/src/utils/bilibili_api/future/__init__.py new file mode 100644 index 00000000..83167339 --- /dev/null +++ b/src/utils/bilibili_api/future/__init__.py @@ -0,0 +1,23 @@ +""" +@Author : Ailitonia +@Date : 2024/10/25 10:53:52 +@FileName : future +@Project : omega-miya +@Description : bilibili API +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from .api import ( + BilibiliCredential, + BilibiliDynamic, + BilibiliLive, + BilibiliUser, +) + +__all__ = [ + 'BilibiliDynamic', + 'BilibiliCredential', + 'BilibiliLive', + 'BilibiliUser', +] diff --git a/src/utils/bilibili_api/future/api/__init__.py b/src/utils/bilibili_api/future/api/__init__.py new file mode 100644 index 00000000..88987800 --- /dev/null +++ b/src/utils/bilibili_api/future/api/__init__.py @@ -0,0 +1,21 @@ +""" +@Author : Ailitonia +@Date : 2024/10/31 17:15:35 +@FileName : api.py +@Project : omega-miya +@Description : bilibili API +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from .dynamic import BilibiliDynamic +from .live import BilibiliLive +from .login import BilibiliCredential +from .user import BilibiliUser + +__all__ = [ + 'BilibiliDynamic', + 'BilibiliCredential', + 'BilibiliLive', + 'BilibiliUser', +] diff --git a/src/utils/bilibili_api/future/api/base.py b/src/utils/bilibili_api/future/api/base.py new file mode 100644 index 00000000..9169600e --- /dev/null +++ b/src/utils/bilibili_api/future/api/base.py @@ -0,0 +1,193 @@ +""" +@Author : Ailitonia +@Date : 2024/11/4 10:59:54 +@FileName : base.py +@Project : omega-miya +@Description : bilibili API 基类 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +import re +from typing import TYPE_CHECKING, Any + +from src.utils import BaseCommonAPI +from ..config import bilibili_api_config +from ..misc import ( + create_gen_web_ticket_params, + extract_key_from_wbi_image, + gen_buvid_fp, + gen_uuid_infoc, + get_payload, + sign_wbi_params, + sign_wbi_params_nav, +) +from ..models import ( + SearchAllResult, + SearchType, + SearchTypeResult, + Ticket, + WebInterfaceNav, + WebInterfaceSpi, +) + +if TYPE_CHECKING: + from nonebot.internal.driver import CookieTypes, Response + + from src.resource import TemporaryResource + + +class BilibiliCommon(BaseCommonAPI): + """Bilibili API 基类""" + + @classmethod + def _get_root_url(cls, *args, **kwargs) -> str: + return 'https://www.bilibili.com' + + @classmethod + async def _async_get_root_url(cls, *args, **kwargs) -> str: + return cls._get_root_url(*args, **kwargs) + + @classmethod + def _load_cloudflare_clearance(cls) -> bool: + return False + + @classmethod + def _get_default_headers(cls) -> dict[str, str]: + headers = cls._get_omega_requests_default_headers() + headers.update({ + 'origin': 'https://www.bilibili.com', + 'referer': 'https://www.bilibili.com/' + }) + return headers + + @classmethod + def _get_default_cookies(cls) -> 'CookieTypes': + return bilibili_api_config.bili_cookies + + @classmethod + def _extra_set_cookies_from_response(cls, response: 'Response') -> dict[str, str]: + """从请求的响应头中获取 set-cookie 字段内容""" + set_cookies: dict[str, str] = {} + for k, v in response.headers.items(): + if re.match(re.compile('set-cookie', re.IGNORECASE), k): + item = v.split(';', maxsplit=1)[0].strip().split('=', maxsplit=1) + if len(item) == 2: + set_cookies.update({item[0]: item[1]}) + return set_cookies + + @classmethod + async def download_resource(cls, url: str) -> 'TemporaryResource': + """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" + return await cls._download_resource( + save_folder=bilibili_api_config.download_folder, url=url, + ) + + @classmethod + async def _sign_wbi_params_nav(cls, params: dict[str, Any] | None = None) -> dict[str, Any]: + """立即从 nav 接口请求参数进行 wbi 签名""" + _wbi_nav_url: str = 'https://api.bilibili.com/x/web-interface/nav' + + response = await cls._get_json(url=_wbi_nav_url) + return sign_wbi_params_nav(nav_data=WebInterfaceNav.model_validate(response), params=params) + + @classmethod + async def sign_wbi_params(cls, params: dict[str, Any] | None = None) -> dict[str, Any]: + """对请求参数进行 wbi 签名""" + img_key = bilibili_api_config.get_config('img_key') + sub_key = bilibili_api_config.get_config('sub_key') + + if (img_key is None) or (sub_key is None): + return await cls._sign_wbi_params_nav(params=params) + + return sign_wbi_params(params=params, img_key=img_key, sub_key=sub_key) + + @classmethod + async def update_ticket_wbi_cookies(cls) -> dict[str, Any]: + """从 BiliTicket 接口更新 web_ticket 及 wbi 签参数缓存""" + _ticket_url: str = 'https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket' + params = create_gen_web_ticket_params(bili_jct=bilibili_api_config.get_config('bili_jct')) + + response = await cls._post_json(url=_ticket_url, params=params) + ticket_data = Ticket.model_validate(response) + + bilibili_api_config.update_config( + bili_ticket=ticket_data.data.ticket, + bili_ticket_expires=ticket_data.data.created_at + ticket_data.data.ttl, + img_key=extract_key_from_wbi_image(ticket_data.data.nav.img), + sub_key=extract_key_from_wbi_image(ticket_data.data.nav.sub), + ) + return bilibili_api_config.bili_cookies + + + @classmethod + async def update_buvid_cookies(cls) -> dict[str, Any]: + """为接口激活 buvid, 并更新 Cookies 缓存""" + _spi_url: str = 'https://api.bilibili.com/x/frontend/finger/spi' + _exclimbwuzhi_url: str = 'https://api.bilibili.com/x/internal/gaia-gateway/ExClimbWuzhi' + + # get buvid3, buvid4 + spi_response = await cls._get_json(url=_spi_url) + spi_data = WebInterfaceSpi.model_validate(spi_response) + + # active buvid + uuid = gen_uuid_infoc() + payload = get_payload() + + bilibili_api_config.update_config( + buvid3=spi_data.data.b_3, + buvid4=spi_data.data.b_4, + buvid_fp=gen_buvid_fp(payload, 31), + b_nut='100', + _uuid=uuid + ) + cookies = bilibili_api_config.bili_cookies + + headers = cls._get_default_headers() + headers.update({ + 'origin': 'https://www.bilibili.com', + 'referer': 'https://www.bilibili.com/', + 'Content-Type': 'application/json' + }) + await cls._post_json(url=_exclimbwuzhi_url, headers=headers, json=payload, cookies=cookies) + return cookies + + @classmethod + async def global_search_all(cls, keyword: str) -> SearchAllResult: + """综合搜索 (web端), 返回和关键字相关的 20 条信息 + + 综合搜索为默认搜索方式, 主要用于优先搜索用户、影视、番剧、游戏、话题等, 并加载第一页的20项相关视频 + """ + url = 'https://api.bilibili.com/x/web-interface/wbi/search/all/v2' + params = await cls.sign_wbi_params(params={'keyword': keyword}) + data = await cls._get_json(url=url, params=params) + return SearchAllResult.model_validate(data) + + @classmethod + async def global_search_by_type( + cls, + search_type: SearchType, + keyword: str, + page: int = 1, + **kwargs, + ) -> SearchTypeResult: + """分类搜索 (web端), 根据关键词进行搜索, 返回结果每页 20 项 + + :param search_type: 搜索类型 + :param keyword: 搜索关键词 + :param page: 搜索页码 + """ + params = { + 'search_type': search_type, + 'keyword': keyword, + 'page': page, + **kwargs + } + search_url: str = 'https://api.bilibili.com/x/web-interface/wbi/search/type' + searching_data = await cls._get_json(url=search_url, params=params) + return SearchTypeResult.model_validate(searching_data) + + +__all__ = [ + 'BilibiliCommon', +] diff --git a/src/utils/bilibili_api/future/api/dynamic.py b/src/utils/bilibili_api/future/api/dynamic.py new file mode 100644 index 00000000..1357efce --- /dev/null +++ b/src/utils/bilibili_api/future/api/dynamic.py @@ -0,0 +1,87 @@ +""" +@Author : Ailitonia +@Date : 2024/12/17 19:38:14 +@FileName : dynamic.py +@Project : omega-miya +@Description : bilibili 动态相关 API +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Literal + +from .base import BilibiliCommon +from ..models import Dynamics, DynDetail + + +class BilibiliDynamic(BilibiliCommon): + """Bilibili 动态 API""" + + @classmethod + async def query_my_following_dynamics( + cls, + *, + type_: Literal['all', 'video', 'pgc', 'article'] | None = None, + host_mid: str | None = None, + offset: int | None = None, + update_baseline: int | None = None, + platform: str = 'web', + web_location: str = '333.1365' + ) -> Dynamics: + """获取我关注的动态列表更新""" + url = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all' + params: dict[str, str] = {'platform': platform, 'web_location': web_location} + if type_ is not None: + params.update({'type': type_}) + if host_mid is not None: + params.update({'host_mid': host_mid}) + if offset is not None: + params.update({'offset': str(offset)}) + if update_baseline is not None: + params.update({'type': str(update_baseline)}) + + data = await cls._get_json(url=url, params=params) + return Dynamics.model_validate(data) + + @classmethod + async def query_user_space_dynamics( + cls, + host_mid: int | str, + *, + offset: int | None = None, + timezone_offset: int | None = None, + features: str | None = None, + ) -> Dynamics: + """获取用户空间动态""" + url = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space' + params: dict[str, str] = {'host_mid': str(host_mid)} + if offset is not None: + params.update({'offset': str(offset)}) + if timezone_offset is not None: + params.update({'timezone_offset': str(timezone_offset)}) + if features is not None: + params.update({'features': features}) + + data = await cls._get_json(url=url, params=params) + return Dynamics.model_validate(data) + + @classmethod + async def query_dynamic_detail( + cls, + id_: int | str, + *, + timezone_offset: int | None = None, + ) -> DynDetail: + """获取动态详细信息""" + url = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/detail' + params: dict[str, str] = {'id': str(id_)} + if timezone_offset is not None: + params.update({'timezone_offset': str(timezone_offset)}) + + data = await cls._get_json(url=url, params=params) + return DynDetail.model_validate(data) + + +__all__ = [ + 'BilibiliDynamic', +] diff --git a/src/utils/bilibili_api/future/api/live.py b/src/utils/bilibili_api/future/api/live.py new file mode 100644 index 00000000..51cd4b66 --- /dev/null +++ b/src/utils/bilibili_api/future/api/live.py @@ -0,0 +1,56 @@ +""" +@Author : Ailitonia +@Date : 2024/12/24 14:01:52 +@FileName : live.py +@Project : omega-miya +@Description : bilibili 直播相关 API +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from collections.abc import Sequence + +from .base import BilibiliCommon +from ..models import RoomBaseInfo, RoomInfo, RoomUserInfo, UsersRoomInfo + + +class BilibiliLive(BilibiliCommon): + """Bilibili 直播 API""" + + @classmethod + async def query_room_info(cls, room_id: int | str) -> RoomInfo: + """获取直播间信息""" + url = 'https://api.live.bilibili.com/room/v1/Room/get_info' + params = {'room_id': str(room_id)} + data = await cls._get_json(url=url, params=params) + return RoomInfo.model_validate(data) + + @classmethod + async def query_room_info_by_room_id_list(cls, room_id_list: Sequence[int | str]) -> RoomBaseInfo: + """根据直播间房间号列表获取这些直播间的信息""" + url = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getRoomBaseInfo' + params = (('req_biz', 'web_room_componet'), *(('room_ids', str(x)) for x in room_id_list)) + data = await cls._get_json(url=url, params=list(params)) + return RoomBaseInfo.model_validate(data) + + @classmethod + async def query_room_info_by_uid_list(cls, uid_list: Sequence[int | str]) -> UsersRoomInfo: + """根据用户 uid 列表获取这些用户的直播间信息(这个 api 没有认证方法,请不要在标头中添加 cookie)""" + url = 'https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids' + payload = {'uids': uid_list} + # 该接口无需鉴权 + data = await cls._post_json(url=url, json=payload, no_headers=True, no_cookies=True) + return UsersRoomInfo.model_validate(data) + + @classmethod + async def query_room_user_info(cls, room_id: int | str) -> RoomUserInfo: + """获取直播间对应的主播信息""" + url = 'https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room' + params = {'roomid': str(room_id)} + data = await cls._get_json(url=url, params=params) + return RoomUserInfo.model_validate(data) + + +__all__ = [ + 'BilibiliLive', +] diff --git a/src/utils/bilibili_api/future/api/login.py b/src/utils/bilibili_api/future/api/login.py new file mode 100644 index 00000000..7cd62f07 --- /dev/null +++ b/src/utils/bilibili_api/future/api/login.py @@ -0,0 +1,248 @@ +""" +@Author : Ailitonia +@Date : 2024/11/4 16:49:50 +@FileName : login.py +@Project : omega-miya +@Description : bilibili 用户凭据相关 API +@GitHub : https://github.com/Ailitonia +@Software : PyCharm + +Reference: +https://socialsisteryi.github.io/bilibili-API-collect/docs/login/cookie_refresh.html +""" + +import asyncio +import binascii +import time +from typing import TYPE_CHECKING + +import qrcode +from Cryptodome.Cipher import PKCS1_OAEP +from Cryptodome.Hash import SHA256 +from Cryptodome.PublicKey import RSA +from lxml import etree +from nonebot import logger +from nonebot.utils import run_sync + +from .base import BilibiliCommon +from ..config import bilibili_api_config +from ..models import ( + WebConfirmRefreshInfo, + WebCookieInfo, + WebCookieRefreshInfo, + WebInterfaceNav, + WebQrcodeGenerateInfo, + WebQrcodePollInfo, +) + +if TYPE_CHECKING: + from src.resource import TemporaryResource + +_PUB_KEY = RSA.importKey("""\ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLgd2OAkcGVtoE3ThUREbio0Eg +Uc/prcajMKXvkCKFCWhJYJcLkcM2DKKcSeFpD/j6Boy538YXnR6VhcuUJOhH2x71 +nzPjfdTcqMz7djHum0qSZA0AyCBDABUqCrfNgCiJ00Ra7GmRj+YCK1NJEuewlb40 +JNrRuoEUXpabUzGB8QIDAQAB +-----END PUBLIC KEY-----""") + + +class BilibiliCredential(BilibiliCommon): + """Bilibili 凭据操作类""" + + @staticmethod + async def save_cookies_to_db() -> None: + await bilibili_api_config.save_to_database() + + @staticmethod + async def load_cookies_from_db() -> None: + await bilibili_api_config.load_from_database() + + @staticmethod + def _get_correspond_path() -> str: + """根据时间生成 CorrespondPath""" + ts = round(time.time() * 1000) + cipher = PKCS1_OAEP.new(_PUB_KEY, SHA256) + encrypted = cipher.encrypt(f'refresh_{ts}'.encode()) + return binascii.b2a_hex(encrypted).decode() + + @staticmethod + @run_sync + def _make_qrcode(content: str) -> 'TemporaryResource': + qrcode_filename = f'{SHA256.new(content.encode()).hexdigest()}.png' + qrcode_file = bilibili_api_config.get_resource_path('login_qr', qrcode_filename) + with qrcode_file.open('wb') as f: + qrcode.make(data=content).save(f) + return qrcode_file + + @classmethod + async def get_login_qrcode(cls) -> WebQrcodeGenerateInfo: + """获取登录二维码信息""" + url = 'https://passport.bilibili.com/x/passport-login/web/qrcode/generate' + data = await cls._get_json(url=url) + return WebQrcodeGenerateInfo.model_validate(data) + + @classmethod + async def generate_login_qrcode(cls, qrcode_info: WebQrcodeGenerateInfo) -> 'TemporaryResource': + """生成登录二维码""" + return await cls._make_qrcode(qrcode_info.data.url) + + @classmethod + async def check_qrcode_login( + cls, + qrcode_info: WebQrcodeGenerateInfo + ) -> tuple[WebQrcodePollInfo, dict[str, str]]: + """检查二维码登录状态 + + :param qrcode_info: 登录二维码信息 + :return: (WebQrcodePollInfo, SetCookiesDict) + """ + url = 'https://passport.bilibili.com/x/passport-login/web/qrcode/poll' + params = {'qrcode_key': qrcode_info.data.qrcode_key} + login_response = await cls._request_get(url=url, params=params) + + login_data = WebQrcodePollInfo.model_validate(cls._parse_content_as_json(login_response)) + login_set_cookies = cls._extra_set_cookies_from_response(response=login_response) + + return login_data, login_set_cookies + + @classmethod + async def login_with_qrcode(cls, qrcode_info: WebQrcodeGenerateInfo) -> bool: + attempt = 0 + while True: + login_info, login_set_cookies = await cls.check_qrcode_login(qrcode_info=qrcode_info) + + if login_info.data.code == 0: + logger.opt(colors=True).success('Bilibili | 扫码登录: 成功') + break + elif attempt >= 10: + logger.opt(colors=True).error(f'Bilibili | 扫码登录: {login_info.data.message}, 等待超时') + raise RuntimeError('等待超时') + elif login_info.data.code == 86101: + logger.opt(colors=True).debug(f'Bilibili | 扫码登录: {login_info.data.message}, 待扫码') + attempt += 1 + elif login_info.data.code == 86090: + logger.opt(colors=True).debug(f'Bilibili | 扫码登录: {login_info.data.message}, 待确认') + attempt += 1 + elif login_info.data.code == 86038: + logger.opt(colors=True).error(f'Bilibili | 扫码登录: {login_info.data.message}, 二维码过期') + raise RuntimeError('登录二维码过期') + else: + logger.opt(colors=True).warning(f'Bilibili | 扫码登录: {login_info.data.message}') + attempt += 1 + await asyncio.sleep(6) + + login_set_cookies.update({'ac_time_value': login_info.data.refresh_token}) + bilibili_api_config.clear_config() + bilibili_api_config.update_config(**login_set_cookies) + + await cls.save_cookies_to_db() + return await cls.check_valid() + + @classmethod + async def check_need_refresh(cls) -> bool: + """检查 Cookies 是否需要刷新""" + if (bili_jct := bilibili_api_config.get_config('bili_jct')) is None: + return True + + url = 'https://passport.bilibili.com/x/passport-login/web/cookie/info' + params = {'csrf': bili_jct} + + data = await cls._get_json(url=url, params=params) + return WebCookieInfo.model_validate(data).data.refresh + + @classmethod + async def check_valid(cls) -> bool: + """检查 cookies 是否有效""" + url = 'https://api.bilibili.com/x/web-interface/nav' + + try: + data = await cls._get_json(url=url, cookies=bilibili_api_config.bili_cookies) + verify = WebInterfaceNav.model_validate(data) + except Exception as e: + logger.opt(colors=True).error(f'Bilibili | 登录验证失败, 访问失败, {e}') + return False + + if verify.code != 0 or not verify.data.isLogin: + bilibili_api_config.clear_config() + logger.opt(colors=True).warning(f'Bilibili | 登录验证失败, 状态异常, {verify.message}') + return False + elif verify.data.mid != bilibili_api_config.get_config('DedeUserID'): + bilibili_api_config.clear_config() + logger.opt(colors=True).warning('Bilibili | 登录验证失败, 状态异常, 用户 UID 不匹配') + return False + else: + logger.opt(colors=True).success(f'Bilibili | 登录已验证, 登录用户: {verify.data.uname}') + return True + + @classmethod + async def get_refresh_csrf(cls) -> str: + """获取 refresh_csrf""" + url = f'https://www.bilibili.com/correspond/1/{cls._get_correspond_path()}' + + # 不知道什么原因, 这里不暂停等一下就只会返回 404, 明明时间是同步的, 另外这里只 sleep(1) 也不行, 必须等待足够长的时间 + await asyncio.sleep(3) + + content = await cls._get_resource_as_text(url=url, cookies=bilibili_api_config.bili_cookies) + refresh_csrf = etree.HTML(content).xpath('/html/body/div[@id="1-name"]').pop(0).text + return refresh_csrf + + @classmethod + async def confirm_cookies_refresh(cls, csrf: str, refresh_token: str | None) -> WebConfirmRefreshInfo: + """确认 Cookies 更新, 该步操作将让旧的 refresh_token 对应的 Cookie 失效 + + :param csrf: 从新的 Cookies 中获取, 位于 Cookies 中的 bili_jct 字段 + :param refresh_token: 在刷新前配置中的 ac_time_value 获取, 并非刷新后返回的值 + """ + url = 'https://passport.bilibili.com/x/passport-login/web/confirm/refresh' + params = {'csrf': csrf, 'refresh_token': refresh_token} + + data = await cls._post_json(url=url, params=params) + return WebConfirmRefreshInfo.model_validate(data) + + @classmethod + async def refresh_cookies(cls) -> bool: + """刷新用户 Cookies""" + url = 'https://passport.bilibili.com/x/passport-login/web/cookie/refresh' + + refresh_csrf = await cls.get_refresh_csrf() + old_refresh_token = bilibili_api_config.get_config('ac_time_value') + params = { + 'csrf': bilibili_api_config.get_config('bili_jct'), + 'refresh_csrf': refresh_csrf, + 'refresh_token': old_refresh_token, + 'source': 'main_web', + } + + response = await cls._request_post(url=url, params=params, cookies=bilibili_api_config.bili_cookies) + refresh_info = WebCookieRefreshInfo.model_validate(cls._parse_content_as_json(response)) + + if refresh_info.code != 0: + logger.opt(colors=True).error(f'Bilibili | 刷新用户 Cookies 失败, {refresh_info}') + return False + + new_cookies = cls._extra_set_cookies_from_response(response=response) + new_cookies.update({'ac_time_value': refresh_info.data.refresh_token}) + bilibili_api_config.update_config(**new_cookies) + + # 激活 buvid + await cls.update_buvid_cookies() + + # 更新 wbi + await cls.update_ticket_wbi_cookies() + + # 确认更新并注销旧 token + confirm_result = await cls.confirm_cookies_refresh( + csrf=new_cookies['bili_jct'], refresh_token=old_refresh_token + ) + if confirm_result.code != 0: + logger.opt(colors=True).error(f'Bilibili | 确认更新用户 Cookies 失败, {confirm_result.message}') + return False + + await cls.save_cookies_to_db() + return await cls.check_valid() + + +__all__ = [ + 'BilibiliCredential', +] diff --git a/src/utils/bilibili_api/future/api/user.py b/src/utils/bilibili_api/future/api/user.py new file mode 100644 index 00000000..6d4791ec --- /dev/null +++ b/src/utils/bilibili_api/future/api/user.py @@ -0,0 +1,77 @@ +""" +@Author : Ailitonia +@Date : 2024/11/15 16:15:55 +@FileName : user.py +@Project : omega-miya +@Description : bilibili 用户相关 API +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from urllib.parse import unquote + +from lxml import etree +from nonebot.utils import run_sync + +from src.compat import parse_json_as +from .base import BilibiliCommon +from ..models import ( + Account, + User, + UserSearchResult, + UserSpaceRenderData, + VipInfo, +) + + +class BilibiliUser(BilibiliCommon): + """Bilibili 用户 API""" + + @classmethod + async def query_my_account_info(cls) -> Account: + """获取我的信息""" + url = 'https://api.bilibili.com/x/member/web/account' + data = await cls._get_json(url=url) + return Account.model_validate(data) + + @classmethod + async def query_my_vip_info(cls) -> VipInfo: + """查询我的大会员状态""" + url = 'https://api.bilibili.com/x/vip/web/user/info' + data = await cls._get_json(url=url) + return VipInfo.model_validate(data) + + @staticmethod + @run_sync + def _parse_user_space_w_webid(content: str) -> UserSpaceRenderData: + """解析用户页面 __RENDER_DATA__ 内容""" + html = etree.HTML(content) + render_data = html.xpath('/html/head/script[@id="__RENDER_DATA__"]').pop(0).text + return parse_json_as(UserSpaceRenderData, unquote(render_data)) + + @classmethod + async def _query_user_space_w_webid(cls, mid: int | str) -> UserSpaceRenderData: + """获取并解析用户页面 __RENDER_DATA__ 内容""" + user_space_url = f'https://space.bilibili.com/{mid}' + user_space_page = await cls._get_resource_as_text(url=user_space_url) + return await cls._parse_user_space_w_webid(content=user_space_page) + + @classmethod + async def query_user_info(cls, mid: int | str) -> User: + """获取用户基本信息""" + url = 'https://api.bilibili.com/x/space/wbi/acc/info' + render_data = await cls._query_user_space_w_webid(mid=mid) + params = await cls.sign_wbi_params(params={'mid': str(mid), 'w_webid': render_data.access_id}) + data = await cls._get_json(url=url, params=params) + return User.model_validate(data) + + @classmethod + async def search_user(cls, keyword: str) -> list[UserSearchResult]: + """搜索用户""" + search_result = await cls.global_search_by_type(search_type='bili_user', keyword=keyword) + return [x for x in search_result.all_results if isinstance(x, UserSearchResult)] + + +__all__ = [ + 'BilibiliUser', +] diff --git a/src/utils/bilibili_api/future/config.py b/src/utils/bilibili_api/future/config.py new file mode 100644 index 00000000..c7721405 --- /dev/null +++ b/src/utils/bilibili_api/future/config.py @@ -0,0 +1,187 @@ +""" +@Author : Ailitonia +@Date : 2024/10/28 14:38:44 +@FileName : config.py +@Project : omega-miya +@Description : bilibili API 配置 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from collections.abc import Generator +from dataclasses import dataclass +from typing import Any, Self +from urllib.parse import quote + +from nonebot import get_plugin_config, logger +from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator +from sqlalchemy.exc import NoResultFound + +from src.database import SystemSettingDAL, begin_db_session +from src.resource import TemporaryResource +from .consts import BILI_DB_SETTING_NAME + + +class BaseConfigModel(BaseModel): + """Bilibili 配置基类""" + + model_config = ConfigDict(extra='ignore', coerce_numbers_to_str=True) + + def get_config_dict(self) -> dict[str, Any]: + return { + k: v for k, v in self.model_dump(by_alias=True).items() + if v is not None + } + + def iter_config(self) -> Generator[tuple[str, str | None], Any, None]: + for config_name, config_value in self.model_dump(by_alias=True).items(): + yield config_name, config_value + + +class BilibiliLoginCookies(BaseConfigModel): + """导出用于用户鉴权的, 包含用户登录信息的 Cookies 值""" + + bilibili_api_sessdata: str | None = Field(default=None, alias='SESSDATA') + bilibili_api_jct: str | None = Field(default=None, alias='bili_jct') + bilibili_api_dedeuserid: str | None = Field(default=None, alias='DedeUserID') + bilibili_api_dedeuserid_md5: str | None = Field(default=None, alias='DedeUserID__ckMd5') + bilibili_api_sid: str | None = Field(default=None, alias='sid') + + @field_validator('bilibili_api_sessdata', mode='after') + @classmethod + def quote_sessdata(cls, v: str | None) -> str | None: + return ( + None if v is None + else ( + v if v.find('%') != -1 + else quote(v) + ) + ) + + +class BilibiliCookies(BilibiliLoginCookies): + """Bilibili API 请求所需 Cookies 值""" + + # buvid3_4 相关 + bilibili_api_buvid3: str | None = Field(default=None, alias='buvid3') + bilibili_api_buvid4: str | None = Field(default=None, alias='buvid4') + bilibili_api_buvid_fp: str | None = Field(default=None, alias='buvid_fp') + bilibili_api_b_nut: str | None = Field(default=None, alias='b_nut') + bilibili_api_uuid: str | None = Field(default=None, alias='_uuid') + + # BiliTicket 相关 + bilibili_api_web_ticket: str | None = Field(default=None, alias='bili_ticket') + bilibili_api_web_ticket_expires: str | None = Field(default=None, alias='bili_ticket_expires') + + +class BilibiliAllCachedCookies(BilibiliCookies): + """Bilibili API 相关的所有所需 Cookies 值 (包含缓存的其他临时性但不直接用于请求的参数)""" + + # localStorage 中缓存的 refresh_token + bilibili_api_refresh_token: str | None = Field(default=None, alias='ac_time_value') + + # WBI 签名相关 + bilibili_api_wbi_img_key: str | None = Field(default=None, alias='img_key') + bilibili_api_wbi_sub_key: str | None = Field(default=None, alias='sub_key') + + +@dataclass +class BilibiliLocalResourceConfig: + # 默认的缓存资源保存路径 + default_folder: TemporaryResource = TemporaryResource('bilibili') + + def get_path(self, *args: str) -> 'TemporaryResource': + """获取缓存路径""" + return self.default_folder(*args) + + +class BilibiliAPIConfigManager: + """bilibili API 配置""" + + __slots__ = ('_cookies_config', '_resource_config',) + + _cookies_config: 'BilibiliAllCachedCookies' + _resource_config: 'BilibiliLocalResourceConfig' + + def __init__(self, cookies_config: 'BilibiliAllCachedCookies') -> None: + self._cookies_config = cookies_config + self._resource_config = BilibiliLocalResourceConfig() + + @property + def bili_cookies(self) -> dict[str, Any]: + """从内部缓存获取 bilibili Cookies""" + return BilibiliCookies.model_validate(self._cookies_config.get_config_dict()).get_config_dict() + + @property + def download_folder(self) -> 'TemporaryResource': + """缓存下载文件目录""" + return self.get_resource_path('download') + + def _iter_config(self) -> Generator[tuple[str, str | None], Any, None]: + """遍历配置项""" + return self._cookies_config.iter_config() + + def get_resource_path(self, *file_name: str) -> 'TemporaryResource': + """获取缓存资源文件路径""" + return self._resource_config.get_path(*file_name) + + def get_config(self, key: str, *, alias: bool = True) -> Any | None: + """根据 alias 获取配置项""" + if alias: + return self._cookies_config.get_config_dict().get(key, None) + return getattr(self._cookies_config, key, None) + + def update_config(self, **kwargs) -> None: + """更新 bilibili Cookies 内部缓存""" + for config_name, config_value in self._iter_config(): + kwargs.setdefault(config_name, config_value) + self._cookies_config = BilibiliAllCachedCookies.model_validate(kwargs) + + def clear_config(self) -> None: + """清空所有配置内容""" + self._cookies_config = BilibiliAllCachedCookies() + + @staticmethod + async def _save_config_to_db(dal: SystemSettingDAL, setting_key: str, value: str | None) -> None: + if value is None: + return + await dal.upsert(setting_name=BILI_DB_SETTING_NAME, setting_key=setting_key, setting_value=value) + + @staticmethod + async def _load_config_from_db(dal: SystemSettingDAL, setting_key: str) -> str | None: + try: + setting = await dal.query_unique(setting_name=BILI_DB_SETTING_NAME, setting_key=setting_key) + return setting.setting_value + except NoResultFound: + return None + + async def save_to_database(self) -> None: + """持久化保存用户登录 Cookies 到数据库""" + async with begin_db_session() as session: + dal = SystemSettingDAL(session=session) + for config_name, config_value in self._iter_config(): + await self._save_config_to_db(dal=dal, setting_key=config_name, value=config_value) + + async def load_from_database(self) -> Self: + """从数据库读取用户登录 Cookies""" + async with begin_db_session() as session: + dal = SystemSettingDAL(session=session) + update_data = { + config_name: await self._load_config_from_db(dal=dal, setting_key=config_name) + for config_name, _ in self._iter_config() + } + self.update_config(**update_data) + return self + + +try: + bilibili_api_config = BilibiliAPIConfigManager(get_plugin_config(BilibiliAllCachedCookies)) +except ValidationError as e: + import sys + + logger.opt(colors=True).critical(f'Bilibili 配置格式验证失败, 错误信息:\n{e}') + sys.exit(f'Bilibili 配置格式验证失败, {e}') + +__all__ = [ + 'bilibili_api_config', +] diff --git a/src/utils/bilibili_api/future/consts.py b/src/utils/bilibili_api/future/consts.py new file mode 100644 index 00000000..bc03fa2e --- /dev/null +++ b/src/utils/bilibili_api/future/consts.py @@ -0,0 +1,25 @@ +""" +@Author : Ailitonia +@Date : 2024/10/28 14:34:36 +@FileName : consts.py +@Project : omega-miya +@Description : bilibili API 模块常量 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Literal + +from pytz import timezone + +BILI_DB_SETTING_NAME: Literal['bilibili_api'] = 'bilibili_api' +"""数据库系统配置表固定字段""" + +DEFAULT_LOCAL_TZ = timezone('Asia/Shanghai') +"""默认本地时区""" + + +__all__ = [ + 'BILI_DB_SETTING_NAME', + 'DEFAULT_LOCAL_TZ', +] diff --git a/src/utils/bilibili_api/future/misc/__init__.py b/src/utils/bilibili_api/future/misc/__init__.py new file mode 100644 index 00000000..5d4b0672 --- /dev/null +++ b/src/utils/bilibili_api/future/misc/__init__.py @@ -0,0 +1,23 @@ +""" +@Author : Ailitonia +@Date : 2024/10/31 16:13:20 +@FileName : misc.py +@Project : omega-miya +@Description : 工具类等杂项 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from .exclimbwuzhi import gen_buvid_fp, gen_uuid_infoc, get_payload +from .wbi import extract_key_from_wbi_image, sign_wbi_params, sign_wbi_params_nav +from .web_ticket import create_gen_web_ticket_params + +__all__ = [ + 'gen_buvid_fp', + 'get_payload', + 'gen_uuid_infoc', + 'extract_key_from_wbi_image', + 'sign_wbi_params', + 'sign_wbi_params_nav', + 'create_gen_web_ticket_params', +] diff --git a/src/utils/bilibili_api/future/misc/exclimbwuzhi.py b/src/utils/bilibili_api/future/misc/exclimbwuzhi.py new file mode 100644 index 00000000..995eb626 --- /dev/null +++ b/src/utils/bilibili_api/future/misc/exclimbwuzhi.py @@ -0,0 +1,323 @@ +""" +@Author : Ailitonia +@Date : 2024/3/25 0:30 +@FileName : exclimbwuzhi +@Project : nonebot2_miya +@Description : 激活 buvid3 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm + +Web 风控相关问题: buvid3, buvid4 获取及激活(ExClimbWuzhi 上传设备指纹消息)见: +https://github.com/SocialSisterYi/bilibili-API-collect/issues/933 + +相关风控问题见: +https://github.com/SocialSisterYi/bilibili-API-collect/issues/686 +https://github.com/SocialSisterYi/bilibili-API-collect/issues/868 + +Reference: https://github.com/Nemo2011/bilibili-api/commit/f7de473bc42d60604372f80d06244e45a08bdbb4 +From: https://github.com/Nemo2011/bilibili-api/blob/f7de473bc42d60604372f80d06244e45a08bdbb4/bilibili_api/utils/exclimbwuzhi.py +""" + +import io +import random +import struct +import time + +import ujson as json + +MOD = 1 << 64 + + +def get_time_milli() -> int: + return int(time.time() * 1000) + + +def rotate_left(x: int, k: int) -> int: + bin_str = bin(x)[2:].rjust(64, '0') + return int(bin_str[k:] + bin_str[:k], base=2) + + +def gen_uuid_infoc() -> str: + t = get_time_milli() % 100000 + mp = list('123456789ABCDEF') + ['10'] + pck = [8, 4, 4, 4, 12] + + def gen_part(x) -> str: + return ''.join([random.choice(mp) for _ in range(x)]) + + return '-'.join([gen_part(x) for x in pck]) + str(t).ljust(5, '0') + 'infoc' + + +def gen_b_lsid() -> str: + ret = '' + for _ in range(8): + ret += hex(random.randint(0, 15))[2:].upper() + ret = f'{ret}_{hex(get_time_milli())[2:].upper()}' + return ret + + +def gen_buvid_fp(key: str, seed: int): + source = io.BytesIO(bytes(key, 'ascii')) + m = murmur3_x64_128(source, seed) + return f'{hex(m & (MOD - 1))[2:]}{hex(m >> 64)[2:]}' + + +def murmur3_x64_128(source: io.BufferedIOBase, seed: int) -> int: # type: ignore + c1 = 0x87C3_7B91_1142_53D5 + c2 = 0x4CF5_AD43_2745_937F + c3 = 0x52DC_E729 + c4 = 0x3849_5AB5 + r1, r2, r3, m = 27, 31, 33, 5 + h1, h2 = seed, seed + processed = 0 + while 1: + read = source.read(16) + processed += len(read) + if len(read) == 16: + k1 = struct.unpack('= 15: + k2 ^= int(read[14]) << 48 + if len(read) >= 14: + k2 ^= int(read[13]) << 40 + if len(read) >= 13: + k2 ^= int(read[12]) << 32 + if len(read) >= 12: + k2 ^= int(read[11]) << 24 + if len(read) >= 11: + k2 ^= int(read[10]) << 16 + if len(read) >= 10: + k2 ^= int(read[9]) << 8 + if len(read) >= 9: + k2 ^= int(read[8]) + k2 = rotate_left(k2 * c2 % MOD, r3) * c1 % MOD + h2 ^= k2 + if len(read) >= 8: + k1 ^= int(read[7]) << 56 + if len(read) >= 7: + k1 ^= int(read[6]) << 48 + if len(read) >= 6: + k1 ^= int(read[5]) << 40 + if len(read) >= 5: + k1 ^= int(read[4]) << 32 + if len(read) >= 4: + k1 ^= int(read[3]) << 24 + if len(read) >= 3: + k1 ^= int(read[2]) << 16 + if len(read) >= 2: + k1 ^= int(read[1]) << 8 + if len(read) >= 1: + k1 ^= int(read[0]) + k1 = rotate_left(k1 * c1 % MOD, r2) * c2 % MOD + h1 ^= k1 + + +def fmix64(k: int) -> int: + c1 = 0xFF51_AFD7_ED55_8CCD + c2 = 0xC4CE_B9FE_1A85_EC53 + r = 33 + tmp = k + tmp ^= tmp >> r + tmp = tmp * c1 % MOD + tmp ^= tmp >> r + tmp = tmp * c2 % MOD + tmp ^= tmp >> r + return tmp + + +def get_payload() -> str: + content = { + '3064': 1, + '5062': get_time_milli(), + '03bf': 'https%3A%2F%2Fwww.bilibili.com%2F', + '39c8': '333.788.fp.risk', + '34f1': '', + 'd402': '', + '654a': '', + '6e7c': '839x959', + '3c43': { + '2673': 0, + '5766': 24, + '6527': 0, + '7003': 1, + '807e': 1, + 'b8ce': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15', + '641c': 0, + '07a4': 'en-US', + '1c57': 'not available', + '0bd0': 8, + '748e': [900, 1440], + 'd61f': [875, 1440], + 'fc9d': -480, + '6aa9': 'Asia/Shanghai', + '75b8': 1, + '3b21': 1, + '8a1c': 0, + 'd52f': 'not available', + 'adca': 'MacIntel', + '80c9': [ + [ + 'PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'Chrome PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'Chromium PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'Microsoft Edge PDF Viewer', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + [ + 'WebKit built-in PDF', + 'Portable Document Format', + [['application/pdf', 'pdf'], ['text/pdf', 'pdf']], + ], + ], + '13ab': '0dAAAAAASUVORK5CYII=', + 'bfe9': 'QgAAEIQAACEIAABCCQN4FXANGq7S8KTZayAAAAAElFTkSuQmCC', + 'a3c1': [ + 'extensions:ANGLE_instanced_arrays;EXT_blend_minmax;EXT_color_buffer_half_float;EXT_float_blend;EXT_frag_depth;EXT_shader_texture_lod;EXT_texture_compression_bptc;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_color_buffer_float;WEBGL_compressed_texture_astc;WEBGL_compressed_texture_etc;WEBGL_compressed_texture_etc1;WEBGL_compressed_texture_pvrtc;WEBKIT_WEBGL_compressed_texture_pvrtc;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw', + 'webgl aliased line width range:[1, 1]', + 'webgl aliased point size range:[1, 511]', + 'webgl alpha bits:8', + 'webgl antialiasing:yes', + 'webgl blue bits:8', + 'webgl depth bits:24', + 'webgl green bits:8', + 'webgl max anisotropy:16', + 'webgl max combined texture image units:32', + 'webgl max cube map texture size:16384', + 'webgl max fragment uniform vectors:1024', + 'webgl max render buffer size:16384', + 'webgl max texture image units:16', + 'webgl max texture size:16384', + 'webgl max varying vectors:30', + 'webgl max vertex attribs:16', + 'webgl max vertex texture image units:16', + 'webgl max vertex uniform vectors:1024', + 'webgl max viewport dims:[16384, 16384]', + 'webgl red bits:8', + 'webgl renderer:WebKit WebGL', + 'webgl shading language version:WebGL GLSL ES 1.0 (1.0)', + 'webgl stencil bits:0', + 'webgl vendor:WebKit', + 'webgl version:WebGL 1.0', + 'webgl unmasked vendor:Apple Inc.', + 'webgl unmasked renderer:Apple GPU', + 'webgl vertex shader high float precision:23', + 'webgl vertex shader high float precision rangeMin:127', + 'webgl vertex shader high float precision rangeMax:127', + 'webgl vertex shader medium float precision:23', + 'webgl vertex shader medium float precision rangeMin:127', + 'webgl vertex shader medium float precision rangeMax:127', + 'webgl vertex shader low float precision:23', + 'webgl vertex shader low float precision rangeMin:127', + 'webgl vertex shader low float precision rangeMax:127', + 'webgl fragment shader high float precision:23', + 'webgl fragment shader high float precision rangeMin:127', + 'webgl fragment shader high float precision rangeMax:127', + 'webgl fragment shader medium float precision:23', + 'webgl fragment shader medium float precision rangeMin:127', + 'webgl fragment shader medium float precision rangeMax:127', + 'webgl fragment shader low float precision:23', + 'webgl fragment shader low float precision rangeMin:127', + 'webgl fragment shader low float precision rangeMax:127', + 'webgl vertex shader high int precision:0', + 'webgl vertex shader high int precision rangeMin:31', + 'webgl vertex shader high int precision rangeMax:30', + 'webgl vertex shader medium int precision:0', + 'webgl vertex shader medium int precision rangeMin:31', + 'webgl vertex shader medium int precision rangeMax:30', + 'webgl vertex shader low int precision:0', + 'webgl vertex shader low int precision rangeMin:31', + 'webgl vertex shader low int precision rangeMax:30', + 'webgl fragment shader high int precision:0', + 'webgl fragment shader high int precision rangeMin:31', + 'webgl fragment shader high int precision rangeMax:30', + 'webgl fragment shader medium int precision:0', + 'webgl fragment shader medium int precision rangeMin:31', + 'webgl fragment shader medium int precision rangeMax:30', + 'webgl fragment shader low int precision:0', + 'webgl fragment shader low int precision rangeMin:31', + 'webgl fragment shader low int precision rangeMax:30', + ], + '6bc5': 'Apple Inc.~Apple GPU', + 'ed31': 0, + '72bd': 0, + '097b': 0, + '52cd': [0, 0, 0], + 'a658': [ + 'Andale Mono', + 'Arial', + 'Arial Black', + 'Arial Hebrew', + 'Arial Narrow', + 'Arial Rounded MT Bold', + 'Arial Unicode MS', + 'Comic Sans MS', + 'Courier', + 'Courier New', + 'Geneva', + 'Georgia', + 'Helvetica', + 'Helvetica Neue', + 'Impact', + 'LUCIDA GRANDE', + 'Microsoft Sans Serif', + 'Monaco', + 'Palatino', + 'Tahoma', + 'Times', + 'Times New Roman', + 'Trebuchet MS', + 'Verdana', + 'Wingdings', + 'Wingdings 2', + 'Wingdings 3', + ], + 'd02f': '124.04345259929687', + }, + '54ef': '{"in_new_ab":true,"ab_version":{"remove_back_version":"REMOVE","login_dialog_version":"V_PLAYER_PLAY_TOAST","open_recommend_blank":"SELF","storage_back_btn":"HIDE","call_pc_app":"FORBID","clean_version_old":"GO_NEW","optimize_fmp_version":"LOADED_METADATA","for_ai_home_version":"V_OTHER","bmg_fallback_version":"DEFAULT","ai_summary_version":"SHOW","weixin_popup_block":"ENABLE","rcmd_tab_version":"DISABLE","in_new_ab":true},"ab_split_num":{"remove_back_version":11,"login_dialog_version":43,"open_recommend_blank":90,"storage_back_btn":87,"call_pc_app":47,"clean_version_old":46,"optimize_fmp_version":28,"for_ai_home_version":38,"bmg_fallback_version":86,"ai_summary_version":466,"weixin_popup_block":45,"rcmd_tab_version":90,"in_new_ab":0},"pageVersion":"new_video","videoGoOldVersion":-1}', + '8b94': 'https%3A%2F%2Fwww.bilibili.com%2F', + 'df35': '2D9BA3CF-B1ED-1674-2492-CF103D9EFACFE46196infoc', + '07a4': 'en-US', + '5f45': None, + 'db46': 0, + } + return json.dumps( + {'payload': json.dumps(content, separators=(',', ':'))}, + separators=(',', ':'), + ) + + +__all__ = [ + 'gen_buvid_fp', + 'get_payload', + 'gen_uuid_infoc', +] diff --git a/src/utils/bilibili_api/future/misc/wbi.py b/src/utils/bilibili_api/future/misc/wbi.py new file mode 100644 index 00000000..f8057a86 --- /dev/null +++ b/src/utils/bilibili_api/future/misc/wbi.py @@ -0,0 +1,76 @@ +""" +@Author : Ailitonia +@Date : 2024/10/31 16:53:25 +@FileName : wbi.py +@Project : omega-miya +@Description : wbi 接口签名及验证 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +import time +import urllib.parse +from functools import reduce +from hashlib import md5 +from typing import Any + +from ..models import WebInterfaceNav + +__MIXIN_KEY_ENC_TAB = [ + 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, + 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, + 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, + 36, 20, 34, 44, 52 +] + + +def get_mixin_key(orig: str) -> str: + """对 imgKey 和 subKey 进行字符顺序打乱编码""" + return reduce(lambda s, i: s + orig[i], __MIXIN_KEY_ENC_TAB, '')[:32] + + +def enc_wbi(params: dict[str, Any] | None, img_key: str, sub_key: str) -> dict[str, Any]: + """为请求参数进行 wbi 签名""" + mixin_key = get_mixin_key(img_key + sub_key) + curr_time = round(time.time()) + _params: dict[str, Any] = {} if params is None else params + # 添加 wts 字段 + _params.update({'wts': curr_time}) + # 按照 key 重排参数 + _params = dict(sorted(_params.items())) + # 过滤 value 中的 "!'()*" 字符 + _params = { + k: ''.join(filter(lambda x: x not in "!'()*", str(v))) + for k, v + in _params.items() + } + # 序列化参数 + query = urllib.parse.urlencode(_params) + # 计算 w_rid + wbi_sign = md5((query + mixin_key).encode()).hexdigest() + _params.update({'w_rid': wbi_sign}) + return _params + + +def extract_key_from_wbi_image(url: Any) -> str: + """从 img_key 与 sub_key 字段中提取 Token""" + return str(url).rsplit('/', 1)[-1].split('.')[0] + + +def sign_wbi_params(params: dict[str, Any] | None, img_key: str, sub_key: str) -> dict[str, Any]: + """使用 img_key 与 sub_key 数据签名请求参数""" + return enc_wbi(params=params, img_key=img_key, sub_key=sub_key) + + +def sign_wbi_params_nav(nav_data: WebInterfaceNav, params: dict[str, Any] | None) -> dict[str, Any]: + """使用 WebInterfaceNav 原始数据签名请求参数""" + img_key = extract_key_from_wbi_image(url=nav_data.data.wbi_img.img_url) + sub_key = extract_key_from_wbi_image(url=nav_data.data.wbi_img.sub_url) + return enc_wbi(params=params, img_key=img_key, sub_key=sub_key) + + +__all__ = [ + 'extract_key_from_wbi_image', + 'sign_wbi_params', + 'sign_wbi_params_nav', +] diff --git a/src/utils/bilibili_api/future/misc/web_ticket.py b/src/utils/bilibili_api/future/misc/web_ticket.py new file mode 100644 index 00000000..4e127f75 --- /dev/null +++ b/src/utils/bilibili_api/future/misc/web_ticket.py @@ -0,0 +1,47 @@ +""" +@Author : Ailitonia +@Date : 2024/11/5 16:48:00 +@FileName : web_ticket.py +@Project : omega-miya +@Description : BiliTicket 令牌 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +import hashlib +import hmac +import time + + +def hmac_sha256(key: str, message: str) -> str: + """使用 HMAC-SHA256 算法对给定的消息进行散列 + + :param key: 密钥 + :param message: 要加密的消息 + :return: 加密后的哈希值 + """ + + encoded_key = key.encode(encoding='utf-8') + encoded_message = message.encode(encoding='utf-8') + + # 使用 HMAC-SHA256 计算哈希值并转换为十六进制字符串 + hmac_obj = hmac.new(encoded_key, encoded_message, hashlib.sha256) + return hmac_obj.digest().hex() + + +def create_gen_web_ticket_params(bili_jct: str | None = None) -> dict[str, str]: + """生成请求 BiliTicket 的参数""" + timestamp = int(time.time()) + hex_sign = hmac_sha256('XgwSnGZ1p', f'ts{timestamp}') + + return { + 'key_id': 'ec02', + 'hexsign': hex_sign, + 'context[ts]': f'{timestamp}', + 'csrf': '' if bili_jct is None else bili_jct + } + + +__all__ = [ + 'create_gen_web_ticket_params', +] diff --git a/src/utils/bilibili_api/future/models/__init__.py b/src/utils/bilibili_api/future/models/__init__.py new file mode 100644 index 00000000..18bd72b2 --- /dev/null +++ b/src/utils/bilibili_api/future/models/__init__.py @@ -0,0 +1,91 @@ +""" +@Author : Ailitonia +@Date : 2024/10/31 16:54:49 +@FileName : models.py +@Project : omega-miya +@Description : bilibili API 数据模型 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from .dynamic import ( + Dynamics, + DynamicType, + DynData, + DynDetail, + DynItemModules, +) +from .live import ( + RoomBaseInfo, + RoomInfo, + RoomUserInfo, + UsersRoomInfo, +) +from .search import ( + AllSearchResultType, + ArticleSearchResult, + LiveRoomSearchResult, + LiveUserSearchResult, + MediaSearchResult, + PhotoSearchResult, + SearchAllResult, + SearchType, + SearchTypeResult, + TopicSearchResult, + UserSearchResult, + VideoSearchResult, +) +from .sign import ( + Ticket, + WebConfirmRefreshInfo, + WebCookieInfo, + WebCookieRefreshInfo, + WebInterfaceNav, + WebInterfaceSpi, + WebQrcodeGenerateInfo, + WebQrcodePollInfo, +) +from .user import ( + Account, + User, + UserLiveRoom, + UserSpaceRenderData, + VipInfo, +) + +__all__ = [ + 'Dynamics', + 'DynamicType', + 'DynDetail', + 'DynData', + 'DynItemModules', + 'Account', + 'AllSearchResultType', + 'ArticleSearchResult', + 'LiveRoomSearchResult', + 'LiveUserSearchResult', + 'MediaSearchResult', + 'PhotoSearchResult', + 'RoomBaseInfo', + 'RoomInfo', + 'RoomUserInfo', + 'UsersRoomInfo', + 'SearchAllResult', + 'SearchType', + 'SearchTypeResult', + 'Ticket', + 'TopicSearchResult', + 'WebCookieInfo', + 'WebCookieRefreshInfo', + 'WebConfirmRefreshInfo', + 'WebInterfaceNav', + 'WebInterfaceSpi', + 'WebQrcodeGenerateInfo', + 'WebQrcodePollInfo', + 'User', + 'UserLiveRoom', + 'UserSearchResult', + 'UserSpaceRenderData', + 'VideoSearchResult', + 'VipInfo', +] diff --git a/src/utils/bilibili_api/future/models/base_model.py b/src/utils/bilibili_api/future/models/base_model.py new file mode 100644 index 00000000..13db9c9d --- /dev/null +++ b/src/utils/bilibili_api/future/models/base_model.py @@ -0,0 +1,34 @@ +""" +@Author : Ailitonia +@Date : 2024/10/31 16:57:22 +@FileName : base_model.py +@Project : omega-miya +@Description : bilibili API BaseModel +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from pydantic import BaseModel, ConfigDict, Field + + +class BaseBilibiliModel(BaseModel): + """bilibili 数据基类""" + + model_config = ConfigDict(extra='ignore', frozen=True, coerce_numbers_to_str=True) + + +class BaseBilibiliResponse(BaseBilibiliModel): + """bilibili 通用 API 调用响应结果基类""" + code: int + message: str + ttl: int = Field(default=1) + + @property + def error(self) -> bool: + return self.code != 0 + + +__all = [ + 'BaseBilibiliModel', + 'BaseBilibiliResponse', +] diff --git a/src/utils/bilibili_api/future/models/dynamic.py b/src/utils/bilibili_api/future/models/dynamic.py new file mode 100644 index 00000000..5785408a --- /dev/null +++ b/src/utils/bilibili_api/future/models/dynamic.py @@ -0,0 +1,1044 @@ +""" +@Author : Ailitonia +@Date : 2024/11/28 10:56:47 +@FileName : dynamic.py +@Project : omega-miya +@Description : dynamic models +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from enum import StrEnum, unique +from typing import Any + +from pydantic import Field, Json + +from src.compat import AnyHttpUrlStr as AnyHttpUrl +from .base_model import BaseBilibiliModel, BaseBilibiliResponse + + +@unique +class DynamicType(StrEnum): + """动态类型""" + none = 'DYNAMIC_TYPE_NONE' # 无效动态 + forward = 'DYNAMIC_TYPE_FORWARD' # 动态转发 + av = 'DYNAMIC_TYPE_AV' # 投稿视频 + pgc = 'DYNAMIC_TYPE_PGC' # 剧集(番剧、电影、纪录片) + pgc_union = 'DYNAMIC_TYPE_PGC_UNION' + courses = 'DYNAMIC_TYPE_COURSES' + word = 'DYNAMIC_TYPE_WORD' # 纯文字动态 + draw = 'DYNAMIC_TYPE_DRAW' # 带图动态 + article = 'DYNAMIC_TYPE_ARTICLE' # 投稿专栏 + music = 'DYNAMIC_TYPE_MUSIC' # 音乐 + common_square = 'DYNAMIC_TYPE_COMMON_SQUARE' # 装扮/剧集点评/普通分享 + common_vertical = 'DYNAMIC_TYPE_COMMON_VERTICAL' + live = 'DYNAMIC_TYPE_LIVE' # 直播间分享 + medialist = 'DYNAMIC_TYPE_MEDIALIST' # 收藏夹 + courses_season = 'DYNAMIC_TYPE_COURSES_SEASON' # 课程 + courses_batch = 'DYNAMIC_TYPE_COURSES_BATCH' + ad = 'DYNAMIC_TYPE_AD' + applet = 'DYNAMIC_TYPE_APPLET' + subscription = 'DYNAMIC_TYPE_SUBSCRIPTION' + live_rcmd = 'DYNAMIC_TYPE_LIVE_RCMD' # 直播开播 + banner = 'DYNAMIC_TYPE_BANNER' + ugc_season = 'DYNAMIC_TYPE_UGC_SEASON' # 合集更新 + subscription_new = 'DYNAMIC_TYPE_SUBSCRIPTION_NEW' + + +@unique +class RichTextNodeType(StrEnum): + """富文本节点类型""" + none = 'RICH_TEXT_NODE_TYPE_NONE' + text = 'RICH_TEXT_NODE_TYPE_TEXT' # 文字节点 + at = 'RICH_TEXT_NODE_TYPE_AT' # @用户 + lottery = 'RICH_TEXT_NODE_TYPE_LOTTERY' # 互动抽奖 + vote = 'RICH_TEXT_NODE_TYPE_VOTE' # 投票 + topic = 'RICH_TEXT_NODE_TYPE_TOPIC' # 话题 + goods = 'RICH_TEXT_NODE_TYPE_GOODS' # 商品链接 + bv = 'RICH_TEXT_NODE_TYPE_BV' # 视频链接 + av = 'RICH_TEXT_NODE_TYPE_AV' + emoji = 'RICH_TEXT_NODE_TYPE_EMOJI' # 表情 + user = 'RICH_TEXT_NODE_TYPE_USER' + cv = 'RICH_TEXT_NODE_TYPE_CV' + vc = 'RICH_TEXT_NODE_TYPE_VC' + web = 'RICH_TEXT_NODE_TYPE_WEB' # 网页链接 + taobao = 'RICH_TEXT_NODE_TYPE_TAOBAO' + mail = 'RICH_TEXT_NODE_TYPE_MAIL' # 邮箱地址 + ogv_season = 'RICH_TEXT_NODE_TYPE_OGV_SEASON' # 剧集信息 + ogv_ep = 'RICH_TEXT_NODE_TYPE_OGV_EP' + search_word = 'RICH_TEXT_NODE_TYPE_SEARCH_WORD' + view_picture = 'RICH_TEXT_NODE_TYPE_VIEW_PICTURE' + + +@unique +class AuthorType(StrEnum): + """作者类型""" + none = 'AUTHOR_TYPE_NONE' + normal = 'AUTHOR_TYPE_NORMAL' # 普通更新 + pgc = 'AUTHOR_TYPE_PGC' # 剧集更新 + ugc_season = 'AUTHOR_TYPE_UGC_SEASON' # 合集更新 + + +@unique +class EmojiType(StrEnum): + """表情类型""" + none = 'EMOJI_TYPE_NONE' + old = 'EMOJI_TYPE_OLD' + new = 'EMOJI_TYPE_NEW' + vip = 'EMOJI_TYPE_VIP' + + +@unique +class AdditionalType(StrEnum): + """相关内容卡片类型""" + none = 'ADDITIONAL_TYPE_NONE' + pgc = 'ADDITIONAL_TYPE_PGC' + goods = 'ADDITIONAL_TYPE_GOODS' # 商品信息 + vote = 'ADDITIONAL_TYPE_VOTE' # 投票 + common = 'ADDITIONAL_TYPE_COMMON' # 一般类型 + match = 'ADDITIONAL_TYPE_MATCH' + up_rcmd = 'ADDITIONAL_TYPE_UP_RCMD' + ugc = 'ADDITIONAL_TYPE_UGC' # 视频跳转 + reserve = 'ADDITIONAL_TYPE_RESERVE' + + +@unique +class AdditionalButtonType(StrEnum): + """相关内容卡片类型 (BUTTON)""" + none = 'ADDITIONAL_BUTTON_TYPE_NONE' + jump = 'ADDITIONAL_BUTTON_TYPE_JUMP' + button = 'ADDITIONAL_BUTTON_TYPE_BUTTON' + + +@unique +class AdditionalButtonStatus(StrEnum): + """相关内容卡片类型 (BUTTON_STATUS)""" + none = 'ADDITIONAL_BUTTON_STATUS_NONE' + uncheck = 'ADDITIONAL_BUTTON_STATUS_UNCHECK' + check = 'ADDITIONAL_BUTTON_STATUS_CHECK' + + +@unique +class AddButtonClickType(StrEnum): + """相关内容卡片类型 (ADD_BUTTON_CLICK)""" + none = 'ADD_BUTTON_CLICK_TYPE_NONE' + reserve = 'ADD_BUTTON_CLICK_TYPE_RESERVE' + + +@unique +class DisableState(StrEnum): + """相关内容卡片类型 (DISABLE_STATE)""" + highlight = 'DISABLE_STATE_HIGHLIGHT' + gray = 'DISABLE_STATE_GRAY' + + +@unique +class AddButtonBgStyle(StrEnum): + """相关内容卡片类型 (ADD_BUTTON_BG_STYLE)""" + fill = 'ADD_BUTTON_BG_STYLE_FILL' + stroke = 'ADD_BUTTON_BG_STYLE_STROKE' + gray = 'ADD_BUTTON_BG_STYLE_GRAY' + + +@unique +class HighlightTextStyleType(StrEnum): + """相关内容卡片类型 (HIGHLIGHT_TEXT_STYLE)""" + none = 'HIGHLIGHT_TEXT_STYLE_TYPE_NONE' + active = 'HIGHLIGHT_TEXT_STYLE_TYPE_ACTIVE' + + +@unique +class MajorType(StrEnum): + """动态主体类型""" + none = 'MAJOR_TYPE_NONE' # 动态失效/转发动态 + opus = 'MAJOR_TYPE_OPUS' # 图文动态 + archive = 'MAJOR_TYPE_ARCHIVE' # 视频 + pgc = 'MAJOR_TYPE_PGC' # 剧集更新 + courses = 'MAJOR_TYPE_COURSES' + draw = 'MAJOR_TYPE_DRAW' # 带图动态 + article = 'MAJOR_TYPE_ARTICLE' + music = 'MAJOR_TYPE_MUSIC' # 音频更新 + common = 'MAJOR_TYPE_COMMON' # 一般类型 + live = 'MAJOR_TYPE_LIVE' # 直播间分享 + medialist = 'MAJOR_TYPE_MEDIALIST' + applet = 'MAJOR_TYPE_APPLET' + subscription = 'MAJOR_TYPE_SUBSCRIPTION' + live_rcmd = 'MAJOR_TYPE_LIVE_RCMD' # 直播状态 + ugc_season = 'MAJOR_TYPE_UGC_SEASON' # 合计更新 + subscription_new = 'MAJOR_TYPE_SUBSCRIPTION_NEW' + + +@unique +class MediaType(StrEnum): + """动态主体类型 (MEDIA)""" + none = 'MEDIA_TYPE_NONE' + ugc = 'MEDIA_TYPE_UGC' + pgc = 'MEDIA_TYPE_PGC' + live = 'MEDIA_TYPE_LIVE' + + +@unique +class PgcSubType(StrEnum): + """动态主体类型 (PGC_SUB)""" + none = 'PGC_SUB_TYPE_NONE' + bangumi = 'PGC_SUB_TYPE_BANGUMI' + movie = 'PGC_SUB_TYPE_MOVIE' + documentary = 'PGC_SUB_TYPE_DOCUMENTARY' + domestic = 'PGC_SUB_TYPE_DOMESTIC' + tv = 'PGC_SUB_TYPE_TV' + + +@unique +class DrawTagType(StrEnum): + """动态主体类型 (DRAW_TAG)""" + none = 'DRAW_TAG_TYPE_NONE' + common = 'DRAW_TAG_TYPE_COMMON' + goods = 'DRAW_TAG_TYPE_GOODS' + user = 'DRAW_TAG_TYPE_USER' + topic = 'DRAW_TAG_TYPE_TOPIC' + lbs = 'DRAW_TAG_TYPE_LBS' + + +@unique +class MajorCommonStyleType(StrEnum): + """动态主体类型 (MAJOR_COMMON_STYLE)""" + none = 'MAJOR_COMMON_STYLE_TYPE_NONE' + square = 'MAJOR_COMMON_STYLE_TYPE_SQUARE' + vertical = 'MAJOR_COMMON_STYLE_TYPE_VERTICAL' + + +@unique +class ReserveType(StrEnum): + """动态主体类型 (RESERVE)""" + none = 'RESERVE_TYPE_NONE' + recall = 'RESERVE_TYPE_RECALL' + + +@unique +class LiveStateType(StrEnum): + """动态主体类型 (LIVE_STATE)""" + none = 'LIVE_STATE_TYPE_NONE' + live = 'LIVE_STATE_TYPE_LIVE' + rotation = 'LIVE_STATE_TYPE_ROTATION' + + +@unique +class SubscriptionNewStyleType(StrEnum): + """动态主体类型 (SUBSCRIPTION_NEW_STYLE)""" + none = 'SUBSCRIPTION_NEW_STYLE_TYPE_NONE' + draw = 'SUBSCRIPTION_NEW_STYLE_TYPE_DRAW' + live = 'SUBSCRIPTION_NEW_STYLE_TYPE_LIVE' + + +@unique +class ThreePointType(StrEnum): + """动态主体类型 (THREE_POINT)""" + delete = 'THREE_POINT_DELETE' # 删除 + report = 'THREE_POINT_REPORT' # 举报 + following = 'THREE_POINT_FOLLOWING' # 关注/取消关注 + top = 'THREE_POINT_TOP' # 置顶/取消置顶 + unfav = 'THREE_POINT_UNFAV' + unsubs = 'THREE_POINT_UNSUBS' + topic_report = 'THREE_POINT_TOPIC_REPORT' + topic_irrelevant = 'THREE_POINT_TOPIC_IRRELEVANT' + rcmd_resource = 'THREE_POINT_RCMD_RESOURCE' + rcmd_feedback = 'THREE_POINT_RCMD_FEEDBACK' + + +@unique +class FoldType(StrEnum): + """动态主体类型 (FOLD)""" + none = 'FOLD_TYPE_NONE' + publish = 'FOLD_TYPE_PUBLISH' + frequent = 'FOLD_TYPE_FREQUENT' + unite = 'FOLD_TYPE_UNITE' + limit = 'FOLD_TYPE_LIMIT' + + +@unique +class DynStatusType(StrEnum): + """动态主体类型 (DYN_STATUS)""" + none = 'DYN_STATUS_TYPE_NONE' + normal = 'DYN_STATUS_TYPE_NORMAL' + auditing = 'DYN_STATUS_TYPE_AUDITING' + self_visible = 'DYN_STATUS_TYPE_SELF_VISIBLE' + deleted = 'DYN_STATUS_TYPE_DELETED' + + +@unique +class SceneType(StrEnum): + """动态主体类型 (SCENE)""" + detail = 'SCENE_DETAIL' + hot = 'SCENE_HOT' + general = 'SCENE_GENERAL' + space = 'SCENE_SPACE' + topic = 'SCENE_TOPIC' + + +class DynItemModuleAuthor(BaseBilibiliModel): + """UP 主信息""" + # avatar: dict[str, Any] + face: AnyHttpUrl + face_nft: bool + following: bool | None = Field(False) + jump_url: str + label: str + mid: str + name: str + # official_verify: dict[str, Any] + # pendant: dict[str, Any] + pub_action: str = Field('') + pub_location_text: str = Field('') + pub_time: str = Field('') + pub_ts: int + type: AuthorType + # vip: dict[str, Any] + + +class BaseAdditionalItemDesc(BaseBilibiliModel): + style: int + text: str + + +class AdditionalNoneItem(BaseBilibiliModel): + """动态失效/转发动态""" + + +class AdditionalPgcItem(BaseBilibiliModel): + """剧集类型""" + + +class AdditionalGoodsItem(BaseBilibiliModel): + """商品内容""" + head_icon: str = Field('') + head_text: str + items: list[dict[str, Any]] + jump_url: str = Field('') + + +class AdditionalVoteItem(BaseBilibiliModel): + """投票信息""" + choice_cnt: int + default_share: int + desc: str + end_time: int + join_num: int + status: int + # type: Any | None + uid: str + vote_id: str + + +class AdditionalCommonItem(BaseBilibiliModel): + """一般类型""" + # button: dict[str, Any] + cover: str + desc1: str + desc2: str + head_text: str + id_str: str + jump_url: str + style: int + sub_type: str + title: str + + +class AdditionalMatchItem(BaseBilibiliModel): + """ADDITIONAL_TYPE_MATCH""" + + +class AdditionalUpRcmdItem(BaseBilibiliModel): + """直播状态更新""" + + +class AdditionalUgcItem(BaseBilibiliModel): + """视频信息""" + cover: str + desc_second: str + duration: str + head_text: str = Field('') + id_str: str + jump_url: str + multi_line: bool + title: str + + +class AdditionalReserveItem(BaseBilibiliModel): + """预约信息""" + # button: dict[str, Any] + desc1: BaseAdditionalItemDesc + desc2: BaseAdditionalItemDesc + # desc3: BaseAdditionalItemDesc + jump_url: str + reserve_total: int + rid: str + state: int + stype: int + title: str + up_mid: str + + + +class BaseModuleDynamicAdditional(BaseBilibiliModel): + """相关内容卡片信息""" + type: AdditionalType + + +class ModuleDynamicAdditionalNone(BaseModuleDynamicAdditional): + """一般类型""" + none: AdditionalNoneItem + + +class ModuleDynamicAdditionalPgc(BaseModuleDynamicAdditional): + """一般类型""" + pgc: AdditionalPgcItem + + +class ModuleDynamicAdditionalGoods(BaseModuleDynamicAdditional): + """商品内容""" + goods: AdditionalGoodsItem + + +class ModuleDynamicAdditionalVote(BaseModuleDynamicAdditional): + """投票信息""" + vote: AdditionalVoteItem + + +class ModuleDynamicAdditionalCommon(BaseModuleDynamicAdditional): + """一般类型""" + common: AdditionalCommonItem + + +class ModuleDynamicAdditionalMatch(BaseModuleDynamicAdditional): + """一般类型""" + match: AdditionalMatchItem + + +class ModuleDynamicAdditionalUpRcmd(BaseModuleDynamicAdditional): + """一般类型""" + up_rcmd: AdditionalUpRcmdItem + + +class ModuleDynamicAdditionalUgc(BaseModuleDynamicAdditional): + """视频信息""" + ugc: AdditionalUgcItem + + +class ModuleDynamicAdditionalReserve(BaseModuleDynamicAdditional): + """预约信息""" + reserve: AdditionalReserveItem + + +type ModuleDynamicAdditional = ( + ModuleDynamicAdditionalNone + | ModuleDynamicAdditionalPgc + | ModuleDynamicAdditionalGoods + | ModuleDynamicAdditionalVote + | ModuleDynamicAdditionalCommon + | ModuleDynamicAdditionalMatch + | ModuleDynamicAdditionalUpRcmd + | ModuleDynamicAdditionalReserve + | ModuleDynamicAdditionalUgc + | BaseModuleDynamicAdditional +) + + +class DescRichTextNodeEmoji(BaseBilibiliModel): + icon_url: str + size: int + text: str + type: int + + +class DescRichTextNode(BaseBilibiliModel): + orig_text: str + text: str + type: RichTextNodeType + emoji: DescRichTextNodeEmoji | None = Field(None) + jump_url: str | None = Field(None) + rid: str | None = Field(None) + + +class ModuleDynamicDesc(BaseBilibiliModel): + """动态文字内容""" + rich_text_nodes: list[DescRichTextNode] + text: str + + +class MajorNoneItem(BaseBilibiliModel): + """动态失效/转发动态""" + tips: str = Field('动态已失效或已被删除') + + +class MajorOpusItem(BaseBilibiliModel): + """图文动态""" + + class _Pic(BaseBilibiliModel): + height: int + width: int + size: str + url: str + live_url: str | None = Field(None) + + fold_action: list[str] + jump_url: str + pics: list[_Pic] + summary: ModuleDynamicDesc + title: str | None = Field(None) + + +class MajorArchiveItem(BaseBilibiliModel): + """视频信息""" + aid: str + # badge: dict[str, Any] + bvid: str + cover: str + desc: str + disable_preview: int + duration_text: str + jump_url: str + stat: dict[str, Any] + title: str + type: int + + +class MajorPgcItem(BaseBilibiliModel): + """剧集信息""" + # badge: dict[str, Any] + cover: str + epid: str + jump_url: str + season_id: str + stat: dict[str, Any] + sub_type: int + title: str + type: int = Field(2) + + +class MajorCoursesItem(BaseBilibiliModel): + """课程信息""" + # badge: dict[str, Any] + cover: str + desc: str + id: str + jump_url: str + sub_title: str + title: str + + +class MajorDrawItem(BaseBilibiliModel): + """带图动态""" + + class _Item(BaseBilibiliModel): + height: int + width: int + size: str + src: str + tags: list[str] + + id: str + items: list[_Item] + + +class MajorArticleItem(BaseBilibiliModel): + """专栏类型""" + covers: list[str] + desc: str + id: str + jump_url: str + label: str + title: str + + +class MajorMusicItem(BaseBilibiliModel): + """音频信息""" + cover: str + id: str + jump_url: str + label: str + title: str + + +class MajorCommonItem(BaseBilibiliModel): + """一般类型""" + # badge: dict[str, Any] + biz_type: int = Field(0) + cover: str + desc: str + id: str + jump_url: str + label: str = Field('') + sketch_id: str + style: int = Field(1) + title: str + + +class MajorLiveItem(BaseBilibiliModel): + """直播间分享""" + # badge: dict[str, Any] + cover: str + desc_first: str # 直播主分区名称 + desc_second: str # 观看人数 + id: str + jump_url: str + live_state: int + reserve_type: int = Field(0) + title: str + + +class MajorLiveRcmdItem(BaseBilibiliModel): + """直播状态""" + class _Content(BaseBilibiliModel): + class _LivePlayInfo(BaseBilibiliModel): + area_id: int + area_name: str + parent_area_id: int + parent_area_name: str + live_start_time: int + room_id: int + room_type: int + room_paid_type: int + play_type: int + cover: str + uid: int + online: int + link: str + live_id: str + live_screen_type: int + live_status: int + title: str + + type: int + live_play_info: _LivePlayInfo + + content: Json[_Content] + reserve_type: str + + +class MajorMedialistItem(BaseBilibiliModel): + """合集信息""" + + +class MajorAppletItem(BaseBilibiliModel): + """小程序信息""" + + +class MajorSubscriptionItem(BaseBilibiliModel): + """订阅信息""" + + +class MajorSubscriptionNewItem(BaseBilibiliModel): + """订阅信息""" + + +class MajorUgcSeasonItem(BaseBilibiliModel): + """合集信息""" + aid: str + # badge: dict[str, Any] + cover: str + desc: str + disable_preview: int + duration_text: str + jump_url: str + stat: dict[str, Any] + title: str + + +class BaseModuleDynamicMajor(BaseBilibiliModel): + """动态主体对象""" + type: MajorType + + def get_major_image_urls(self) -> list[str]: + """获取图片链接""" + return [] + + def get_major_text(self) -> str: + """获取文本内容""" + return '' + + +class ModuleDynamicMajorNone(BaseModuleDynamicMajor): + """动态失效/转发动态""" + none: MajorNoneItem + + def get_major_text(self) -> str: + return self.none.tips + + + +class ModuleDynamicMajorOpus(BaseModuleDynamicMajor): + """图文动态""" + opus: MajorOpusItem + + def get_major_image_urls(self) -> list[str]: + return [x.url for x in self.opus.pics] + + def get_major_text(self) -> str: + return self.opus.summary.text + + +class ModuleDynamicMajorArchive(BaseModuleDynamicMajor): + """视频信息""" + archive: MajorArchiveItem + + def get_major_image_urls(self) -> list[str]: + return [self.archive.cover] + + def get_major_text(self) -> str: + return ( + f'《{self.archive.title}》\n{self.archive.desc}\n' + f'视频传送门: https://{self.archive.jump_url.removeprefix("//")}' + ) + + +class ModuleDynamicMajorPgc(BaseModuleDynamicMajor): + """剧集信息""" + pgc: MajorPgcItem + + def get_major_image_urls(self) -> list[str]: + return [self.pgc.cover] + + def get_major_text(self) -> str: + return ( + f'《{self.pgc.title}》\n' + f'剧集传送门: {self.pgc.jump_url.removeprefix("//")}' + ) + + +class ModuleDynamicMajorCourses(BaseModuleDynamicMajor): + """课程信息""" + courses: MajorCoursesItem + + def get_major_image_urls(self) -> list[str]: + return [self.courses.cover] + + def get_major_text(self) -> str: + return ( + f'《{self.courses.title}》\n{self.courses.desc}\n' + f'课程传送门: https://{self.courses.jump_url.removeprefix("//")}' + ) + + +class ModuleDynamicMajorDraw(BaseModuleDynamicMajor): + """带图动态""" + draw: MajorDrawItem + + def get_major_image_urls(self) -> list[str]: + return [x.src for x in self.draw.items] + + def get_major_text(self) -> str: + return '' + + +class ModuleDynamicMajorArticle(BaseModuleDynamicMajor): + """专栏类型""" + article: MajorArticleItem + + def get_major_image_urls(self) -> list[str]: + return self.article.covers + + def get_major_text(self) -> str: + return ( + f'《{self.article.title}》\n{self.article.desc}\n' + f'专栏传送门: https://{self.article.jump_url.removeprefix("//")}' + ) + + +class ModuleDynamicMajorMusic(BaseModuleDynamicMajor): + """音频信息""" + music: MajorMusicItem + + def get_major_image_urls(self) -> list[str]: + return [self.music.cover] + + def get_major_text(self) -> str: + return ( + f'《{self.music.title}》\n' + f'音频传送门: https://{self.music.jump_url.removeprefix("//")}' + ) + + +class ModuleDynamicMajorCommon(BaseModuleDynamicMajor): + """一般类型""" + common: MajorCommonItem + + def get_major_image_urls(self) -> list[str]: + return [self.common.cover] + + def get_major_text(self) -> str: + return f'{self.common.title}\n{self.common.desc}' + + +class ModuleDynamicMajorLive(BaseModuleDynamicMajor): + """直播间分享""" + live: MajorLiveItem + + def get_major_image_urls(self) -> list[str]: + return [self.live.cover] + + def get_major_text(self) -> str: + return ( + f'{self.live.title}\n' + f'直播间传送门: https://{self.live.jump_url.removeprefix("//")}' + ) + + +class ModuleDynamicMajorLiveRcmd(BaseModuleDynamicMajor): + """直播状态""" + live_rcmd: MajorLiveRcmdItem + + def get_major_image_urls(self) -> list[str]: + return [self.live_rcmd.content.live_play_info.cover] + + def get_major_text(self) -> str: + return self.live_rcmd.content.live_play_info.title + + +class ModuleDynamicMajorMedialist(BaseModuleDynamicMajor): + """合集信息""" + medialist: MajorMedialistItem + + +class ModuleDynamicMajorApplet(BaseModuleDynamicMajor): + """小程序信息""" + applet: MajorAppletItem + + +class ModuleDynamicMajorSubscription(BaseModuleDynamicMajor): + """订阅信息""" + subscription: MajorSubscriptionItem + + +class ModuleDynamicMajorSubscriptionNew(BaseModuleDynamicMajor): + """订阅信息""" + subscription_new: MajorSubscriptionNewItem + + +class ModuleDynamicMajorUgcSeason(BaseModuleDynamicMajor): + """合集信息""" + ugc_season: MajorUgcSeasonItem + + def get_major_image_urls(self) -> list[str]: + return [self.ugc_season.cover] + + def get_major_text(self) -> str: + return ( + f'《{self.ugc_season.title}》\n{self.ugc_season.desc}\n' + f'合集传送门: https://{self.ugc_season.jump_url.removeprefix("//")}' + ) + + +type ModuleDynamicMajor = ( + ModuleDynamicMajorNone + | ModuleDynamicMajorOpus + | ModuleDynamicMajorArchive + | ModuleDynamicMajorPgc + | ModuleDynamicMajorCourses + | ModuleDynamicMajorDraw + | ModuleDynamicMajorArticle + | ModuleDynamicMajorMusic + | ModuleDynamicMajorCommon + | ModuleDynamicMajorLive + | ModuleDynamicMajorLiveRcmd + | ModuleDynamicMajorMedialist + | ModuleDynamicMajorApplet + | ModuleDynamicMajorSubscription + | ModuleDynamicMajorSubscriptionNew + | ModuleDynamicMajorUgcSeason + | BaseModuleDynamicMajor +) + + +class ModuleDynamicTopic(BaseBilibiliModel): + """话题信息""" + id: str + jump_url: str + name: str + + +class DynItemModuleDynamic(BaseBilibiliModel): + """动态内容信息""" + additional: ModuleDynamicAdditional | None = Field(None) # 相关内容卡片信息 + desc: ModuleDynamicDesc | None = Field(None) # 动态文字内容 + major: ModuleDynamicMajor | None = Field(None) # 动态主体对象 + topic: ModuleDynamicTopic | None = Field(None) # 话题信息 + + +class DynItemModuleMore(BaseBilibiliModel): + """动态右上角三点菜单""" + three_point_items: list[dict[str, Any]] + + +class DynItemModuleStat(BaseBilibiliModel): + """动态统计数据""" + comment: dict[str, Any] + forward: dict[str, Any] + like: dict[str, Any] + + +class DynItemModuleInteraction(BaseBilibiliModel): + """热度评论""" + items: list[dict[str, Any]] + + +class DynItemModuleFold(BaseBilibiliModel): + """动态折叠信息""" + ids: list[str] + statement: str + type: int = Field(1) + users: list[str] = Field(default_factory=list) + + +class DynItemModuleDispute(BaseBilibiliModel): + """争议小黄条""" + desc: str + jump_url: str + title: str + + +class DynItemModuleTag(BaseBilibiliModel): + """置顶信息""" + text: str + + +class DynItemModules(BaseBilibiliModel): + """动态信息""" + module_author: DynItemModuleAuthor + module_dynamic: DynItemModuleDynamic + # module_more: DynItemModuleMore | None = Field(None) + # module_stat: DynItemModuleStat | None = Field(None) + # module_interaction: DynItemModuleInteraction | None = Field(None) + # module_fold: DynItemModuleFold | None = Field(None) + # module_dispute: DynItemModuleDispute | None = Field(None) + # module_tag: DynItemModuleTag | None = Field(None) + + @property + def uname(self) -> str: + return self.module_author.name + + @property + def pub_text(self) -> str: + """动态发布说明文本""" + return ( + f'{self.uname}{f" {pub_time} " if (pub_time := self.module_author.pub_time) else ""}' + f'{pub_action if (pub_action := self.module_author.pub_action) else "发布了新动态"}' + ) + + @property + def desc_text(self) -> str: + """动态内容文本""" + return desc.text if (desc := self.module_dynamic.desc) is not None else '' + + @property + def major_text(self) -> str: + """动态主体内容文本""" + return major.get_major_text() if (major := self.module_dynamic.major) is not None else '' + + @property + def dyn_text(self) -> str: + """格式化动态内容文本""" + return ( + f'{self.pub_text}' + f'{f"\n\n“{self.desc_text}”" if self.desc_text else ""}' + f'{f"\n\n{self.major_text}" if self.major_text else ""}' + ) + + @property + def dyn_image_urls(self) -> list[str]: + """动态图片链接列表""" + return self.module_dynamic.major.get_major_image_urls() if self.module_dynamic.major is not None else [] + + +class DynItemBasic(BaseBilibiliModel): + comment_id_str: str + comment_type: str + rid_str: str + + +class DynCommonItem(BaseBilibiliModel): + basic: DynItemBasic + id_str: str + modules: DynItemModules + type: DynamicType + visible: bool + + @property + def dyn_text(self) -> str: + """动态内容文本""" + return self.modules.dyn_text + + @property + def dyn_image_urls(self) -> list[str]: + """动态图片链接列表""" + return self.modules.dyn_image_urls + + +class DynForwardItem(DynCommonItem): + orig: DynCommonItem + + @property + def dyn_text(self) -> str: + """动态内容文本""" + return ( + f'{self.modules.dyn_text}' + f'{f"\n{"=" * 8}转发动态{"=" * 8}\n{self.orig.dyn_text}" if self.orig.dyn_text else ""}' + ) + + @property + def dyn_image_urls(self) -> list[str]: + """动态图片链接列表""" + return self.modules.dyn_image_urls + self.orig.dyn_image_urls + + +type DynItem = DynForwardItem | DynCommonItem + + +class DynData(BaseBilibiliModel): + has_more: bool + items: list[DynItem] + offset: str + update_baseline: str + update_num: int + + +class Dynamics(BaseBilibiliResponse): + """获取动态列表结果""" + data: DynData + + +class DynDetail(BaseBilibiliResponse): + """获取单条动态详情结果""" + class _Item(BaseBilibiliModel): + item: DynItem + + data: _Item + + +__all__ = [ + 'BaseModuleDynamicMajor', + 'Dynamics', + 'DynamicType', + 'DynDetail', + 'DynData', + 'DynItem', + 'DynCommonItem', + 'DynForwardItem', + 'DynItemModules', + 'ModuleDynamicMajor', + 'ModuleDynamicMajorNone', + 'ModuleDynamicMajorOpus', + 'ModuleDynamicMajorArchive', + 'ModuleDynamicMajorPgc', + 'ModuleDynamicMajorCourses', + 'ModuleDynamicMajorDraw', + 'ModuleDynamicMajorArticle', + 'ModuleDynamicMajorMusic', + 'ModuleDynamicMajorCommon', + 'ModuleDynamicMajorLive', + 'ModuleDynamicMajorLiveRcmd', + 'ModuleDynamicMajorMedialist', + 'ModuleDynamicMajorApplet', + 'ModuleDynamicMajorSubscription', + 'ModuleDynamicMajorSubscriptionNew', + 'ModuleDynamicMajorUgcSeason', +] diff --git a/src/utils/bilibili_api/future/models/live.py b/src/utils/bilibili_api/future/models/live.py new file mode 100644 index 00000000..479a4145 --- /dev/null +++ b/src/utils/bilibili_api/future/models/live.py @@ -0,0 +1,95 @@ +""" +@Author : Ailitonia +@Date : 2024/12/24 14:09:35 +@FileName : live.py +@Project : omega-miya +@Description : live models +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from datetime import datetime +from enum import IntEnum, unique + +from pydantic import Field, field_validator + +from src.compat import AnyHttpUrlStr as AnyHttpUrl +from .base_model import BaseBilibiliModel, BaseBilibiliResponse +from ..consts import DEFAULT_LOCAL_TZ + + +@unique +class LiveStatus(IntEnum): + offline = 0 # 未开播 + streaming = 1 # 正在直播 + rotating = 2 # 轮播中 + + +class RoomInfoData(BaseBilibiliModel): + uid: str + room_id: str + short_id: str + live_status: LiveStatus + live_time: datetime | str = Field(default_factory=datetime.now) + uname: str = Field('bilibili用户') + title: str + description: str = Field('') + cover: AnyHttpUrl | str = Field('') + user_cover: AnyHttpUrl | str = Field('') + cover_from_user: AnyHttpUrl | str = Field('') + + @property + def cover_url(self) -> str: + return self.cover or self.user_cover or self.cover_from_user + + @field_validator('live_time', mode='after') + @classmethod + def time_zone_conversion(cls, v): + if isinstance(v, datetime): + v = v.astimezone(DEFAULT_LOCAL_TZ) + return v + + +class RoomBaseInfoData(BaseBilibiliModel): + # by_uids: dict[str, Any] + by_room_ids: dict[str, RoomInfoData] + + +class RoomBaseInfo(BaseBilibiliResponse): + data: RoomBaseInfoData + + +class RoomInfo(BaseBilibiliResponse): + data: RoomInfoData + + +class UsersRoomInfo(BaseBilibiliResponse): + data: dict[int, RoomInfoData] + + +class UserDataInfo(BaseBilibiliModel): + uid: int + uname: str + face: str + rank: str + gender: int + + +class RoomUserData(BaseBilibiliModel): + info: UserDataInfo + # level: dict[str, Any] + san: int + + +class RoomUserInfo(BaseBilibiliResponse): + data: RoomUserData + + +__all__ = [ + 'LiveStatus', + 'RoomBaseInfo', + 'RoomInfo', + 'RoomInfoData', + 'RoomUserInfo', + 'UsersRoomInfo', +] diff --git a/src/utils/bilibili_api/future/models/search.py b/src/utils/bilibili_api/future/models/search.py new file mode 100644 index 00000000..275c8ae0 --- /dev/null +++ b/src/utils/bilibili_api/future/models/search.py @@ -0,0 +1,406 @@ +""" +@Author : Ailitonia +@Date : 2024/11/15 17:59:38 +@FileName : search.py +@Project : omega-miya +@Description : search models +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Literal + +from pydantic import Field + +from src.compat import AnyHttpUrlStr as AnyHttpUrl +from .base_model import BaseBilibiliModel, BaseBilibiliResponse + + +class ActivitySearchResult(BaseBilibiliModel): + """站内活动搜索结果""" + type: Literal['activity'] + id: int + title: str + desc: str + cover: str + corner: str + pos: int + url: str + card_type: int + card_value: str + state: int + status: int + author: str + position: int + + +class WebGameSearchResult(BaseBilibiliModel): + """游戏(网页)搜索结果""" + game_base_id: int + game_name: str + game_icon: str + summary: str + game_status: int + game_link: str + grade: float + book_num: int + download_num: int + comment_num: int + platform: str + notice_title: str + notice: str + game_tags: str + recommend_reason: str + official_account: int + + +class VideoSearchResult(BaseBilibiliModel): + """视频搜索结果""" + type: Literal['video'] + id: int + author: str + mid: int + typeid: str + typename: str + arcurl: str + aid: int + bvid: str + title: str + description: str + pic: str + play: int + video_review: int + favorites: int + tag: str + review: int + pubdate: int + senddate: int + duration: str + is_union_video: int + rank_score: int + hit_columns: list[str] + + +class _MediaScore(BaseBilibiliModel): + user_count: int + score: float + + +class _Ep(BaseBilibiliModel): + id: int + cover: AnyHttpUrl + title: str + url: AnyHttpUrl + release_date: str + index_title: str + long_title: str + + +class MediaSearchResult(BaseBilibiliModel): + """番剧&影视搜索结果""" + type: Literal['media_bangumi', 'media_ft'] + media_id: int + season_id: int + title: str + org_title: str + cover: str + media_type: int + areas: str + styles: str + cv: str + staff: str + goto_url: AnyHttpUrl + desc: str + pubtime: int + fix_pubtime_str: str + pgc_season_id: int + season_type: int + season_type_name: str + selection_style: str + ep_size: int + eps: list[_Ep] + url: str + is_follow: int + media_score: _MediaScore + hit_columns: list[str] + hit_epids: str + + +class LiveRoomSearchResult(BaseBilibiliModel): + """直播间搜索结果""" + type: Literal['live_room'] + rank_offset: int + uid: int + roomid: int + short_id: int + tags: str + live_time: str + cate_name: str + live_status: int + uname: str + uface: str + user_cover: str + area: int + title: str + cover: str + online: int + rank_index: int + rank_score: int + attentions: int | None = Field(default=None) + hit_columns: list[str] + + +class LiveUserSearchResult(BaseBilibiliModel): + """主播搜索结果""" + type: Literal['live_user'] + rank_offset: int + uid: int + roomid: int + tags: str + live_time: str + live_status: int + area: int + is_live: bool + uname: str + uface: str + rank_index: int + rank_score: int + attentions: int + hit_columns: list[str] + + +class ArticleSearchResult(BaseBilibiliModel): + """专栏搜索结果""" + type: Literal['article'] + id: int + category_name: str + title: str + mid: int + desc: str + image_urls: list[str] + pub_time: int + template_id: int + category_id: int + view: int + like: int + reply: int + rank_offset: int + rank_index: int + rank_score: int + + +class TopicSearchResult(BaseBilibiliModel): + """话题搜索结果""" + type: Literal['topic'] + description: str + pubdate: int + title: str + mid: int + author: str + arcurl: str + keyword: str + cover: str + update: int + favourite: int + review: int + tp_id: int + tp_type: int + rank_offset: int + rank_index: int + rank_score: int + hit_columns: list[str] + + +class _Res(BaseBilibiliModel): + aid: int + bvid: str + title: str + pubdate: int + arcurl: str + pic: str + play: str + dm: int + coin: int + fav: int + desc: str + duration: str + is_pay: int | None = Field(default=None) + is_union_video: int + + +class _OfficialVerify(BaseBilibiliModel): + type: int + desc: str + + +class UserSearchResult(BaseBilibiliModel): + """用户搜索结果""" + type: Literal['bili_user'] + mid: int + uname: str + usign: str + fans: int + videos: int + upic: str + level: int + gender: int + is_upuser: int + is_live: int + room_id: int + res: list[_Res] + official_verify: _OfficialVerify + hit_columns: list[str] + + +class PhotoSearchResult(BaseBilibiliModel): + """相簿搜索结果""" + type: Literal['photo'] + id: int + mid: int + title: str + cover: str + uname: str + count: int + like: int + view: int + rank_index: int + rank_score: int + rank_offset: int + hit_columns: list[str] + + +type SearchType = Literal[ + 'video', + 'media_bangumi', + 'media_ft', + 'live', + 'live_room', + 'live_user', + 'article', + 'topic', + 'bili_user', + 'photo', +] + +type AllSearchResultType = ActivitySearchResult | WebGameSearchResult | VideoSearchResult | MediaSearchResult | LiveRoomSearchResult | LiveUserSearchResult | ArticleSearchResult | TopicSearchResult | UserSearchResult | PhotoSearchResult + + +class _PageInfoCount(BaseBilibiliModel): + num_results: int = Field(alias='numResults') + total: int + pages: int + + +class _PageInfo(BaseBilibiliModel): + pgc: _PageInfoCount + live_room: _PageInfoCount + # photo: _PageInfoCount # maybe deactivated + topic: _PageInfoCount + video: _PageInfoCount + user: _PageInfoCount + bili_user: _PageInfoCount + media_ft: _PageInfoCount + article: _PageInfoCount + media_bangumi: _PageInfoCount + special: _PageInfoCount + operation_card: _PageInfoCount + upuser: _PageInfoCount + movie: _PageInfoCount + live_all: _PageInfoCount + tv: _PageInfoCount + live: _PageInfoCount + bangumi: _PageInfoCount + activity: _PageInfoCount + live_master: _PageInfoCount + live_user: _PageInfoCount + + +class _TopTlist(BaseBilibiliModel): + pgc: int + live_room: int + # photo: int # maybe deactivated + topic: int + video: int + user: int + bili_user: int + media_ft: int + article: int + media_bangumi: int + card: int + operation_card: int + upuser: int + movie: int + # live_all: int # maybe deactivated + tv: int + live: int + special: int + bangumi: int + activity: int + live_master: int + live_user: int + + +class _SearchAllDataResult(BaseBilibiliModel): + result_type: str + data: list[AllSearchResultType] + + +class _SearchAllData(BaseBilibiliModel): + seid: str + page: int = Field(default=1) + page_size: int = Field(default=20) + numResults: int = Field(default=1000) + numPages: int = Field(default=50) + suggest_keyword: str + rqt_type: str + pageinfo: _PageInfo + top_tlist: _TopTlist + show_column: int + show_module_list: list[str] + result: list[_SearchAllDataResult] + + +class SearchAllResult(BaseBilibiliResponse): + """api.bilibili.com/x/web-interface/wbi/search/all/v2 返回值""" + data: _SearchAllData + + @property + def all_results(self) -> list[AllSearchResultType]: + return [x for results in self.data.result for x in results.data] + + +class _SearchTypeData(BaseBilibiliModel): + seid: str + page: int = Field(default=1) + page_size: int = Field(default=20) + numResults: int = Field(default=1000) + numPages: int = Field(default=50) + suggest_keyword: str + rqt_type: str + show_column: int + result: list[AllSearchResultType] + + +class SearchTypeResult(BaseBilibiliResponse): + """api.bilibili.com/x/web-interface/wbi/search/type 返回值""" + data: _SearchTypeData + + @property + def all_results(self) -> list[AllSearchResultType]: + return [x for x in self.data.result] + + +__all__ = [ + 'VideoSearchResult', + 'MediaSearchResult', + 'LiveRoomSearchResult', + 'LiveUserSearchResult', + 'ArticleSearchResult', + 'TopicSearchResult', + 'UserSearchResult', + 'PhotoSearchResult', + 'AllSearchResultType', + 'SearchAllResult', + 'SearchType', + 'SearchTypeResult', +] diff --git a/src/utils/bilibili_api/future/models/sign.py b/src/utils/bilibili_api/future/models/sign.py new file mode 100644 index 00000000..1f742e93 --- /dev/null +++ b/src/utils/bilibili_api/future/models/sign.py @@ -0,0 +1,148 @@ +""" +@Author : Ailitonia +@Date : 2024/10/31 16:55:52 +@FileName : sign.py +@Project : omega-miya +@Description : sign models +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + + +from src.compat import AnyHttpUrlStr as AnyHttpUrl + +from .base_model import BaseBilibiliModel, BaseBilibiliResponse + + +class TicketNav(BaseBilibiliModel): + img: str + sub: str + + +class TicketData(BaseBilibiliModel): + ticket: str + created_at: int + ttl: int + context: dict | None + nav: TicketNav + + +class Ticket(BaseBilibiliResponse): + """api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket 返回值""" + data: TicketData + + +class WbiImg(BaseBilibiliModel): + img_url: AnyHttpUrl + sub_url: AnyHttpUrl + + +class WebInterfaceNavData(BaseBilibiliModel): + isLogin: bool + wbi_img: WbiImg + uname: str | None = None + mid: str | None = None + + +class WebInterfaceNav(BaseBilibiliResponse): + """api.bilibili.com/x/web-interface/nav 返回值""" + data: WebInterfaceNavData + + +class WebInterfaceSpiData(BaseBilibiliModel): + b_3: str + b_4: str + + +class WebInterfaceSpi(BaseBilibiliResponse): + """api.bilibili.com/x/frontend/finger/spi 返回值""" + data: WebInterfaceSpiData + + +class WebCookieInfoData(BaseBilibiliModel): + """ + - refresh: 是否应该刷新 Cookie, true: 需要刷新 Cookie, false: 无需刷新 Cookie + - timestamp: 当前毫秒时间戳, 用于获取 refresh_csrf + """ + refresh: bool + timestamp: int + + +class WebCookieInfo(BaseBilibiliResponse): + """passport.bilibili.com/x/passport-login/web/cookie/info 返回值""" + data: WebCookieInfoData + + +class WebQrcodeGenerateData(BaseBilibiliModel): + url: str + qrcode_key: str + + +class WebQrcodeGenerateInfo(BaseBilibiliResponse): + """passport.bilibili.com/x/passport-login/web/qrcode/generate 返回值""" + data: WebQrcodeGenerateData + + +class WebQrcodePollData(BaseBilibiliModel): + """ + code: + - 86101: 未扫码 + - 86090: 已扫描未确认 + - 86038: 二维码过期 + - 0: 成功 + """ + url: str + refresh_token: str + timestamp: int + code: int + message: str + + +class WebQrcodePollInfo(BaseBilibiliResponse): + """passport.bilibili.com/x/passport-login/web/qrcode/poll 返回值""" + data: WebQrcodePollData + + +class WebCookieRefreshData(BaseBilibiliModel): + """ + - refresh_token: 新的持久化刷新口令, 用于更新配置中的 ac_time_value 字段, 以便下次使用 + """ + status: int + message: str + refresh_token: str + + +class WebCookieRefreshInfo(BaseBilibiliResponse): + """passport.bilibili.com/x/passport-login/web/cookie/refresh 返回值 + + code: + - 0: 成功 + - -101: 账号未登录 + - -111: csrf 校验失败 + - 86095: refresh_csrf 错误或 refresh_token 与 cookie 不匹配 + """ + data: WebCookieRefreshData + + +class WebConfirmRefreshInfo(BaseBilibiliResponse): + """passport.bilibili.com/x/passport-login/web/confirm/refresh 返回值 + + code: + - 0: 成功 + - -101: 账号未登录 + - -111: csrf 校验失败 + - -400: 请求错误 + """ + + +__all__ = [ + 'Ticket', + 'WebInterfaceNav', + 'WebInterfaceSpi', + 'WebCookieInfo', + 'WebQrcodeGenerateInfo', + 'WebQrcodePollData', + 'WebQrcodePollInfo', + 'WebCookieRefreshInfo', + 'WebConfirmRefreshInfo', +] diff --git a/src/utils/bilibili_api/future/models/user.py b/src/utils/bilibili_api/future/models/user.py new file mode 100644 index 00000000..ecd791ab --- /dev/null +++ b/src/utils/bilibili_api/future/models/user.py @@ -0,0 +1,107 @@ +""" +@Author : Ailitonia +@Date : 2024/11/15 16:19:17 +@FileName : user.py +@Project : omega-miya +@Description : user models +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from pydantic import Field + +from src.compat import AnyHttpUrlStr as AnyHttpUrl +from .base_model import BaseBilibiliModel, BaseBilibiliResponse + + +class AccountData(BaseBilibiliModel): + mid: int + uname: str + userid: str + sign: str + birthday: str + sex: str + nick_free: bool + rank: str + + +class Account(BaseBilibiliResponse): + """api.bilibili.com/x/member/web/account 返回值""" + data: AccountData + + +class VipData(BaseBilibiliModel): + mid: int + vip_type: int + vip_status: int + vip_due_date: int + vip_pay_type: int + theme_type: int + + +class VipInfo(BaseBilibiliResponse): + data: VipData + + +class UserOfficial(BaseBilibiliModel): + role: int + title: str + desc: str + type: int + + +class UserLiveRoom(BaseBilibiliModel): + room_status: int = Field(alias='roomStatus') + live_status: int = Field(alias='liveStatus') + url: AnyHttpUrl + title: str + cover: AnyHttpUrl + roomid: int + round_status: int = Field(alias='roundStatus') + broadcast_type: int + + +class UserData(BaseBilibiliModel): + mid: int + name: str + sex: str + face: AnyHttpUrl + sign: str + rank: int + level: int + jointime: int + # moral: int + # silence: int + # coins: int + # official: UserOfficial + is_followed: bool + top_photo: AnyHttpUrl + live_room: UserLiveRoom | None = Field(None) + birthday: str + + +class User(BaseBilibiliResponse): + """api.bilibili.com/x/space/wbi/acc/info 返回值""" + data: UserData + + @property + def mid(self) -> int: + return self.data.mid + + @property + def uname(self) -> str: + return self.data.name + + +class UserSpaceRenderData(BaseBilibiliModel): + """space.bilibili.com 页面 __RENDER_DATA__ 元素内容""" + access_id: str + + +__all__ = [ + 'Account', + 'User', + 'UserLiveRoom', + 'UserSpaceRenderData', + 'VipInfo', +] diff --git a/src/utils/booru_api/__init__.py b/src/utils/booru_api/__init__.py index efb63c25..eeb6101b 100644 --- a/src/utils/booru_api/__init__.py +++ b/src/utils/booru_api/__init__.py @@ -13,7 +13,6 @@ from .gelbooru import GelbooruAPI from .moebooru import BehoimiAPI, KonachanAPI, KonachanSafeAPI, YandereAPI - danbooru_api = DanbooruAPI(username=booru_config.danbooru_username, api_key=booru_config.danbooru_api_key) gelbooru_api = GelbooruAPI(user_id=booru_config.gelbooru_user_id, api_key=booru_config.gelbooru_api_key) behoimi_api = BehoimiAPI(login_name=booru_config.behoimi_login_name, diff --git a/src/utils/booru_api/danbooru.py b/src/utils/booru_api/danbooru.py index 9e206dda..55e506fd 100644 --- a/src/utils/booru_api/danbooru.py +++ b/src/utils/booru_api/danbooru.py @@ -9,34 +9,34 @@ """ import abc -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from src.compat import parse_obj_as -from src.utils.common_api import BaseCommonAPI +from src.utils import BaseCommonAPI from .models.danbooru import ( Artist, ArtistCommentary, - Note, - Pool, - Post, - Wiki, - ArtistVersion, ArtistCommentaryVersion, - NoteVersion, - PoolVersion, - PostVersion, - WikiPageVersion, + ArtistVersion, Comment, Dmail, ForumPost, ForumTopic, + Note, + NoteVersion, + Pool, + PoolVersion, + Post, PostAppeal, PostFlag, + PostVersion, Tag, TagAlias, TagImplication, Upload, User, + Wiki, + WikiPageVersion, ) if TYPE_CHECKING: @@ -46,7 +46,7 @@ class BaseDanbooruAPI(BaseCommonAPI, abc.ABC): """Danbooru API 基类, 文档见 https://danbooru.donmai.us/wiki_pages/help:api""" - def __init__(self, *, username: Optional[str] = None, api_key: Optional[str] = None): + def __init__(self, *, username: str | None = None, api_key: str | None = None): self.__username = username self.__api_key = api_key @@ -66,17 +66,17 @@ def _load_cloudflare_clearance(cls) -> bool: return False @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None async def get_json( self, url: str, - params: Optional[dict[str, Any]] = None, + params: dict[str, Any] | None = None, ) -> Any: """使用 GET 方法请求 API, 返回 json 内容""" if isinstance(params, dict): @@ -89,7 +89,7 @@ async def get_json( async def get_resource_as_bytes( self, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 30, ) -> bytes: @@ -98,7 +98,7 @@ async def get_resource_as_bytes( async def get_resource_as_text( self, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 10, ) -> str: @@ -106,8 +106,8 @@ async def get_resource_as_text( @staticmethod def generate_common_search_params( - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> dict[str, Any]: """全站通用搜索参数""" @@ -129,8 +129,8 @@ def generate_common_search_params( async def artists_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Artist]: """Show artists index""" @@ -156,8 +156,8 @@ async def artist_show(self, id_: int) -> Artist: async def artist_commentaries_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[ArtistCommentary]: """Show artist commentaries index""" @@ -177,8 +177,8 @@ async def artist_commentary_show(self, id_: int) -> ArtistCommentary: async def notes_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Note]: """Show notes index""" @@ -198,8 +198,8 @@ async def note_show(self, id_: int) -> Note: async def pools_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Pool]: """Show pools index""" @@ -219,8 +219,8 @@ async def pool_show(self, id_: int) -> Pool: async def posts_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Post]: """Show posts index""" @@ -277,8 +277,8 @@ async def post_show_artist_commentary(self, id_: int) -> ArtistCommentary: async def wikis_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Wiki]: """Show wikis index""" @@ -298,8 +298,8 @@ async def wiki_show(self, id_: int) -> Wiki: async def artist_versions_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[ArtistVersion]: """Show artist versions index""" @@ -319,8 +319,8 @@ async def artist_version_show(self, id_: int) -> ArtistVersion: async def artist_commentary_versions_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[ArtistCommentaryVersion]: """Show artist commentary versions index""" @@ -340,8 +340,8 @@ async def artist_commentary_version_show(self, id_: int) -> ArtistCommentaryVers async def note_versions_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[NoteVersion]: """Show note versions index""" @@ -361,8 +361,8 @@ async def note_version_show(self, id_: int) -> NoteVersion: async def pool_versions_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[PoolVersion]: """Show pool versions index""" @@ -376,8 +376,8 @@ async def pool_versions_index( async def post_versions_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[PostVersion]: """Show post versions index""" @@ -391,8 +391,8 @@ async def post_versions_index( async def wiki_page_versions_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[WikiPageVersion]: """Show wiki page versions index""" @@ -406,8 +406,8 @@ async def wiki_page_versions_index( async def comments_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Comment]: """Show comments index""" @@ -427,8 +427,8 @@ async def comment_show(self, id_: int) -> Comment: async def dmails_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Dmail]: """Show dmails index""" @@ -437,7 +437,7 @@ async def dmails_index( params = self.generate_common_search_params(page=page, limit=limit, **search_kwargs) return parse_obj_as(list[Dmail], await self.get_json(url=index_url, params=params)) - async def dmail_show(self, id_: int, key: Optional[str] = None) -> Dmail: + async def dmail_show(self, id_: int, key: str | None = None) -> Dmail: """Show dmail data""" url = f'{self._get_root_url()}/dmails/{id_}.json' @@ -449,8 +449,8 @@ async def dmail_show(self, id_: int, key: Optional[str] = None) -> Dmail: async def forum_posts_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[ForumPost]: """Show forum posts index""" @@ -470,8 +470,8 @@ async def forum_post_show(self, id_: int) -> ForumPost: async def forum_topics_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[ForumTopic]: """Show forum topics index""" @@ -491,8 +491,8 @@ async def forum_topic_show(self, id_: int) -> ForumTopic: async def post_appeals_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[PostAppeal]: """Show post appeals index""" @@ -512,8 +512,8 @@ async def post_appeal_show(self, id_: int) -> PostAppeal: async def post_flags_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[PostFlag]: """Show post flags index""" @@ -533,8 +533,8 @@ async def post_flag_show(self, id_: int) -> PostFlag: async def tags_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Tag]: """Show tags index""" @@ -554,8 +554,8 @@ async def tag_show(self, id_: int) -> Tag: async def tag_aliases_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[TagAlias]: """Show tag aliases index""" @@ -575,8 +575,8 @@ async def tag_alias_show(self, id_: int) -> TagAlias: async def tag_implications_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[TagImplication]: """Show tag implications index""" @@ -596,8 +596,8 @@ async def tag_implication_show(self, id_: int) -> TagImplication: async def uploads_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[Upload]: """Show uploads index""" @@ -617,8 +617,8 @@ async def upload_show(self, id_: int) -> Upload: async def users_index( self, *, - page: Optional[int] = None, - limit: Optional[int] = None, + page: int | None = None, + limit: int | None = None, **search_kwargs ) -> list[User]: """Show users index""" diff --git a/src/utils/booru_api/gelbooru.py b/src/utils/booru_api/gelbooru.py index d8e3cb56..cd79dbd7 100644 --- a/src/utils/booru_api/gelbooru.py +++ b/src/utils/booru_api/gelbooru.py @@ -9,10 +9,10 @@ """ import abc -from typing import TYPE_CHECKING, Any, Literal, Optional +from typing import TYPE_CHECKING, Any, Literal -from src.utils.common_api import BaseCommonAPI -from .models.gelbooru import Post, PostsData, TagsData, UsersData, CommentsData +from src.utils import BaseCommonAPI +from .models.gelbooru import CommentsData, Post, PostsData, TagsData, UsersData if TYPE_CHECKING: from nonebot.internal.driver import CookieTypes, HeaderTypes, QueryTypes @@ -21,7 +21,7 @@ class BaseGelbooruAPI(BaseCommonAPI, abc.ABC): """Gelbooru API 基类, 文档见 https://gelbooru.com/index.php?page=help&topic=dapi""" - def __init__(self, *, user_id: Optional[str] = None, api_key: Optional[str] = None): + def __init__(self, *, user_id: str | None = None, api_key: str | None = None): self.__user_id = user_id self.__api_key = api_key @@ -41,17 +41,17 @@ def _load_cloudflare_clearance(cls) -> bool: return False @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None async def get_json( self, url: str, - params: Optional[dict[str, Any]] = None, + params: dict[str, Any] | None = None, ) -> Any: """使用 GET 方法请求 API, 返回 json 内容""" if isinstance(params, dict): @@ -64,7 +64,7 @@ async def get_json( async def get_resource_as_bytes( self, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 30, ) -> bytes: @@ -73,7 +73,7 @@ async def get_resource_as_bytes( async def get_resource_as_text( self, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 10, ) -> str: @@ -84,11 +84,11 @@ async def get_resource_as_text( async def posts_index( self, *, - limit: Optional[int] = None, - page: Optional[int] = None, - tags: Optional[str] = None, - cid: Optional[int] = None, - id_: Optional[int] = None, + limit: int | None = None, + page: int | None = None, + tags: str | None = None, + cid: int | None = None, + id_: int | None = None, ) -> PostsData: index_url = f'{self._get_root_url()}/index.php' params = {'page': 'dapi', 's': 'post', 'q': 'index', 'json': '1'} @@ -117,14 +117,14 @@ async def post_show(self, id_: int) -> Post: async def tags_index( self, - limit: Optional[int] = None, - id_: Optional[int] = None, - after_id: Optional[int] = None, - name: Optional[str] = None, - names: Optional[str] = None, - name_pattern: Optional[str] = None, - order: Optional[Literal['ASC', 'DESC']] = None, - orderby: Optional[Literal['date', 'count', 'name']] = None, + limit: int | None = None, + id_: int | None = None, + after_id: int | None = None, + name: str | None = None, + names: str | None = None, + name_pattern: str | None = None, + order: Literal['ASC', 'DESC'] | None = None, + orderby: Literal['date', 'count', 'name'] | None = None, ) -> TagsData: index_url = f'{self._get_root_url()}/index.php' params = {'page': 'dapi', 's': 'tag', 'q': 'index', 'json': '1'} @@ -152,10 +152,10 @@ async def tags_index( async def users_index( self, - limit: Optional[int] = None, - page: Optional[int] = None, - name: Optional[str] = None, - name_pattern: Optional[str] = None, + limit: int | None = None, + page: int | None = None, + name: str | None = None, + name_pattern: str | None = None, ) -> UsersData: index_url = f'{self._get_root_url()}/index.php' params = {'page': 'dapi', 's': 'user', 'q': 'index', 'json': '1'} @@ -175,7 +175,7 @@ async def users_index( async def comments_index( self, - post_id: Optional[int] = None, + post_id: int | None = None, ) -> CommentsData: index_url = f'{self._get_root_url()}/index.php' params = {'page': 'dapi', 's': 'comment', 'q': 'index', 'json': '1'} diff --git a/src/utils/booru_api/models/danbooru.py b/src/utils/booru_api/models/danbooru.py index 49fa6b33..b37e975d 100644 --- a/src/utils/booru_api/models/danbooru.py +++ b/src/utils/booru_api/models/danbooru.py @@ -10,7 +10,7 @@ from datetime import datetime from enum import IntEnum, StrEnum, unique -from typing import Any, Literal, Optional, Union, TypeVar +from typing import Any, Literal, TypeVar from pydantic import BaseModel, ConfigDict, Field, IPvAnyNetwork @@ -67,7 +67,7 @@ class Pool(BaseDanbooruModel): post_ids: list[int] category: Literal['series', 'collection'] is_deleted: bool - is_active: Optional[bool] = None # unused + is_active: bool | None = None # unused created_at: datetime updated_at: datetime @@ -103,31 +103,24 @@ class PostVariantTypeOriginal(PostVariant): type: Literal['original'] -type PostVariantTypes = Union[ - PostVariantType180, - PostVariantType360, - PostVariantType720, - PostVariantTypeSample, - PostVariantTypeFull, - PostVariantTypeOriginal -] +type PostVariantTypes = PostVariantType180 | PostVariantType360 | PostVariantType720 | PostVariantTypeSample | PostVariantTypeFull | PostVariantTypeOriginal PostVariant_T = TypeVar('PostVariant_T', bound=PostVariant) class PostMediaAsset(BaseDanbooruModel): id: int - md5: Optional[str] = None - file_key: Optional[str] = None + md5: str | None = None + file_key: str | None = None file_ext: str file_size: int image_width: int image_height: int - duration: Optional[Any] = None + duration: Any | None = None status: str is_public: bool pixel_hash: str - variants: Optional[list[PostVariantTypes]] = None + variants: list[PostVariantTypes] | None = None created_at: datetime updated_at: datetime @@ -169,7 +162,7 @@ def variant_type_original(self) -> PostVariantTypeOriginal | None: class Post(BaseDanbooruModel): id: int uploader_id: int - approver_id: Optional[int] + approver_id: int | None is_banned: bool is_deleted: bool is_flagged: bool @@ -185,8 +178,8 @@ class Post(BaseDanbooruModel): tag_count_copyright: int tag_count_character: int tag_count_meta: int - rating: Optional[Literal['g', 's', 'q', 'e']] # includes [g, s, q, e] - parent_id: Optional[int] + rating: Literal['g', 's', 'q', 'e'] | None # includes [g, s, q, e] + parent_id: int | None has_children: bool has_active_children: bool has_visible_children: bool @@ -194,18 +187,18 @@ class Post(BaseDanbooruModel): image_width: int image_height: int source: str - md5: Optional[str] = None # some image need Gold+ account - file_url: Optional[str] = None # some image need Gold+ account - large_file_url: Optional[str] = None # some image need Gold+ account - preview_file_url: Optional[str] = None # some image need Gold+ account + md5: str | None = None # some image need Gold+ account + file_url: str | None = None # some image need Gold+ account + large_file_url: str | None = None # some image need Gold+ account + preview_file_url: str | None = None # some image need Gold+ account file_ext: str file_size: int score: int up_score: int down_score: int fav_count: int - last_comment_bumped_at: Optional[datetime] - last_noted_at: Optional[datetime] + last_comment_bumped_at: datetime | None + last_noted_at: datetime | None media_asset: PostMediaAsset # not in api docs but it actually exists created_at: datetime updated_at: datetime @@ -236,7 +229,7 @@ class ArtistVersion(BaseDanbooruModel): updater_id: int created_at: datetime updated_at: datetime - updater_addr_ip: Optional[IPvAnyNetwork] = None # Limited to Moderator+ + updater_addr_ip: IPvAnyNetwork | None = None # Limited to Moderator+ class ArtistCommentaryVersion(BaseDanbooruModel): @@ -249,7 +242,7 @@ class ArtistCommentaryVersion(BaseDanbooruModel): updater_id: int created_at: datetime updated_at: datetime - updater_addr_ip: Optional[IPvAnyNetwork] = None # Limited to Moderator+ + updater_addr_ip: IPvAnyNetwork | None = None # Limited to Moderator+ class NoteVersion(BaseDanbooruModel): @@ -266,7 +259,7 @@ class NoteVersion(BaseDanbooruModel): updater_id: int created_at: datetime updated_at: datetime - updater_addr_ip: Optional[IPvAnyNetwork] = None # Limited to Moderator+ + updater_addr_ip: IPvAnyNetwork | None = None # Limited to Moderator+ class PoolVersion(BaseDanbooruModel): @@ -280,14 +273,14 @@ class PoolVersion(BaseDanbooruModel): category: Literal['collection', 'series'] name_changed: bool description_changed: bool - is_active: Optional[bool] = None # unused + is_active: bool | None = None # unused is_deleted: bool - boolean: Optional[bool] = None # unused + boolean: bool | None = None # unused version: int updater_id: int created_at: datetime updated_at: datetime - updater_addr_ip: Optional[IPvAnyNetwork] = None # Limited to Moderator+ + updater_addr_ip: IPvAnyNetwork | None = None # Limited to Moderator+ class PostVersion(BaseDanbooruModel): @@ -297,16 +290,16 @@ class PostVersion(BaseDanbooruModel): added_tags: list[str] # tag format removed_tags: list[str] # tag format rating: Literal['g', 's', 'q', 'e'] - parent_id: Optional[int] + parent_id: int | None source: str rating_changed: bool parent_changed: bool source_changed: bool version: int updater_id: int - created_at: Optional[datetime] = None # Actually not exists + created_at: datetime | None = None # Actually not exists updated_at: datetime - updater_addr_ip: Optional[IPvAnyNetwork] = None # Limited to Moderator+ + updater_addr_ip: IPvAnyNetwork | None = None # Limited to Moderator+ class WikiPageVersion(BaseDanbooruModel): @@ -320,7 +313,7 @@ class WikiPageVersion(BaseDanbooruModel): updater_id: int created_at: datetime updated_at: datetime - updater_addr_ip: Optional[IPvAnyNetwork] = None # Limited to Moderator+ + updater_addr_ip: IPvAnyNetwork | None = None # Limited to Moderator+ """Non-versioned Types""" @@ -338,8 +331,8 @@ class Comment(BaseDanbooruModel): updater_id: int created_at: datetime updated_at: datetime - creator_ip_addr: Optional[IPvAnyNetwork] = None # Limited to Moderator+ - updater_ip_addr: Optional[IPvAnyNetwork] = None # Limited to Moderator+ + creator_ip_addr: IPvAnyNetwork | None = None # Limited to Moderator+ + updater_ip_addr: IPvAnyNetwork | None = None # Limited to Moderator+ class Dmail(BaseDanbooruModel): @@ -351,7 +344,7 @@ class Dmail(BaseDanbooruModel): body: str is_read: bool is_deleted: bool - is_spam: Optional[bool] = None # obsolete + is_spam: bool | None = None # obsolete key: bool created_at: datetime updated_at: datetime @@ -428,7 +421,7 @@ class PostFlag(BaseDanbooruModel): status: PostFlagStatus category: PostFlagCategory # Not in API docs but it exists is_resolved: bool - creator_id: Optional[int] = None # limited to Moderator+ or the flag creator + creator_id: int | None = None # limited to Moderator+ or the flag creator created_at: datetime updated_at: datetime @@ -450,7 +443,7 @@ class Tag(BaseDanbooruModel): is_deprecated: bool created_at: datetime updated_at: datetime - words: Optional[list[str]] = None # Not in API docs but it exists, the split words of tag + words: list[str] | None = None # Not in API docs but it exists, the split words of tag class TagAlias(BaseDanbooruModel): @@ -458,11 +451,11 @@ class TagAlias(BaseDanbooruModel): antecedent_name: str consequent_name: str status: Literal['active', 'deleted', 'retired'] - reason: Optional[str] = None # unused - forum_topic_id: Optional[int] - forum_post_id: Optional[int] + reason: str | None = None # unused + forum_topic_id: int | None + forum_post_id: int | None creator_id: int - approver_id: Optional[int] + approver_id: int | None created_at: datetime updated_at: datetime @@ -472,11 +465,11 @@ class TagImplication(BaseDanbooruModel): antecedent_name: str consequent_name: str status: Literal['active', 'deleted', 'retired'] - reason: Optional[str] = None # unused - forum_topic_id: Optional[int] - forum_post_id: Optional[int] + reason: str | None = None # unused + forum_topic_id: int | None + forum_post_id: int | None creator_id: int - approver_id: Optional[int] + approver_id: int | None created_at: datetime updated_at: datetime @@ -488,7 +481,7 @@ class Upload(BaseDanbooruModel): referer_url: str media_asset_count: int status: str - error: Optional[str] + error: str | None created_at: datetime updated_at: datetime @@ -497,67 +490,67 @@ class User(BaseDanbooruModel): id: int name: str level: Literal[10, 20, 30, 31, 32, 40, 50] - level_string: Optional[str] = None - inviter_id: Optional[int] + level_string: str | None = None + inviter_id: int | None post_update_count: int note_update_count: int post_upload_count: int - favorite_count: Optional[int] = None - unread_dmail_count: Optional[int] = None + favorite_count: int | None = None + unread_dmail_count: int | None = None is_banned: bool - is_deleted: Optional[bool] = None - receive_email_notifications: Optional[bool] = None - always_resize_images: Optional[bool] = None - enable_post_navigation: Optional[bool] = None - new_post_navigation_layout: Optional[bool] = None - enable_private_favorites: Optional[bool] = None - enable_sequential_post_navigation: Optional[bool] = None - hide_deleted_posts: Optional[bool] = None - style_usernames: Optional[bool] = None - enable_auto_complete: Optional[bool] = None - show_deleted_children: Optional[bool] = None - disable_categorized_saved_searches: Optional[bool] = None - disable_tagged_filenames: Optional[bool] = None - disable_cropped_thumbnails: Optional[bool] = None - disable_mobile_gestures: Optional[bool] = None - enable_safe_mode: Optional[bool] = None - enable_desktop_mode: Optional[bool] = None - disable_post_tooltips: Optional[bool] = None - enable_recommended_posts: Optional[bool] = None - requires_verification: Optional[bool] = None - is_verified: Optional[bool] = None - bit_prefs: Optional[int] = None # Each bit stores a boolean value. See Bit fields below for more information. - theme: Optional[Literal['auto', 'light', 'dark']] = None - favorite_tags: Optional[str] = None - blacklisted_tags: Optional[str] = None - comment_threshold: Optional[int] = None - timezone: Optional[str] = None - per_page: Optional[int] = None - default_image_size: Optional[Literal['large', 'original']] = None - custom_css: Optional[str] = None - upload_points: Optional[str] = None - time_zone: Optional[str] = None - show_deleted_posts: Optional[bool] = None - statement_timeout: Optional[int] = None - favorite_group_limit: Optional[int] = None - tag_query_limit: Optional[int] = None - max_saved_searches: Optional[int] = None - wiki_page_version_count: Optional[int] = None - artist_version_count: Optional[int] = None - artist_commentary_version_count: Optional[int] = None - pool_version_count: Optional[int] = None - forum_post_count: Optional[int] = None - comment_count: Optional[int] = None - favorite_group_count: Optional[int] = None - appeal_count: Optional[int] = None - flag_count: Optional[int] = None - positive_feedback_count: Optional[int] = None - neutral_feedback_count: Optional[int] = None - negative_feedback_count: Optional[int] = None - last_forum_read_at: Optional[datetime] = None - last_logged_in_at: Optional[datetime] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + is_deleted: bool | None = None + receive_email_notifications: bool | None = None + always_resize_images: bool | None = None + enable_post_navigation: bool | None = None + new_post_navigation_layout: bool | None = None + enable_private_favorites: bool | None = None + enable_sequential_post_navigation: bool | None = None + hide_deleted_posts: bool | None = None + style_usernames: bool | None = None + enable_auto_complete: bool | None = None + show_deleted_children: bool | None = None + disable_categorized_saved_searches: bool | None = None + disable_tagged_filenames: bool | None = None + disable_cropped_thumbnails: bool | None = None + disable_mobile_gestures: bool | None = None + enable_safe_mode: bool | None = None + enable_desktop_mode: bool | None = None + disable_post_tooltips: bool | None = None + enable_recommended_posts: bool | None = None + requires_verification: bool | None = None + is_verified: bool | None = None + bit_prefs: int | None = None # Each bit stores a boolean value. See Bit fields below for more information. + theme: Literal['auto', 'light', 'dark'] | None = None + favorite_tags: str | None = None + blacklisted_tags: str | None = None + comment_threshold: int | None = None + timezone: str | None = None + per_page: int | None = None + default_image_size: Literal['large', 'original'] | None = None + custom_css: str | None = None + upload_points: str | None = None + time_zone: str | None = None + show_deleted_posts: bool | None = None + statement_timeout: int | None = None + favorite_group_limit: int | None = None + tag_query_limit: int | None = None + max_saved_searches: int | None = None + wiki_page_version_count: int | None = None + artist_version_count: int | None = None + artist_commentary_version_count: int | None = None + pool_version_count: int | None = None + forum_post_count: int | None = None + comment_count: int | None = None + favorite_group_count: int | None = None + appeal_count: int | None = None + flag_count: int | None = None + positive_feedback_count: int | None = None + neutral_feedback_count: int | None = None + negative_feedback_count: int | None = None + last_forum_read_at: datetime | None = None + last_logged_in_at: datetime | None = None + created_at: datetime | None = None + updated_at: datetime | None = None __all__ = [ diff --git a/src/utils/booru_api/models/gelbooru.py b/src/utils/booru_api/models/gelbooru.py index d7851bba..6647ffbd 100644 --- a/src/utils/booru_api/models/gelbooru.py +++ b/src/utils/booru_api/models/gelbooru.py @@ -9,7 +9,6 @@ """ from enum import StrEnum, unique -from typing import Optional from pydantic import BaseModel, ConfigDict, Field @@ -57,10 +56,10 @@ class Post(BaseGelbooruModel): preview_height: int sample_height: int sample_width: int - file_url: Optional[str] = None - jpeg_url: Optional[str] = None - preview_url: Optional[str] = None - sample_url: Optional[str] = None + file_url: str | None = None + jpeg_url: str | None = None + preview_url: str | None = None + sample_url: str | None = None parent_id: int sample: int has_children: bool diff --git a/src/utils/booru_api/models/moebooru.py b/src/utils/booru_api/models/moebooru.py index e7a9f3f9..549eafa4 100644 --- a/src/utils/booru_api/models/moebooru.py +++ b/src/utils/booru_api/models/moebooru.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from typing import Any, Literal, Optional +from typing import Any, Literal from pydantic import BaseModel, ConfigDict @@ -22,8 +22,8 @@ class BaseMoebooruModel(BaseModel): class Post(BaseMoebooruModel): id: int author: str - creator_id: Optional[int] = None - approver_id: Optional[int] = None + creator_id: int | None = None + approver_id: int | None = None tags: str rating: Literal['s', 'q', 'e'] change: int @@ -34,25 +34,25 @@ class Post(BaseMoebooruModel): height: int preview_width: int preview_height: int - actual_preview_width: Optional[int] = None - actual_preview_height: Optional[int] = None + actual_preview_width: int | None = None + actual_preview_height: int | None = None sample_height: int sample_width: int - sample_file_size: Optional[int] = None - jpeg_width: Optional[int] = None - jpeg_height: Optional[int] = None - jpeg_file_size: Optional[int] = None - file_ext: Optional[str] = None + sample_file_size: int | None = None + jpeg_width: int | None = None + jpeg_height: int | None = None + jpeg_file_size: int | None = None + file_ext: str | None = None file_size: int - file_url: Optional[str] = None - jpeg_url: Optional[str] = None - preview_url: Optional[str] = None - sample_url: Optional[str] = None - frames_pending_string: Optional[str] = None - frames_pending: Optional[list[Any]] = None - frames_string: Optional[str] = None - frames: Optional[list[Any]] = None - parent_id: Optional[int] = None + file_url: str | None = None + jpeg_url: str | None = None + preview_url: str | None = None + sample_url: str | None = None + frames_pending_string: str | None = None + frames_pending: list[Any] | None = None + frames_string: str | None = None + frames: list[Any] | None = None + parent_id: int | None = None has_children: bool status: str is_rating_locked: bool = False @@ -60,15 +60,15 @@ class Post(BaseMoebooruModel): is_pending: bool = False is_held: bool = True is_note_locked: bool = False - last_noted_at: Optional[int] = None - last_commented_at: Optional[int] = None + last_noted_at: int | None = None + last_commented_at: int | None = None created_at: Any = None updated_at: Any = None class SimilarPosts(BaseMoebooruModel): posts: list[Post] - search_id: Optional[int] = None + search_id: int | None = None success: bool @@ -83,8 +83,8 @@ class Tag(BaseMoebooruModel): class Artist(BaseMoebooruModel): id: int name: str - alias_id: Optional[int] = None - group_id: Optional[int] = None + alias_id: int | None = None + group_id: int | None = None urls: list[str] @@ -92,7 +92,7 @@ class Comment(BaseMoebooruModel): id: int post_id: int creator: str - creator_id: Optional[int] = None # Anonymous creator + creator_id: int | None = None # Anonymous creator body: str created_at: Any = None @@ -115,7 +115,7 @@ class Note(BaseMoebooruModel): width: int height: int is_active: bool - creator_id: Optional[int] = None # Anonymous creator + creator_id: int | None = None # Anonymous creator post_id: int body: str version: int @@ -130,9 +130,9 @@ class User(BaseMoebooruModel): class Forum(BaseMoebooruModel): id: int - parent_id: Optional[int] = None + parent_id: int | None = None creator: str - creator_id: Optional[int] = None # Anonymous creator + creator_id: int | None = None # Anonymous creator title: str body: str pages: int @@ -145,8 +145,8 @@ class Pool(BaseMoebooruModel): user_id: int is_public: bool post_count: int - description: Optional[str] = None - posts: Optional[list[Post]] = None + description: str | None = None + posts: list[Post] | None = None created_at: Any = None updated_at: Any = None diff --git a/src/utils/booru_api/moebooru.py b/src/utils/booru_api/moebooru.py index 14b9f423..aab89e5d 100644 --- a/src/utils/booru_api/moebooru.py +++ b/src/utils/booru_api/moebooru.py @@ -9,21 +9,21 @@ """ import abc -from typing import TYPE_CHECKING, Any, Literal, Optional +from typing import TYPE_CHECKING, Any, Literal from src.compat import parse_obj_as -from src.utils.common_api import BaseCommonAPI +from src.utils import BaseCommonAPI from .models.moebooru import ( - Post, - SimilarPosts, - Tag, Artist, Comment, - Wiki, - Note, - User, Forum, + Note, Pool, + Post, + SimilarPosts, + Tag, + User, + Wiki, ) if TYPE_CHECKING: @@ -44,8 +44,8 @@ class BaseMoebooruAPI(BaseCommonAPI, abc.ABC): def __init__( self, *, - login_name: Optional[str] = None, - password_hash: Optional[str] = None, + login_name: str | None = None, + password_hash: str | None = None, legacy_endpoint: bool = False, ) -> None: """初始化鉴权信息 @@ -77,17 +77,17 @@ def _load_cloudflare_clearance(cls) -> bool: return False @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None async def get_json( self, url: str, - params: Optional[dict[str, Any]] = None, + params: dict[str, Any] | None = None, ) -> Any: """使用 GET 方法请求 API, 返回 json 内容""" if isinstance(params, dict): @@ -100,7 +100,7 @@ async def get_json( async def get_resource_as_bytes( self, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 30, ) -> bytes: @@ -109,7 +109,7 @@ async def get_resource_as_bytes( async def get_resource_as_text( self, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 10, ) -> str: @@ -120,9 +120,9 @@ async def get_resource_as_text( async def posts_index( self, *, - limit: Optional[int] = None, - page: Optional[int] = None, - tags: Optional[str] = None, + limit: int | None = None, + page: int | None = None, + tags: str | None = None, ) -> list[Post]: if self.__legacy_endpoint: index_url = f'{self._get_root_url()}/post/index.json' @@ -176,13 +176,13 @@ async def post_show_similar(self, id_: int) -> SimilarPosts: async def tags_index( self, *, - limit: Optional[int] = None, - page: Optional[int] = None, - order: Optional[Literal['date', 'count', 'name']] = None, - id_: Optional[int] = None, - after_id: Optional[int] = None, - name: Optional[str] = None, - name_pattern: Optional[str] = None, + limit: int | None = None, + page: int | None = None, + order: Literal['date', 'count', 'name'] | None = None, + id_: int | None = None, + after_id: int | None = None, + name: str | None = None, + name_pattern: str | None = None, ) -> list[Tag]: if self.__legacy_endpoint: index_url = f'{self._get_root_url()}/tag/index.json' @@ -213,9 +213,9 @@ async def tags_index( async def artists_index( self, *, - name: Optional[str] = None, - order: Optional[Literal['date', 'name']] = None, - page: Optional[int] = None, + name: str | None = None, + order: Literal['date', 'name'] | None = None, + page: int | None = None, ) -> list[Artist]: if self.__legacy_endpoint: index_url = f'{self._get_root_url()}/artist/index.json' @@ -246,10 +246,10 @@ async def comment_show(self, id_: int) -> Comment: async def wikis_index( self, *, - limit: Optional[int] = None, - page: Optional[int] = None, - order: Optional[Literal['title', 'date']] = None, - query: Optional[str] = None, + limit: int | None = None, + page: int | None = None, + order: Literal['title', 'date'] | None = None, + query: str | None = None, ) -> list[Wiki]: if self.__legacy_endpoint: index_url = f'{self._get_root_url()}/wiki/index.json' @@ -294,8 +294,8 @@ async def note_post_show(self, post_id: int) -> list[Note]: async def users_index( self, *, - id_: Optional[int] = None, - name: Optional[str] = None, + id_: int | None = None, + name: str | None = None, ) -> list[User]: if self.__legacy_endpoint: index_url = f'{self._get_root_url()}/user/index.json' @@ -316,7 +316,7 @@ async def users_index( async def forums_index( self, *, - parent_id: Optional[int] = None, + parent_id: int | None = None, ) -> list[Forum]: if self.__legacy_endpoint: index_url = f'{self._get_root_url()}/forum/index.json' @@ -332,8 +332,8 @@ async def forums_index( async def pools_index( self, *, - query: Optional[str] = None, - page: Optional[int] = None, + query: str | None = None, + page: int | None = None, ) -> list[Pool]: if self.__legacy_endpoint: index_url = f'{self._get_root_url()}/pool/index.json' @@ -349,7 +349,7 @@ async def pools_index( return parse_obj_as(list[Pool], await self.get_json(url=index_url, params=params)) - async def pool_posts_show(self, pool_id: int, *, page: Optional[int] = None) -> Pool: + async def pool_posts_show(self, pool_id: int, *, page: int | None = None) -> Pool: url = f'{self._get_root_url()}/pool/show.json' # alternative_url = f'{self._get_root_url()}/pool/show/{pool_id}.json' @@ -364,7 +364,7 @@ class BehoimiAPI(BaseMoebooruAPI): """http://behoimi.org 主站 API""" @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return { 'origin': f'{cls._get_root_url()}/', 'referer': f'{cls._get_root_url()}/', @@ -384,7 +384,7 @@ def _load_cloudflare_clearance(cls) -> bool: return True @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @classmethod @@ -396,7 +396,7 @@ class KonachanSafeAPI(BaseMoebooruAPI): """https://konachan.net 全年龄站 API, 与主站 API 数据相同, 只是网站页面不显示 rating:E 的作品""" @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @classmethod diff --git a/src/utils/comic18/__init__.py b/src/utils/comic18/__init__.py index 73349e12..7ca1cfe6 100644 --- a/src/utils/comic18/__init__.py +++ b/src/utils/comic18/__init__.py @@ -10,7 +10,6 @@ from .main import Comic18 - __all__ = [ 'Comic18' ] diff --git a/src/utils/comic18/helper.py b/src/utils/comic18/helper.py index 87c53634..d864cd4b 100644 --- a/src/utils/comic18/helper.py +++ b/src/utils/comic18/helper.py @@ -28,7 +28,7 @@ from lxml.etree import _Element -class Comic18Parser(object): +class Comic18Parser: """Comic18 解析工具""" def __init__(self, root_url: str): self.root_url = root_url @@ -40,7 +40,7 @@ def parse_root_url(content: str) -> str: script_text = html.xpath('/html/body/script').pop(0).text return script_text[script_text.index('"')+1:script_text.rindex('"')] - def _parse_query_albums_container(self, container: "_Element") -> AlbumsResult: + def _parse_query_albums_container(self, container: '_Element') -> AlbumsResult: relative_url = container.xpath('div[contains(@class, "thumb-overlay")]/a').pop(0).attrib.get('href') url = f'{self.root_url}{relative_url}' aid = relative_url.split('/')[2] @@ -89,7 +89,7 @@ def parse_query_albums_result_page(self, content: str) -> list[AlbumsResult]: return results - def _parse_search_photos_container(self, container: "_Element") -> AlbumsResult: + def _parse_search_photos_container(self, container: '_Element') -> AlbumsResult: relative_url = container.xpath('a').pop(0).attrib.get('href') url = f'{self.root_url}{relative_url}' aid = relative_url.split('/')[2] @@ -244,16 +244,16 @@ def get_split_num(album_id: int, page_id: str) -> int: elif album_id < 268850: split_num = 10 elif album_id < 421926: - split_seed = md5(f'{album_id}{page_index_str}'.encode('utf-8')).hexdigest() + split_seed = md5(f'{album_id}{page_index_str}'.encode()).hexdigest() split_num = (ord(split_seed[-1]) % 10) * 2 + 2 else: - split_seed = md5(f'{album_id}{page_index_str}'.encode('utf-8')).hexdigest() + split_seed = md5(f'{album_id}{page_index_str}'.encode()).hexdigest() split_num = (ord(split_seed[-1]) % 8) * 2 + 2 return split_num @run_sync - def reverse_segmental_image(self, album_id: int, page_id: str) -> "Comic18ImgOps": + def reverse_segmental_image(self, album_id: int, page_id: str) -> 'Comic18ImgOps': """对被分割图片进行重新排序""" split_num = self.get_split_num(album_id=album_id, page_id=page_id) if split_num <= 1: diff --git a/src/utils/comic18/main.py b/src/utils/comic18/main.py index bcf853f7..14a91cb2 100644 --- a/src/utils/comic18/main.py +++ b/src/utils/comic18/main.py @@ -11,23 +11,23 @@ import random import string from asyncio import sleep as async_sleep -from typing import TYPE_CHECKING, Literal, Optional, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Literal from nonebot.log import logger from src.exception import WebSourceException -from src.utils.common_api import BaseCommonAPI +from src.utils import BaseCommonAPI, semaphore_gather from src.utils.image_utils.template import generate_thumbs_preview_image -from src.utils.process_utils import semaphore_gather from src.utils.zip_utils import ZipUtils from .config import comic18_config, comic18_resource_config -from .helper import Comic18Parser, Comic18ImgOps +from .helper import Comic18ImgOps, Comic18Parser from .model import ( AlbumData, + AlbumPackResult, AlbumPage, AlbumPageContent, AlbumsResult, - AlbumPackResult, Comic18PreviewBody, Comic18PreviewModel, Comic18PreviewRequestModel, @@ -35,12 +35,13 @@ if TYPE_CHECKING: from nonebot.internal.driver import CookieTypes, QueryTypes + from src.resource import TemporaryResource class _BaseComic18(BaseCommonAPI): """18Comic 基类""" - __root_url: Optional[str] = None + __root_url: str | None = None @classmethod def _get_root_url(cls, *args, **kwargs) -> str: @@ -62,7 +63,9 @@ async def _async_get_root_url( go_url = f'https://raw.githubusercontent.com/jmcmomic/jmcmomic.github.io/main/go/{type_}.html' go_response = await cls._request_get(go_url) if go_response.status_code != 200: - raise WebSourceException(f'{go_response.request}, status code {go_response.status_code}') + raise WebSourceException( + go_response.status_code, f'{go_response.request}, status code {go_response.status_code}' + ) cls.__root_url = Comic18Parser.parse_root_url(content=cls._parse_content_as_text(go_response)) return cls.__root_url @@ -76,14 +79,14 @@ def _get_default_headers(cls) -> dict[str, str]: return {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return comic18_config.cookies @classmethod async def request_resource_as_bytes( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 30 ) -> bytes: @@ -94,7 +97,10 @@ async def request_resource_as_bytes( try: response = await cls._request_get(url, params, headers=headers, cookies=cookies, timeout=timeout) - except WebSourceException: + except WebSourceException as e: + if e.status_code != 403: + raise e + # 请求过快可能导致 403 被暂时流控了, 暂停一下重试一次 await async_sleep(3) response = await cls._request_get(url, params, headers=headers, cookies=cookies, timeout=timeout) @@ -105,7 +111,7 @@ async def request_resource_as_bytes( async def request_resource_as_text( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, timeout: int = 10 ) -> str: @@ -116,7 +122,10 @@ async def request_resource_as_text( try: response = await cls._request_get(url, params, headers=headers, cookies=cookies, timeout=timeout) - except WebSourceException: + except WebSourceException as e: + if e.status_code != 403: + raise e + # 请求过快可能导致 403 被暂时流控了, 暂停一下重试一次 await async_sleep(3) response = await cls._request_get(url, params, headers=headers, cookies=cookies, timeout=timeout) @@ -130,20 +139,24 @@ async def download_resource( *, folder_name: str | None = None, ignore_exist_file: bool = False, - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" try: file = await cls._download_resource( save_folder=comic18_resource_config.default_download_folder, url=url, subdir=folder_name, ignore_exist_file=ignore_exist_file ) - except WebSourceException: + except WebSourceException as e: + if e.status_code != 403: + raise e + # 请求过快可能导致 403 被暂时流控了, 暂停一下重试一次 await async_sleep(3) file = await cls._download_resource( save_folder=comic18_resource_config.default_download_folder, url=url, subdir=folder_name, ignore_exist_file=ignore_exist_file ) + return file @@ -162,10 +175,10 @@ def __repr__(self) -> str: @classmethod async def query_albums_list( cls, - page: Optional[int] = None, - type_: Optional[Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single']] = None, - time: Optional[Literal['a', 't', 'w', 'm']] = None, - order: Optional[Literal['mr', 'mv', 'mp', 'md', 'tr', 'tf']] = None, + page: int | None = None, + type_: Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single'] | None = None, + time: Literal['a', 't', 'w', 'm'] | None = None, + order: Literal['mr', 'mv', 'mp', 'md', 'tr', 'tf'] | None = None, ) -> list[AlbumsResult]: """获取分类漫画 @@ -194,11 +207,11 @@ async def query_albums_list( @classmethod async def query_albums_list_with_preview( cls, - page: Optional[int] = None, - type_: Optional[Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single']] = None, - time: Optional[Literal['a', 't', 'w', 'm']] = None, - order: Optional[Literal['mr', 'mv', 'mp', 'md', 'tr', 'tf']] = None, - ) -> "TemporaryResource": + page: int | None = None, + type_: Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single'] | None = None, + time: Literal['a', 't', 'w', 'm'] | None = None, + order: Literal['mr', 'mv', 'mp', 'md', 'tr', 'tf'] | None = None, + ) -> 'TemporaryResource': """获取分类漫画并生成预览图""" result = await cls.query_albums_list(page=page, type_=type_, time=time, order=order) name = f'AlbumsList - {type_} - Page {page}' @@ -209,7 +222,7 @@ async def query_albums_list_with_preview( async def query_promotes( cls, type_: int = 27, - page: Optional[int] = None, + page: int | None = None, ) -> list[AlbumsResult]: """获取漫画推荐专题 @@ -230,8 +243,8 @@ async def query_promotes( async def query_promotes_with_preview( cls, type_: int = 27, - page: Optional[int] = None, - ) -> "TemporaryResource": + page: int | None = None, + ) -> 'TemporaryResource': """获取漫画推荐专题并生成预览图""" result = await cls.query_promotes(type_=type_, page=page) name = f'PromotesList - {type_} - Page {page}' @@ -243,11 +256,11 @@ async def search_photos( cls, search_query: str, *, - page: Optional[int] = None, - type_: Optional[Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single']] = None, - time: Optional[Literal['a', 't', 'w', 'm']] = None, - order: Optional[Literal['mr', 'mv', 'mp', 'tf']] = None, - main_tag: Optional[Literal['0', '1', '2', '3', '4']] = None, + page: int | None = None, + type_: Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single'] | None = None, + time: Literal['a', 't', 'w', 'm'] | None = None, + order: Literal['mr', 'mv', 'mp', 'tf'] | None = None, + main_tag: Literal['0', '1', '2', '3', '4'] | None = None, ) -> list[AlbumsResult]: """搜索漫画 @@ -282,12 +295,12 @@ async def search_photos_with_preview( cls, search_query: str, *, - page: Optional[int] = None, - type_: Optional[Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single']] = None, - time: Optional[Literal['a', 't', 'w', 'm']] = None, - order: Optional[Literal['mr', 'mv', 'mp', 'tf']] = None, - main_tag: Optional[Literal['0', '1', '2', '3', '4']] = None, - ) -> "TemporaryResource": + page: int | None = None, + type_: Literal['another', 'doujin', 'hanman', 'meiman', 'short', 'single'] | None = None, + time: Literal['a', 't', 'w', 'm'] | None = None, + order: Literal['mr', 'mv', 'mp', 'tf'] | None = None, + main_tag: Literal['0', '1', '2', '3', '4'] | None = None, + ) -> 'TemporaryResource': """搜索漫画并生成预览图""" result = await cls.search_photos( search_query, page=page, type_=type_, time=time, order=order, main_tag=main_tag @@ -324,11 +337,11 @@ async def query_album(self) -> AlbumData: raise TypeError('Query album data failed') return self.album_data - async def query_album_with_preview(self) -> "TemporaryResource": + async def query_album_with_preview(self) -> 'TemporaryResource': """获取漫画并生成漫画内容预览图""" return await self._generate_album_preview_image() - async def query_pages(self, page: Optional[int] = None) -> AlbumPage: + async def query_pages(self, page: int | None = None) -> AlbumPage: """获取漫画图片""" root_url = await self._async_get_root_url() url = f'{root_url}/photo/{self.aid}' @@ -357,15 +370,15 @@ async def query_all_pages(self) -> list[AlbumPageContent]: async def _reverse_image( self, page_id: str, - file: "TemporaryResource", - save_folder: "TemporaryResource" - ) -> "TemporaryResource": + file: 'TemporaryResource', + save_folder: 'TemporaryResource' + ) -> 'TemporaryResource': """恢复被分割的图片""" image: Comic18ImgOps = await Comic18ImgOps.async_init_from_file(file=file) output_image = await image.reverse_segmental_image(album_id=self.aid, page_id=page_id) return await output_image.save(save_folder(f'{page_id}.jpg')) - async def download_album(self, *, ignore_exist_file: bool = True) -> list["TemporaryResource"]: + async def download_album(self, *, ignore_exist_file: bool = True) -> list['TemporaryResource']: """下载漫画""" album_pages = await self.query_all_pages() @@ -459,7 +472,7 @@ async def _generate_preview_image( hold_ratio: bool = False, num_of_line: int = 4, limit: int = 1000 - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """生成多个图片内容的预览图 :param preview_size: 单个小缩略图的尺寸 @@ -515,7 +528,7 @@ async def _emit_preview_model_from_album_data(self) -> Comic18PreviewModel: return Comic18PreviewModel(preview_name=preview_name, count=count, previews=previews) - async def _generate_album_preview_image(self) -> "TemporaryResource": + async def _generate_album_preview_image(self) -> 'TemporaryResource': """生成作品预览图""" preview_data = await self._emit_preview_model_from_album_data() return await self._generate_preview_image(preview=preview_data, hold_ratio=True) diff --git a/src/utils/comic18/model.py b/src/utils/comic18/model.py index 0879fe17..5797f967 100644 --- a/src/utils/comic18/model.py +++ b/src/utils/comic18/model.py @@ -9,13 +9,12 @@ """ from dataclasses import dataclass -from typing import Optional from pydantic import BaseModel, ConfigDict from src.compat import AnyHttpUrlStr as AnyHttpUrl from src.resource import TemporaryResource -from src.utils.image_utils.template import PreviewImageThumbs, PreviewImageModel +from src.utils.image_utils.template import PreviewImageModel, PreviewImageThumbs class BaseComic18Model(BaseModel): @@ -57,7 +56,7 @@ class AlbumPageContent(BaseComic18Model): page_index: int page_type: str url: AnyHttpUrl - description: Optional[str] = None + description: str | None = None class AlbumPage(BaseComic18Model): diff --git a/src/utils/encrypt/__init__.py b/src/utils/crypto/__init__.py similarity index 65% rename from src/utils/encrypt/__init__.py rename to src/utils/crypto/__init__.py index 9b8b437f..e5cd9a4c 100644 --- a/src/utils/encrypt/__init__.py +++ b/src/utils/crypto/__init__.py @@ -1,16 +1,16 @@ """ @Author : Ailitonia @Date : 2024/8/31 下午2:32 -@FileName : encrypt +@FileName : crypto @Project : omega-miya @Description : 加密解密工具集 @GitHub : https://github.com/Ailitonia @Software : PyCharm """ -from .encrypter import AESEncrypter - +from .encryptor import AESEncryptor, ChaCha20Encryptor __all__ = [ - 'AESEncrypter', + 'AESEncryptor', + 'ChaCha20Encryptor', ] diff --git a/src/utils/encrypt/config.py b/src/utils/crypto/config.py similarity index 96% rename from src/utils/encrypt/config.py rename to src/utils/crypto/config.py index 694d8a21..070917ac 100644 --- a/src/utils/encrypt/config.py +++ b/src/utils/crypto/config.py @@ -23,7 +23,7 @@ def generate_aes_key_by_hardware() -> SecretStr: system = platform.system() node = str(uuid.getnode()) - return SecretStr(sha256(f'{system}+{machine}+{processor}+{node}'.encode(encoding='utf8')).hexdigest()) + return SecretStr(sha256(f'{system}+{machine}+{processor}+{node}'.encode()).hexdigest()) class EncryptConfig(BaseModel): diff --git a/src/utils/encrypt/encrypter/__init__.py b/src/utils/crypto/encryptor/__init__.py similarity index 61% rename from src/utils/encrypt/encrypter/__init__.py rename to src/utils/crypto/encryptor/__init__.py index 5426d9ab..22e1904f 100644 --- a/src/utils/encrypt/encrypter/__init__.py +++ b/src/utils/crypto/encryptor/__init__.py @@ -1,15 +1,17 @@ """ @Author : Ailitonia @Date : 2024/8/31 下午2:31 -@FileName : encrypter +@FileName : encryptor @Project : omega-miya @Description : 加密套件 @GitHub : https://github.com/Ailitonia @Software : PyCharm """ -from .aes import AESEncrypter +from .aes import AESEncryptor +from .chacha20 import ChaCha20Encryptor __all__ = [ - 'AESEncrypter', + 'AESEncryptor', + 'ChaCha20Encryptor', ] diff --git a/src/utils/encrypt/encrypter/aes.py b/src/utils/crypto/encryptor/aes.py similarity index 96% rename from src/utils/encrypt/encrypter/aes.py rename to src/utils/crypto/encryptor/aes.py index b986c612..edcf1e15 100644 --- a/src/utils/encrypt/encrypter/aes.py +++ b/src/utils/crypto/encryptor/aes.py @@ -11,7 +11,7 @@ import base64 from hashlib import shake_128 from os import urandom -from typing import Literal, Optional +from typing import Literal from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad @@ -19,12 +19,14 @@ from ..config import encrypt_config -class AESEncrypter(object): +class AESEncryptor: + """AES 加解密工具集""" + def __init__( self, - key: Optional[str] = None, + key: str | None = None, *, - version: Optional[Literal['AES-128', 'AES-192', 'AES-256']] = None, + version: Literal['AES-128', 'AES-192', 'AES-256'] | None = None, ) -> None: if key is None: key = encrypt_config.omega_aes_key.get_secret_value() @@ -194,5 +196,5 @@ def eax_decrypt(self, ciphertext: str, nonce_text: str, tag_text: str) -> str: __all__ = [ - 'AESEncrypter', + 'AESEncryptor', ] diff --git a/src/utils/crypto/encryptor/chacha20.py b/src/utils/crypto/encryptor/chacha20.py new file mode 100644 index 00000000..df17cc97 --- /dev/null +++ b/src/utils/crypto/encryptor/chacha20.py @@ -0,0 +1,87 @@ +""" +@Author : Ailitonia +@Date : 2024/9/7 23:51 +@FileName : chacha20 +@Project : omega-miya +@Description : ChaCha20 加密套件 +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +import base64 +from hashlib import shake_128 +from os import urandom + +from Crypto.Cipher import ChaCha20, ChaCha20_Poly1305 + +from ..config import encrypt_config + + +class ChaCha20Encryptor: + """ChaCha20 加解密工具集""" + + def __init__(self, key: str | None = None) -> None: + if key is None: + key = encrypt_config.omega_aes_key.get_secret_value() + + self.__key = shake_128(key.encode(encoding='utf-8')).hexdigest(ChaCha20.key_size // 2).encode(encoding='utf-8') + + @staticmethod + def _b64_encode(content: bytes) -> str: + return base64.b64encode(content).decode(encoding='utf-8') + + @staticmethod + def _b64_decode(content: str) -> bytes: + return base64.b64decode(content.encode('utf-8')) + + def chacha20_encrypt(self, plaintext: str) -> tuple[str, str]: + """标准 ChaCha20 加密 + + :return: ciphertext, nonce + """ + plaintext_bytes = plaintext.encode('utf-8') + nonce = urandom(12) # Nonce must be 8/12 bytes(ChaCha20) or 24 bytes (XChaCha20) + + cipher = ChaCha20.new(key=self.__key, nonce=nonce) + ciphertext_bytes = cipher.encrypt(plaintext_bytes) + + return self._b64_encode(ciphertext_bytes), self._b64_encode(nonce) + + def chacha20_decrypt(self, ciphertext: str, nonce_text: str) -> str: + """标准 ChaCha20 解密""" + ciphertext_bytes = self._b64_decode(ciphertext) + nonce = self._b64_decode(nonce_text) + + cipher = ChaCha20.new(key=self.__key, nonce=nonce) + plaintext_bytes = cipher.decrypt(ciphertext_bytes) + + return plaintext_bytes.decode('utf-8') + + def chacha20_poly1305_encrypt(self, plaintext: str) -> tuple[str, str, str]: + """ChaCha20-Poly1305 加密 + + :return: ciphertext, nonce, tag + """ + plaintext_bytes = plaintext.encode('utf-8') + nonce = urandom(12) # Nonce must be 8, 12 or 24 bytes long + + cipher = ChaCha20_Poly1305.new(key=self.__key, nonce=nonce) + ciphertext_bytes, tag = cipher.encrypt_and_digest(plaintext_bytes) + + return self._b64_encode(ciphertext_bytes), self._b64_encode(nonce), self._b64_encode(tag) + + def chacha20_poly1305_decrypt(self, ciphertext: str, nonce_text: str, tag_text: str) -> str: + """ChaCha20-Poly1305 解密""" + ciphertext_bytes = self._b64_decode(ciphertext) + nonce = self._b64_decode(nonce_text) + tag = self._b64_decode(tag_text) + + cipher = ChaCha20_Poly1305.new(key=self.__key, nonce=nonce) + plaintext_bytes = cipher.decrypt_and_verify(ciphertext_bytes, tag) + + return plaintext_bytes.decode('utf-8') + + +__all__ = [ + 'ChaCha20Encryptor', +] diff --git a/src/utils/image_searcher/__init__.py b/src/utils/image_searcher/__init__.py index 1817b7a0..b51658a7 100644 --- a/src/utils/image_searcher/__init__.py +++ b/src/utils/image_searcher/__init__.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING -from src.utils.process_utils import semaphore_gather +from src.utils import semaphore_gather from .config import image_searcher_config from .model import BaseImageSearcher from .seachers import ( @@ -28,7 +28,7 @@ class ComplexImageSearcher(BaseImageSearcher): """综合图片搜索""" - _searcher: list[type["BaseImageSearcherAPI"]] = [] + _searcher: list[type['BaseImageSearcherAPI']] = [] if image_searcher_config.image_searcher_enable_saucenao: _searcher.append(Saucenao) @@ -42,7 +42,7 @@ class ComplexImageSearcher(BaseImageSearcher): if image_searcher_config.image_searcher_enable_yandex: _searcher.append(Yandex) - async def search(self) -> list["ImageSearchingResult"]: + async def search(self) -> list['ImageSearchingResult']: searching_tasks = [ searcher(image_url=self.image_url).search() for searcher in self._searcher diff --git a/src/utils/image_searcher/model.py b/src/utils/image_searcher/model.py index 9485116c..cc2fc9fc 100644 --- a/src/utils/image_searcher/model.py +++ b/src/utils/image_searcher/model.py @@ -9,12 +9,12 @@ """ import abc -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from pydantic import BaseModel, ConfigDict from src.compat import AnyUrlStr as AnyUrl -from src.utils.common_api import BaseCommonAPI +from src.utils import BaseCommonAPI if TYPE_CHECKING: from nonebot.internal.driver import QueryTypes @@ -23,9 +23,9 @@ class ImageSearchingResult(BaseModel): """识图结果""" source: str # 来源说明 - source_urls: Optional[list[AnyUrl]] = None # 来源地址 - similarity: Optional[str] = None # 相似度 - thumbnail: Optional[AnyUrl] = None # 缩略图地址 + source_urls: list[AnyUrl] | None = None # 来源地址 + similarity: str | None = None # 相似度 + thumbnail: AnyUrl | None = None # 缩略图地址 model_config = ConfigDict(extra='ignore', frozen=True, coerce_numbers_to_str=True) @@ -57,12 +57,12 @@ def _load_cloudflare_clearance(cls) -> bool: return False @classmethod - async def get_resource_as_bytes(cls, url: str, *, params: "QueryTypes" = None, timeout: int = 30) -> bytes: + async def get_resource_as_bytes(cls, url: str, *, params: 'QueryTypes' = None, timeout: int = 30) -> bytes: """请求原始资源内容""" return await cls._get_resource_as_bytes(url, params, timeout=timeout) @classmethod - async def get_resource_as_text(cls, url: str, *, params: "QueryTypes" = None, timeout: int = 10) -> str: + async def get_resource_as_text(cls, url: str, *, params: 'QueryTypes' = None, timeout: int = 10) -> str: """请求原始资源内容""" return await cls._get_resource_as_text(url, params, timeout=timeout) diff --git a/src/utils/image_searcher/seachers/ascii2d.py b/src/utils/image_searcher/seachers/ascii2d.py index 8eeb5c27..725d35c1 100644 --- a/src/utils/image_searcher/seachers/ascii2d.py +++ b/src/utils/image_searcher/seachers/ascii2d.py @@ -36,11 +36,11 @@ def _load_cloudflare_clearance(cls) -> bool: return True @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None @staticmethod diff --git a/src/utils/image_searcher/seachers/iqdb.py b/src/utils/image_searcher/seachers/iqdb.py index c5adc3b4..e9219802 100644 --- a/src/utils/image_searcher/seachers/iqdb.py +++ b/src/utils/image_searcher/seachers/iqdb.py @@ -32,7 +32,7 @@ async def _async_get_root_url(cls, *args, **kwargs) -> str: return cls._get_root_url(*args, **kwargs) @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': headers = cls._get_omega_requests_default_headers() headers.update({ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,' @@ -49,7 +49,7 @@ def _get_default_headers(cls) -> "HeaderTypes": return headers @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None @staticmethod @@ -69,7 +69,7 @@ def _parser(content: str) -> list[dict]: thumbnail = row.xpath('tr/td[@class="image"]/a/img').pop(0).attrib.get('src') urls = [ f'https:{url}' if url.startswith('//') else url - for url in (x.attrib.get("href") for x in row.xpath('tr/td//a')) + for url in (x.attrib.get('href') for x in row.xpath('tr/td//a')) ] similarity = row.xpath('tr[last()]/td').pop(0).text result.append({ diff --git a/src/utils/image_searcher/seachers/saucenao.py b/src/utils/image_searcher/seachers/saucenao.py index a40fb7bf..089c3e26 100644 --- a/src/utils/image_searcher/seachers/saucenao.py +++ b/src/utils/image_searcher/seachers/saucenao.py @@ -8,12 +8,13 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from nonebot.log import logger from pydantic import BaseModel, ConfigDict -from src.compat import AnyUrlStr as AnyUrl, parse_obj_as +from src.compat import AnyUrlStr as AnyUrl +from src.compat import parse_obj_as from ..config import image_searcher_config from ..model import BaseImageSearcherAPI, ImageSearchingResult @@ -39,18 +40,18 @@ class _GlobalStatusHeader(BaseSaucenaoModel): long_remaining: int status: int results_requested: int - message: Optional[str] = None + message: str | None = None class _Result(BaseSaucenaoModel): class _Header(BaseSaucenaoModel): similarity: float - thumbnail: Optional[AnyUrl] = None + thumbnail: AnyUrl | None = None index_id: int index_name: str class _BaseData(BaseSaucenaoModel): - ext_urls: Optional[list[AnyUrl]] = None + ext_urls: list[AnyUrl] | None = None @property def data_text(self) -> str: @@ -64,8 +65,8 @@ def data_text(self) -> str: return '' class _DefaultData(_BaseData): - author_name: Optional[str] = None - author_url: Optional[str] = None + author_name: str | None = None + author_url: str | None = None creator: str creator_name: str @@ -161,7 +162,7 @@ def data_text(self) -> str: _NullData) header: _GlobalStatusHeader - results: Optional[list[_Result]] = None + results: list[_Result] | None = None class Saucenao(BaseImageSearcherAPI): @@ -179,11 +180,11 @@ async def _async_get_root_url(cls, *args, **kwargs) -> str: return cls._get_root_url(*args, **kwargs) @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': return {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0'} @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None @property diff --git a/src/utils/image_searcher/seachers/trace_moe.py b/src/utils/image_searcher/seachers/trace_moe.py index 0ae1c792..e5d0abc1 100644 --- a/src/utils/image_searcher/seachers/trace_moe.py +++ b/src/utils/image_searcher/seachers/trace_moe.py @@ -8,12 +8,13 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from pydantic import BaseModel, Field -from src.compat import AnyUrlStr as AnyUrl, parse_obj_as -from src.utils.process_utils import semaphore_gather +from src.compat import AnyUrlStr as AnyUrl +from src.compat import parse_obj_as +from src.utils import semaphore_gather from ..model import BaseImageSearcherAPI, ImageSearchingResult if TYPE_CHECKING: @@ -47,9 +48,9 @@ class _Media(BaseModel): class _Title(BaseModel): native: str - romaji: Optional[str] = None - english: Optional[str] = None - chinese: Optional[str] = None + romaji: str | None = None + english: str | None = None + chinese: str | None = None id: int title: _Title @@ -80,7 +81,7 @@ async def _async_get_root_url(cls, *args, **kwargs) -> str: return cls._get_root_url(*args, **kwargs) @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': headers = cls._get_omega_requests_default_headers() headers.update({ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;' @@ -89,7 +90,7 @@ def _get_default_headers(cls) -> "HeaderTypes": return headers @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None @property @@ -111,7 +112,7 @@ def anilist_api_cn(self) -> str: def anilist_api_query(self) -> str: """Anilist API 请求内容""" - return r''' + return r""" query ($id: Int) { # Define which variables will be used in the query (id) Media (id: $id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query) id # you must query the id field for it to search the translated database @@ -124,7 +125,7 @@ def anilist_api_query(self) -> str: synonyms # chinese titles will always be merged into this array } } - ''' + """ async def _handel_anilist_result(self, data: TraceMoeResults) -> ImageSearchingResult: """获取 anilist 数据""" diff --git a/src/utils/image_searcher/seachers/yandex.py b/src/utils/image_searcher/seachers/yandex.py index ec45aa5b..096c377e 100644 --- a/src/utils/image_searcher/seachers/yandex.py +++ b/src/utils/image_searcher/seachers/yandex.py @@ -35,7 +35,7 @@ async def _async_get_root_url(cls, *args, **kwargs) -> str: return cls._get_root_url(*args, **kwargs) @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': headers = cls._get_omega_requests_default_headers() headers.update({ 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,' @@ -47,7 +47,7 @@ def _get_default_headers(cls) -> "HeaderTypes": return headers @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return None @property diff --git a/src/utils/image_utils/__init__.py b/src/utils/image_utils/__init__.py index f1d89ea0..f3b41c15 100644 --- a/src/utils/image_utils/__init__.py +++ b/src/utils/image_utils/__init__.py @@ -10,7 +10,6 @@ from .image_util import ImageUtils - __all__ = [ 'ImageUtils' ] diff --git a/src/utils/image_utils/config.py b/src/utils/image_utils/config.py index e0d3e69a..e332430a 100644 --- a/src/utils/image_utils/config.py +++ b/src/utils/image_utils/config.py @@ -9,6 +9,7 @@ """ from dataclasses import dataclass + from src.resource import StaticResource, TemporaryResource diff --git a/src/utils/image_utils/image_util.py b/src/utils/image_utils/image_util.py index b73dd4f1..5f096ad3 100644 --- a/src/utils/image_utils/image_util.py +++ b/src/utils/image_utils/image_util.py @@ -12,17 +12,17 @@ import random from copy import deepcopy from io import BytesIO -from typing import Literal, Optional, Self +from typing import Literal, Self -from PIL import Image, ImageFilter, ImageEnhance, ImageDraw, ImageFont +from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont from nonebot.utils import run_sync from src.resource import BaseResource, TemporaryResource -from src.service import OmegaRequests +from src.utils import OmegaRequests from .config import image_utils_config -class ImageUtils(object): +class ImageUtils: def __init__(self, image: Image.Image): self._image: Image.Image = image @@ -104,9 +104,9 @@ def init_from_text( # 初始化背景图层 image_height = text_height + int(image_width * 0.25) if alpha: - background = Image.new(mode="RGBA", size=(image_width, image_height), color=(255, 255, 255, 0)) + background = Image.new(mode='RGBA', size=(image_width, image_height), color=(255, 255, 255, 0)) else: - background = Image.new(mode="RGB", size=(image_width, image_height), color=(255, 255, 255)) + background = Image.new(mode='RGB', size=(image_width, image_height), color=(255, 255, 255)) # 绘制文字 ImageDraw.Draw(background).multiline_text( xy=(int(image_width * 0.115), int(image_width * 0.115)), @@ -122,7 +122,7 @@ def get_text_size( text: str, font: ImageFont.FreeTypeFont, *, - anchor: Optional[str] = None, + anchor: str | None = None, spacing: int = 4, stroke_width: int = 0, **kwargs @@ -358,7 +358,7 @@ def add_edge( image = image.resize((int(width * scale), int(height * scale)), Image.Resampling.LANCZOS) box = (int(width * (1 - scale) / 2)), int(height * (1 - scale) / 2) - background = Image.new(mode="RGBA", size=(width, height), color=edge_color) + background = Image.new(mode='RGBA', size=(width, height), color=edge_color) background.paste(image, box=box, mask=image) self._image = background @@ -381,7 +381,7 @@ def resize_with_filling( image = image.resize((int(width * scale), int(height * scale)), Image.Resampling.LANCZOS) box = (int(abs(width * scale - rs_width) / 2), int(abs(height * scale - rs_height) / 2)) - background = Image.new(mode="RGBA", size=size, color=background_color) + background = Image.new(mode='RGBA', size=size, color=background_color) background.paste(image, box=box, mask=image) self._image = background @@ -404,7 +404,7 @@ def resize_fill_canvas( image = image.resize((int(width * scale), int(height * scale)), Image.Resampling.LANCZOS) box = (- int(abs(width * scale - rs_width) / 2), - int(abs(height * scale - rs_height) / 2)) - background = Image.new(mode="RGBA", size=size, color=background_color) + background = Image.new(mode='RGBA', size=size, color=background_color) background.paste(image, box=box, mask=image) self._image = background diff --git a/src/utils/image_utils/template/__init__.py b/src/utils/image_utils/template/__init__.py index d7b9e2cf..bed22e9e 100644 --- a/src/utils/image_utils/template/__init__.py +++ b/src/utils/image_utils/template/__init__.py @@ -8,10 +8,9 @@ @Software : PyCharm """ -from .model import PreviewImageThumbs, PreviewImageModel +from .model import PreviewImageModel, PreviewImageThumbs from .template_preview import generate_thumbs_preview_image - __all__ = [ 'PreviewImageThumbs', 'PreviewImageModel', diff --git a/src/utils/image_utils/template/template_preview.py b/src/utils/image_utils/template/template_preview.py index 27fb0aa1..431ad345 100644 --- a/src/utils/image_utils/template/template_preview.py +++ b/src/utils/image_utils/template/template_preview.py @@ -12,8 +12,7 @@ from io import BytesIO from math import ceil -from PIL import Image, ImageDraw, ImageFont -from PIL import UnidentifiedImageError +from PIL import Image, ImageDraw, ImageFont, UnidentifiedImageError from nonebot.utils import run_sync from src.resource import BaseResource, TemporaryResource @@ -70,7 +69,7 @@ def _handle_preview_image() -> bytes: _spacing_title = _spacing_w if _title_h <= int(_spacing_w * 0.75) else int(_title_h * 1.5) _background = Image.new( - mode="RGB", + mode='RGB', size=(_preview_w, (_thumb_h + _spacing_w) * ceil(len(previews) / num_of_line) + _spacing_title), color=(255, 255, 255)) diff --git a/src/utils/nhentai/__init__.py b/src/utils/nhentai/__init__.py index 65e691b5..c1fdce50 100644 --- a/src/utils/nhentai/__init__.py +++ b/src/utils/nhentai/__init__.py @@ -11,7 +11,6 @@ from .main import NhentaiGallery - __all__ = [ 'NhentaiGallery' ] diff --git a/src/utils/nhentai/helper.py b/src/utils/nhentai/helper.py index 5dbcca95..56bbae39 100644 --- a/src/utils/nhentai/helper.py +++ b/src/utils/nhentai/helper.py @@ -15,10 +15,10 @@ from nonebot.utils import run_sync from .exception import NhentaiParseError -from .model import NhentaiSearchingResult, NhentaiGalleryModel +from .model import NhentaiGalleryModel, NhentaiSearchingResult -class NhentaiParser(object): +class NhentaiParser: """Nhentai 页面解析工具集""" @staticmethod diff --git a/src/utils/nhentai/main.py b/src/utils/nhentai/main.py index 89b7b47b..b03ea0d3 100644 --- a/src/utils/nhentai/main.py +++ b/src/utils/nhentai/main.py @@ -10,26 +10,27 @@ import random import string -from typing import TYPE_CHECKING, Literal, Sequence +from collections.abc import Sequence +from typing import TYPE_CHECKING, Literal from src.exception import WebSourceException -from src.utils.common_api import BaseCommonAPI +from src.utils import BaseCommonAPI, semaphore_gather from src.utils.image_utils.template import generate_thumbs_preview_image -from src.utils.process_utils import semaphore_gather from src.utils.zip_utils import ZipUtils from .config import nhentai_config, nhentai_resource_config from .helper import NhentaiParser from .model import ( NhentaiDownloadResult, NhentaiGalleryModel, - NhentaiPreviewRequestModel, NhentaiPreviewBody, NhentaiPreviewModel, + NhentaiPreviewRequestModel, NhentaiSearchingResult, ) if TYPE_CHECKING: from nonebot.internal.driver import CookieTypes, HeaderTypes + from src.resource import TemporaryResource @@ -53,13 +54,13 @@ def _load_cloudflare_clearance(cls) -> bool: return False @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': headers = cls._get_omega_requests_default_headers() headers.update({'referer': 'https://nhentai.net/'}) return headers @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return nhentai_config.nhentai_cookies @classmethod @@ -69,7 +70,7 @@ async def download_resource( *, folder_name: str | None = None, ignore_exist_file: bool = False - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" return await cls._download_resource( save_folder=nhentai_resource_config.default_download_folder, @@ -162,7 +163,7 @@ async def generate_nhentai_preview_image( hold_ratio: bool = False, num_of_line: int = 6, limit: int = 1000 - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """生成多个作品的预览图 :param preview: 经过预处理的生成预览的数据 @@ -211,7 +212,7 @@ async def search_gallery_with_preview( *, page: int = 1, sort: Literal['recent', 'popular-today', 'popular-week', 'popular'] = 'recent' - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """通过关键词搜索本子id和标题并生成预览图""" searching_result = await cls.search_gallery(keyword=keyword, page=page, sort=sort) name = f'Searching - {keyword} - Page {page}' @@ -246,7 +247,7 @@ async def query_gallery(self) -> NhentaiGalleryModel: raise TypeError('Query gallery model failed') return self.gallery_model - async def query_gallery_with_preview(self) -> "TemporaryResource": + async def query_gallery_with_preview(self) -> 'TemporaryResource': gallery_data = await self.query_gallery() name = f'NhentaiGallery - {gallery_data.id} - {gallery_data.title.japanese}' preview_request = await self.emit_preview_model_from_gallery_model(gallery_name=name, model=gallery_data) @@ -284,8 +285,10 @@ async def download_gallery(self, *, ignore_exist_file: bool = True) -> NhentaiDo # 执行下载任务 download_result = await semaphore_gather(tasks=download_tasks, semaphore_num=10) for result in download_result: - if isinstance(result, Exception): - raise WebSourceException(f'Some page(s) download failed, {result}') + if isinstance(result, WebSourceException): + raise WebSourceException(result.status_code, 'Some page(s) download failed') from result + elif isinstance(result, Exception): + raise WebSourceException(404, f'Some page(s) download failed, {result}') from result # 生成包含本子原始信息的文件 manifest_path = download_folder('manifest.json') diff --git a/src/utils/nhentai/model.py b/src/utils/nhentai/model.py index 0b7f80fd..58e47209 100644 --- a/src/utils/nhentai/model.py +++ b/src/utils/nhentai/model.py @@ -10,11 +10,12 @@ from dataclasses import dataclass from typing import Literal + from pydantic import BaseModel, ConfigDict, model_validator from src.compat import AnyHttpUrlStr as AnyHttpUrl from src.resource import TemporaryResource -from src.utils.image_utils.template import PreviewImageThumbs, PreviewImageModel +from src.utils.image_utils.template import PreviewImageModel, PreviewImageThumbs class BaseNhentaiModel(BaseModel): diff --git a/src/utils/common_api/__init__.py b/src/utils/omega_common_api/__init__.py similarity index 88% rename from src/utils/common_api/__init__.py rename to src/utils/omega_common_api/__init__.py index bc79d476..dff7f466 100644 --- a/src/utils/common_api/__init__.py +++ b/src/utils/omega_common_api/__init__.py @@ -1,7 +1,7 @@ """ @Author : Ailitonia @Date : 2024/8/7 10:57:12 -@FileName : common_api.py +@FileName : omega_common_api.py @Project : omega-miya @Description : 第三方 API 通用请求基类 @GitHub : https://github.com/Ailitonia diff --git a/src/utils/common_api/api_base.py b/src/utils/omega_common_api/api_base.py similarity index 78% rename from src/utils/common_api/api_base.py rename to src/utils/omega_common_api/api_base.py index f016d7ef..a802cb6d 100644 --- a/src/utils/common_api/api_base.py +++ b/src/utils/omega_common_api/api_base.py @@ -9,10 +9,10 @@ """ import abc -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from src.exception import WebSourceException -from src.service import OmegaRequests +from ..omega_requests import OmegaRequests if TYPE_CHECKING: from nonebot.internal.driver import ( @@ -24,6 +24,7 @@ QueryTypes, Response, ) + from src.resource import TemporaryResource @@ -53,13 +54,13 @@ def _load_cloudflare_clearance(cls) -> bool: @classmethod @abc.abstractmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': """内部方法, 获取默认 Headers""" raise NotImplementedError @classmethod @abc.abstractmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': """内部方法, 获取默认 Cookies""" raise NotImplementedError @@ -71,8 +72,8 @@ def _get_omega_requests_default_headers(cls) -> dict[str, str]: @classmethod def _init_omega_requests( cls, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 10, no_headers: bool = False, no_cookies: bool = False, @@ -92,36 +93,36 @@ def _init_omega_requests( return OmegaRequests(headers=headers, cookies=cookies, timeout=timeout, load_cloudflare_clearance=lcc) @staticmethod - def _parse_content_as_bytes(response: "Response") -> bytes: + def _parse_content_as_bytes(response: 'Response') -> bytes: return OmegaRequests.parse_content_as_bytes(response) @staticmethod - def _parse_content_as_json(response: "Response") -> Any: + def _parse_content_as_json(response: 'Response') -> Any: return OmegaRequests.parse_content_as_json(response) @staticmethod - def _parse_content_as_text(response: "Response") -> str: + def _parse_content_as_text(response: 'Response') -> str: return OmegaRequests.parse_content_as_text(response) @classmethod async def _request_get( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 10, no_headers: bool = False, no_cookies: bool = False, - ) -> "Response": + ) -> 'Response': """内部方法, 使用 GET 方法请求""" requests = cls._init_omega_requests( headers=headers, cookies=cookies, timeout=timeout, no_headers=no_headers, no_cookies=no_cookies ) response = await requests.get(url=url, params=params) if response.status_code != 200: - raise WebSourceException(f'{response.request}, status code {response.status_code}') + raise WebSourceException(response.status_code, f'{response.request}, status code {response.status_code}') return response @@ -129,25 +130,25 @@ async def _request_get( async def _request_post( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, - content: "ContentTypes" = None, - data: "DataTypes" = None, + content: 'ContentTypes' = None, + data: 'DataTypes' = None, json: Any = None, - files: "FilesTypes" = None, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + files: 'FilesTypes' = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 10, no_headers: bool = False, no_cookies: bool = False, - ) -> "Response": + ) -> 'Response': """内部方法, 使用 POST 方法请求""" requests = cls._init_omega_requests( headers=headers, cookies=cookies, timeout=timeout, no_headers=no_headers, no_cookies=no_cookies ) response = await requests.post(url=url, params=params, content=content, data=data, json=json, files=files) if response.status_code != 200: - raise WebSourceException(f'{response.request}, status code {response.status_code}') + raise WebSourceException(response.status_code, f'{response.request}, status code {response.status_code}') return response @@ -155,10 +156,10 @@ async def _request_post( async def _get_json( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 10, no_headers: bool = False, no_cookies: bool = False, @@ -174,14 +175,14 @@ async def _get_json( async def _post_json( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, - content: "ContentTypes" = None, - data: "DataTypes" = None, + content: 'ContentTypes' = None, + data: 'DataTypes' = None, json: Any = None, - files: "FilesTypes" = None, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + files: 'FilesTypes' = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 10, no_headers: bool = False, no_cookies: bool = False, @@ -197,10 +198,10 @@ async def _post_json( async def _get_resource_as_bytes( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 30, no_headers: bool = False, no_cookies: bool = False, @@ -216,10 +217,10 @@ async def _get_resource_as_bytes( async def _get_resource_as_text( cls, url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 10, no_headers: bool = False, no_cookies: bool = False, @@ -234,20 +235,20 @@ async def _get_resource_as_text( @classmethod async def _download_resource( cls, - save_folder: "TemporaryResource", + save_folder: 'TemporaryResource', url: str, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, *, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, timeout: int = 60, subdir: str | None = None, ignore_exist_file: bool = False, no_headers: bool = False, no_cookies: bool = False, hash_file_name: bool = False, - custom_file_name: Optional[str] = None, - ) -> "TemporaryResource": + custom_file_name: str | None = None, + ) -> 'TemporaryResource': """内部方法, 下载任意资源到本地, 保持原始文件名, 默认直接覆盖同名文件""" if custom_file_name is not None: file_name = custom_file_name diff --git a/src/service/omega_requests/__init__.py b/src/utils/omega_requests/__init__.py similarity index 99% rename from src/service/omega_requests/__init__.py rename to src/utils/omega_requests/__init__.py index f3c8f585..30f4083f 100644 --- a/src/service/omega_requests/__init__.py +++ b/src/utils/omega_requests/__init__.py @@ -10,7 +10,6 @@ from .requests import OmegaRequests - __all__ = [ 'OmegaRequests', ] diff --git a/src/service/omega_requests/config.py b/src/utils/omega_requests/config.py similarity index 89% rename from src/service/omega_requests/config.py rename to src/utils/omega_requests/config.py index 97029c7b..dc7bb632 100644 --- a/src/service/omega_requests/config.py +++ b/src/utils/omega_requests/config.py @@ -11,14 +11,14 @@ from typing import Literal from nonebot import get_plugin_config, logger -from pydantic import BaseModel, ConfigDict, IPvAnyAddress, ValidationError +from pydantic import BaseModel, ConfigDict, Field, IPvAnyAddress, ValidationError class HttpProxyConfig(BaseModel): """Http 代理配置""" enable_proxy: bool = False proxy_type: Literal['http'] = 'http' # 仅支持 http 代理 - proxy_address: IPvAnyAddress = '127.0.0.1' + proxy_address: IPvAnyAddress = Field('127.0.0.1') proxy_port: int = 1081 model_config = ConfigDict(extra='ignore') diff --git a/src/service/omega_requests/requests.py b/src/utils/omega_requests/requests.py similarity index 80% rename from src/service/omega_requests/requests.py rename to src/utils/omega_requests/requests.py index 0ba24ac7..ebadbc60 100644 --- a/src/service/omega_requests/requests.py +++ b/src/utils/omega_requests/requests.py @@ -11,10 +11,11 @@ import hashlib import pathlib from asyncio.exceptions import TimeoutError as AsyncTimeoutError +from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from copy import deepcopy -from typing import TYPE_CHECKING, AsyncGenerator, Optional, Any -from urllib.parse import urlparse, unquote +from typing import TYPE_CHECKING, Any, Optional +from urllib.parse import unquote, urlparse import ujson from nonebot import get_driver, logger @@ -26,14 +27,13 @@ ) from src.exception import WebSourceException -from src.resource import TemporaryResource from .config import http_proxy_config from .utils import cloudflare_clearance_config if TYPE_CHECKING: from nonebot.internal.driver import ( - CookieTypes, ContentTypes, + CookieTypes, DataTypes, FilesTypes, HeaderTypes, @@ -43,12 +43,10 @@ WebSocket, ) + from src.resource import BaseResource -class ExceededAttemptError(WebSourceException): - """重试次数超过限制异常""" - -class OmegaRequests(object): +class OmegaRequests: """对 ForwardDriver 二次封装实现的 HttpClient""" _default_retry_limit: int = 3 @@ -58,22 +56,22 @@ class OmegaRequests(object): 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.9', 'dnt': '1', - 'sec-ch-ua': '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"', + 'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-gpc': '1', 'upgrade-insecure-requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' - 'Chrome/127.0.0.0 Safari/537.36' + 'Chrome/131.0.0.0 Safari/537.36' } def __init__( self, *, - timeout: Optional[float] = None, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, - retry: Optional[int] = None, + timeout: float | None = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, + retry: int | None = None, load_cloudflare_clearance: bool = False, ): self.driver = get_driver() @@ -90,7 +88,7 @@ def __init__( self.load_cloudflare_clearance = load_cloudflare_clearance @staticmethod - def parse_content_as_bytes(response: "Response", encoding: str = 'utf-8') -> bytes: + def parse_content_as_bytes(response: 'Response', encoding: str = 'utf-8') -> bytes: """解析 Response Content 为 bytes""" if isinstance(response.content, str): return response.content.encode(encoding=encoding) @@ -100,14 +98,14 @@ def parse_content_as_bytes(response: "Response", encoding: str = 'utf-8') -> byt return b'' if response.content is None else bytes(response.content) @staticmethod - def parse_content_as_json(response: "Response", **kwargs) -> Any: + def parse_content_as_json(response: 'Response', **kwargs) -> Any: """解析 Response Content 为 Json""" if response.content is None: raise ValueError('content of response is None') return ujson.loads(response.content, **kwargs) @staticmethod - def parse_content_as_text(response: "Response", encoding: str = 'utf-8') -> str: + def parse_content_as_text(response: 'Response', encoding: str = 'utf-8') -> str: """解析 Response Content 为字符串""" if isinstance(response.content, str): return response.content @@ -137,7 +135,7 @@ def hash_url_file_name(cls, *prefix: str, url: str) -> str: def get_default_headers(cls) -> dict[str, str]: return deepcopy(cls._default_headers) - def get_session(self, params: Optional["QueryTypes"] = None, use_proxy: bool = True) -> "HTTPClientSession": + def get_session(self, params: Optional['QueryTypes'] = None, use_proxy: bool = True) -> 'HTTPClientSession': if not isinstance(self.driver, HTTPClientMixin): raise RuntimeError( f"Current driver {self.driver.type} doesn't support forward http connections! " @@ -151,7 +149,7 @@ def get_session(self, params: Optional["QueryTypes"] = None, use_proxy: bool = T proxy=http_proxy_config.proxy_url if use_proxy else None ) - async def request(self, setup: Request) -> "Response": + async def request(self, setup: Request) -> 'Response': """装饰原 request 方法, 自动重试""" if not isinstance(self.driver, HTTPClientMixin): raise RuntimeError( @@ -163,8 +161,8 @@ async def request(self, setup: Request) -> "Response": if self.load_cloudflare_clearance: domain_cloudflare_clearance = cloudflare_clearance_config.get_url_config(url=str(setup.url)) if domain_cloudflare_clearance is not None: - setup.headers.update(domain_cloudflare_clearance.headers) - setup.cookies.update(domain_cloudflare_clearance.cookies) + setup.headers.update(domain_cloudflare_clearance.get_headers()) + setup.cookies.update(domain_cloudflare_clearance.get_cookies()) # 处理自动重试 attempts_num = 0 @@ -190,10 +188,10 @@ async def request(self, setup: Request) -> "Response": logger.opt(colors=True).error( f'Omega Requests | {setup!r} failed with {attempts_num} times attempts > ' - f'Exception ExceededAttemptError: The number of attempts exceeds limit with final exception: ' + f'ExceededAttemptLimited: The number of attempts exceeds limit with final exception: ' f'{final_exception.__class__.__name__}: {final_exception}' ) - raise ExceededAttemptError('The number of attempts exceeds limit.') + raise WebSourceException(500, 'The number of attempts exceeds limit.') @asynccontextmanager async def websocket( @@ -201,16 +199,16 @@ async def websocket( method: str, url: str, *, - params: "QueryTypes" = None, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, - content: "ContentTypes" = None, - data: "DataTypes" = None, + params: 'QueryTypes' = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, + content: 'ContentTypes' = None, + data: 'DataTypes' = None, json: Any = None, - files: "FilesTypes" = None, - timeout: Optional[float] = None, + files: 'FilesTypes' = None, + timeout: float | None = None, use_proxy: bool = True - ) -> AsyncGenerator["WebSocket", None]: + ) -> AsyncGenerator['WebSocket', None]: """建立 websocket 连接""" if not isinstance(self.driver, WebSocketClientMixin): raise RuntimeError( @@ -239,16 +237,16 @@ async def get( self, url: str, *, - params: "QueryTypes" = None, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, - content: "ContentTypes" = None, - data: "DataTypes" = None, + params: 'QueryTypes' = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, + content: 'ContentTypes' = None, + data: 'DataTypes' = None, json: Any = None, - files: "FilesTypes" = None, - timeout: Optional[float] = None, + files: 'FilesTypes' = None, + timeout: float | None = None, use_proxy: bool = True - ) -> "Response": + ) -> 'Response': setup = Request( method='GET', url=url, @@ -268,16 +266,16 @@ async def post( self, url: str, *, - params: "QueryTypes" = None, - headers: "HeaderTypes" = None, - cookies: "CookieTypes" = None, - content: "ContentTypes" = None, - data: "DataTypes" = None, + params: 'QueryTypes' = None, + headers: 'HeaderTypes' = None, + cookies: 'CookieTypes' = None, + content: 'ContentTypes' = None, + data: 'DataTypes' = None, json: Any = None, - files: "FilesTypes" = None, - timeout: Optional[float] = None, + files: 'FilesTypes' = None, + timeout: float | None = None, use_proxy: bool = True - ) -> "Response": + ) -> 'Response': setup = Request( method='POST', url=url, @@ -293,15 +291,15 @@ async def post( ) return await self.request(setup=setup) - async def download( + async def download[T: 'BaseResource']( self, url: str, - file: TemporaryResource, + file: T, *, - params: "QueryTypes" = None, + params: 'QueryTypes' = None, ignore_exist_file: bool = False, **kwargs - ) -> TemporaryResource: + ) -> T: """下载文件 :param url: 链接 @@ -318,7 +316,9 @@ async def download( if response.status_code != 200: logger.opt(colors=True).error(f'Omega Requests | Download {url!r} ' f'to {file!r} failed with code {response.status_code!r}') - raise WebSourceException(f'Download {url!r} to {file!r} failed with code {response.status_code!r}') + raise WebSourceException( + response.status_code, f'Download {url!r} to {file!r} failed with code {response.status_code!r}' + ) async with file.async_open(mode='wb') as af: await af.write(self.parse_content_as_bytes(response=response)) diff --git a/src/service/omega_requests/utils.py b/src/utils/omega_requests/utils.py similarity index 64% rename from src/service/omega_requests/utils.py rename to src/utils/omega_requests/utils.py index 56132a8d..fa66c8b1 100644 --- a/src/service/omega_requests/utils.py +++ b/src/utils/omega_requests/utils.py @@ -8,24 +8,42 @@ @Software : PyCharm """ +from typing import Any + from nonebot import get_plugin_config, logger -from pydantic import AnyHttpUrl, BaseModel, ConfigDict, TypeAdapter, ValidationError +from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field, TypeAdapter, ValidationError class CloudflareClearanceModel(BaseModel): model_config = ConfigDict(extra='ignore') +class DomainCloudflareClearanceCookies(CloudflareClearanceModel): + cf_clearance: str + cf_bm: str | None = Field(None, alias='__cf_bm') + cflb: str | None = Field(None, alias='__cflb') + + +class DomainCloudflareClearanceHeaders(CloudflareClearanceModel): + user_agent: str | None = Field(None, alias='User-Agent') + + class DomainCloudflareClearance(CloudflareClearanceModel): """网站的 Cloudflare Clearance cookie 内容""" domain: str - cookies: dict[str, str] - headers: dict[str, str] + cookies: DomainCloudflareClearanceCookies + headers: DomainCloudflareClearanceHeaders + + def get_cookies(self) -> dict[str, Any]: + return {k: v for k, v in self.cookies.model_dump(by_alias=True).items() if v is not None} + + def get_headers(self) -> dict[str, Any]: + return {k: v for k, v in self.headers.model_dump(by_alias=True).items() if v is not None} class CloudflareClearanceConfig(CloudflareClearanceModel): """Cloudflare Clearance 配置""" - cloudflare_clearance_config: list[DomainCloudflareClearance] = [] + cloudflare_clearance_config: list[DomainCloudflareClearance] = Field(default_factory=list) @property def _config_map(self) -> dict[str, DomainCloudflareClearance]: @@ -52,5 +70,5 @@ def get_url_config(self, url: str) -> DomainCloudflareClearance | None: __all__ = [ - 'cloudflare_clearance_config' + 'cloudflare_clearance_config', ] diff --git a/src/utils/pixiv_api/__init__.py b/src/utils/pixiv_api/__init__.py index f622598e..96396fe6 100644 --- a/src/utils/pixiv_api/__init__.py +++ b/src/utils/pixiv_api/__init__.py @@ -11,7 +11,6 @@ from .pixiv import PixivArtwork, PixivCommon, PixivUser from .pixivision import Pixivision - __all__ = [ 'PixivArtwork', 'PixivCommon', diff --git a/src/utils/pixiv_api/api_base.py b/src/utils/pixiv_api/api_base.py index 1f40df7a..f0ad5a99 100644 --- a/src/utils/pixiv_api/api_base.py +++ b/src/utils/pixiv_api/api_base.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING -from src.utils.common_api import BaseCommonAPI +from src.utils import BaseCommonAPI from .config import pixiv_config if TYPE_CHECKING: @@ -33,22 +33,22 @@ def _load_cloudflare_clearance(cls) -> bool: return False @classmethod - def _get_default_headers(cls) -> "HeaderTypes": + def _get_default_headers(cls) -> 'HeaderTypes': headers = cls._get_omega_requests_default_headers() headers.update({'referer': 'https://www.pixiv.net/'}) return headers @classmethod - def _get_default_cookies(cls) -> "CookieTypes": + def _get_default_cookies(cls) -> 'CookieTypes': return pixiv_config.cookie_phpssid @classmethod - async def get_resource_as_bytes(cls, url: str, *, params: "QueryTypes" = None, timeout: int = 30) -> bytes: + async def get_resource_as_bytes(cls, url: str, *, params: 'QueryTypes' = None, timeout: int = 30) -> bytes: """请求原始资源内容""" return await cls._get_resource_as_bytes(url, params, timeout=timeout) @classmethod - async def get_resource_as_text(cls, url: str, *, params: "QueryTypes" = None, timeout: int = 10) -> str: + async def get_resource_as_text(cls, url: str, *, params: 'QueryTypes' = None, timeout: int = 10) -> str: """请求原始资源内容""" return await cls._get_resource_as_text(url, params, timeout=timeout) diff --git a/src/utils/pixiv_api/exception.py b/src/utils/pixiv_api/exception.py deleted file mode 100644 index 04c89027..00000000 --- a/src/utils/pixiv_api/exception.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2022/04/09 15:57 -@FileName : exception.py -@Project : nonebot2_miya -@Description : Pixiv custom Exception -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from src.exception import WebSourceException - - -class BasePixivError(WebSourceException): - """Pixiv 异常基类""" - - -class PixivApiError(BasePixivError): - """Pixiv API 返回错误""" - - -__all__ = [ - 'PixivApiError', -] diff --git a/src/utils/pixiv_api/helper.py b/src/utils/pixiv_api/helper.py index e569d474..821abacd 100644 --- a/src/utils/pixiv_api/helper.py +++ b/src/utils/pixiv_api/helper.py @@ -18,7 +18,7 @@ from .model.user import PixivGlobalData, PixivUserSearchingModel -class PixivParser(object): +class PixivParser: """Pixiv 页面解析工具集""" @staticmethod diff --git a/src/utils/pixiv_api/model/__init__.py b/src/utils/pixiv_api/model/__init__.py index 5ada8cc0..6df4b017 100644 --- a/src/utils/pixiv_api/model/__init__.py +++ b/src/utils/pixiv_api/model/__init__.py @@ -8,15 +8,28 @@ @Software : PyCharm """ -from .artwork import (PixivArtworkDataModel, PixivArtworkPageModel, PixivArtworkUgoiraMeta, - PixivArtworkCompleteDataModel, PixivArtworkRecommendModel, - PixivArtworkPreviewRequestModel) +from .artwork import ( + PixivArtworkCompleteDataModel, + PixivArtworkDataModel, + PixivArtworkPageModel, + PixivArtworkPreviewRequestModel, + PixivArtworkRecommendModel, + PixivArtworkUgoiraMeta, +) from .discovery import PixivDiscoveryModel, PixivTopModel from .pixivision import PixivisionArticle, PixivisionIllustrationList from .ranking import PixivRankingModel from .searching import PixivSearchingResultModel -from .user import (PixivGlobalData, PixivUserDataModel, PixivUserArtworkDataModel, PixivUserModel, - PixivUserSearchingModel, PixivFollowLatestIllust, PixivBookmark) +from .user import ( + PixivBookmark, + PixivFollowLatestIllust, + PixivFollowUser, + PixivGlobalData, + PixivUserArtworkDataModel, + PixivUserDataModel, + PixivUserModel, + PixivUserSearchingModel, +) __all__ = [ 'PixivArtworkDataModel', @@ -35,7 +48,8 @@ 'PixivUserModel', 'PixivUserSearchingModel', 'PixivFollowLatestIllust', + 'PixivFollowUser', 'PixivisionArticle', 'PixivisionIllustrationList', - 'PixivBookmark' + 'PixivBookmark', ] diff --git a/src/utils/pixiv_api/model/artwork.py b/src/utils/pixiv_api/model/artwork.py index 01fba832..e7d9b1dd 100644 --- a/src/utils/pixiv_api/model/artwork.py +++ b/src/utils/pixiv_api/model/artwork.py @@ -8,11 +8,10 @@ @Software : PyCharm """ + from lxml import etree -from typing import Optional from src.compat import AnyHttpUrlStr as AnyHttpUrl - from .base_model import BasePixivModel from .searching import PixivSearchingData @@ -25,15 +24,15 @@ class PixivTagTranslation(BasePixivModel): class PixivTag(BasePixivModel): """Pixiv tag 模型""" tag: str - translation: Optional[PixivTagTranslation] = None + translation: PixivTagTranslation | None = None class PixivArtworkTags(BasePixivModel): """Pixiv 作品 tag 属性""" tags: list[PixivTag] - authorId: Optional[int] = None - isLocked: Optional[bool] = None - writable: Optional[bool] = None + authorId: int | None = None + isLocked: bool | None = None + writable: bool | None = None @property def all_tags(self) -> list[str]: @@ -83,7 +82,7 @@ def parsed_description(self) -> str: return '' description_html = etree.HTML(self.description) - for br in description_html.xpath("*//br"): + for br in description_html.xpath('*//br'): br.tail = '\n' + br.tail if br.tail else '\n' # replace br tag return ''.join(text for x in description_html.xpath('/html/body/*') for text in x.itertext()) @@ -190,7 +189,7 @@ class PixivArtworkCompleteDataModel(BasePixivModel): regular_url: AnyHttpUrl all_url: PixivArtworkAllPages all_page: dict[int, PixivArtworkPageUrl] - ugoira_meta: Optional[PixivArtworkUgoiraMetaBody] = None + ugoira_meta: PixivArtworkUgoiraMetaBody | None = None class PixivArtworkRecommendModel(BasePixivModel): diff --git a/src/utils/pixiv_api/model/discovery.py b/src/utils/pixiv_api/model/discovery.py index 131eff8a..c69b82cc 100644 --- a/src/utils/pixiv_api/model/discovery.py +++ b/src/utils/pixiv_api/model/discovery.py @@ -9,7 +9,6 @@ """ import random -from typing import Optional from src.compat import AnyHttpUrlStr as AnyHttpUrl from .base_model import BasePixivModel @@ -18,7 +17,7 @@ class ThumbnailData(BasePixivModel): id: int title: str - alt: Optional[str] = None + alt: str | None = None userId: int userName: str aiType: int @@ -130,7 +129,7 @@ class PixivTopUser(BasePixivModel): image: AnyHttpUrl imageBig: AnyHttpUrl premium: bool - comment: Optional[str] = None + comment: str | None = None class PixivTopBody(BasePixivModel): diff --git a/src/utils/pixiv_api/model/searching.py b/src/utils/pixiv_api/model/searching.py index 9a845c5c..e682101d 100644 --- a/src/utils/pixiv_api/model/searching.py +++ b/src/utils/pixiv_api/model/searching.py @@ -8,7 +8,6 @@ @Software : PyCharm """ -from typing import Optional from src.compat import AnyHttpUrlStr as AnyHttpUrl @@ -36,9 +35,9 @@ class PixivSearchingContent(BasePixivModel): class PixivSearchingResultBody(BasePixivModel): """Pixiv 搜索结果 body""" - illustManga: Optional[PixivSearchingContent] = None - illust: Optional[PixivSearchingContent] = None - manga: Optional[PixivSearchingContent] = None + illustManga: PixivSearchingContent | None = None + illust: PixivSearchingContent | None = None + manga: PixivSearchingContent | None = None popular: dict extraData: dict diff --git a/src/utils/pixiv_api/model/user.py b/src/utils/pixiv_api/model/user.py index ed7382da..ff5ad0c9 100644 --- a/src/utils/pixiv_api/model/user.py +++ b/src/utils/pixiv_api/model/user.py @@ -8,9 +8,9 @@ @Software : PyCharm """ -from typing import Any, Optional +from typing import Any -from pydantic import model_validator +from pydantic import Field, model_validator from src.compat import AnyHttpUrlStr as AnyHttpUrl from .base_model import BasePixivModel @@ -21,12 +21,12 @@ class _GlobalUserData(BasePixivModel): id: int pixivId: str name: str - profileImg: Optional[AnyHttpUrl] = None - profileImgBig: Optional[AnyHttpUrl] = None + profileImg: AnyHttpUrl | None = None + profileImgBig: AnyHttpUrl | None = None premium: bool xRestrict: int adult: bool - safeMode: bool + safeMode: bool | None = None # maybe deactivated illustCreator: bool novelCreator: bool hideAiWorks: bool @@ -38,13 +38,13 @@ class PixivGlobalData(BasePixivModel): token: str services: dict oneSignalAppId: str - publicPath: Optional[AnyHttpUrl] = None - commonResourcePath: Optional[AnyHttpUrl] = None + publicPath: AnyHttpUrl | None = None + commonResourcePath: AnyHttpUrl | None = None development: bool userData: _GlobalUserData - adsData: Optional[dict] = None - miscData: Optional[dict] = None - premium: Optional[dict] = None + adsData: dict | None = None + miscData: dict | None = None + premium: dict | None = None mute: list @property @@ -60,8 +60,8 @@ class PixivUserDataBody(BasePixivModel): """Pixiv 用户信息 Body""" userId: int name: str - image: Optional[AnyHttpUrl] = None - imageBig: Optional[AnyHttpUrl] = None + image: AnyHttpUrl | None = None + imageBig: AnyHttpUrl | None = None class PixivUserDataModel(BasePixivModel): @@ -111,8 +111,8 @@ class PixivUserModel(BasePixivModel): """Pixiv 用户 Model""" user_id: int name: str - image: Optional[AnyHttpUrl] = None - image_big: Optional[AnyHttpUrl] = None + image: AnyHttpUrl | None = None + image_big: AnyHttpUrl | None = None illusts: list[int] manga: list[int] novels: list[int] @@ -128,10 +128,10 @@ class PixivUserSearchingBody(BasePixivModel): """Pixiv 用户搜索结果 body""" user_id: int user_name: str - user_head_url: Optional[str] = None - user_illust_count: Optional[int] = None - user_desc: Optional[str] = None - illusts_thumb_urls: list[AnyHttpUrl] = [] + user_head_url: str | None = None + user_illust_count: int | None = None + user_desc: str | None = None + illusts_thumb_urls: list[AnyHttpUrl] = Field(default_factory=list) class PixivUserSearchingModel(BasePixivModel): @@ -193,7 +193,7 @@ class BookmarkWork(BasePixivModel): height: int pageCount: int isBookmarkable: bool - bookmarkData: Optional[_WorkBookmarkData] = None + bookmarkData: _WorkBookmarkData | None = None alt: str titleCaptionTranslation: dict createDate: str @@ -201,12 +201,12 @@ class BookmarkWork(BasePixivModel): isUnlisted: bool isMasked: bool aiType: int - profileImageUrl: Optional[AnyHttpUrl] = None + profileImageUrl: AnyHttpUrl | None = None class BookmarkBody(BasePixivModel): """收藏页内容""" - bookmarkTags: Optional[list | dict] = None + bookmarkTags: list | dict | None = None extraData: dict total: int works: list[BookmarkWork] @@ -228,6 +228,84 @@ def illust_ids(self) -> list[int]: return [x.id for x in self.body.works] +class FollowUserIllust(BasePixivModel): + id: str + title: str + illustType: int + xRestrict: int + restrict: int + sl: int + url: str + description: str + tags: list[str] + userId: str + userName: str + width: int + height: int + pageCount: int + isBookmarkable: bool + alt: str + createDate: str + updateDate: str + isUnlisted: bool + isMasked: bool + aiType: int + profileImageUrl: str + + +class FollowUserNovel(BasePixivModel): + id: str + title: str + genre: str + xRestrict: int + restrict: int + url: str + tags: list[str] + userId: str + userName: str + profileImageUrl: str + textCount: int + wordCount: int + readingTime: int + useWordCount: bool + description: str + isBookmarkable: bool + bookmarkCount: int + isOriginal: bool + createDate: str + updateDate: str + isMasked: bool + aiType: int + isUnlisted: bool + + +class FollowUserData(BasePixivModel): + """关注的用户信息""" + userId: str + userName: str + profileImageUrl: str + userComment: str + following: bool + followed: bool + isBlocking: bool + isMypixiv: bool + illusts: list[FollowUserIllust] + novels: list[FollowUserNovel] + + +class FollowUserBody(BasePixivModel): + users: list[FollowUserData] + total: int + followUserTags: list[Any] + + +class PixivFollowUser(BasePixivModel): + """关注用户""" + error: bool + message: str + body: FollowUserBody + + __all__ = [ 'PixivGlobalData', 'PixivUserDataModel', @@ -236,5 +314,6 @@ def illust_ids(self) -> list[int]: 'PixivUserSearchingBody', 'PixivUserSearchingModel', 'PixivFollowLatestIllust', - 'PixivBookmark' + 'PixivBookmark', + 'PixivFollowUser', ] diff --git a/src/utils/pixiv_api/pixiv.py b/src/utils/pixiv_api/pixiv.py index 7a74d90c..ef3ec8d3 100644 --- a/src/utils/pixiv_api/pixiv.py +++ b/src/utils/pixiv_api/pixiv.py @@ -10,32 +10,32 @@ import re from datetime import datetime -from typing import Literal, Optional +from typing import Literal from urllib.parse import quote from pydantic import ValidationError from src.exception import WebSourceException from .api_base import BasePixivAPI -from .exception import PixivApiError from .helper import PixivParser from .model import ( + PixivArtworkCompleteDataModel, PixivArtworkDataModel, PixivArtworkPageModel, - PixivArtworkUgoiraMeta, - PixivArtworkCompleteDataModel, PixivArtworkRecommendModel, + PixivArtworkUgoiraMeta, + PixivBookmark, + PixivDiscoveryModel, + PixivFollowLatestIllust, + PixivFollowUser, + PixivGlobalData, PixivRankingModel, PixivSearchingResultModel, - PixivDiscoveryModel, PixivTopModel, - PixivGlobalData, - PixivUserDataModel, PixivUserArtworkDataModel, + PixivUserDataModel, PixivUserModel, PixivUserSearchingModel, - PixivFollowLatestIllust, - PixivBookmark ) @@ -109,20 +109,27 @@ async def search( :return: dict, 原始返回数据 """ word = quote(word, encoding='utf-8') - params = { - 'word': word, 'order': order, 'mode': mode_, 'p': page, 's_mode': s_mode_, 'type': type_, 'lang': lang_} - if ai_type: - params.update({'ai_type': ai_type}) - if ratio_: + params: dict[str, str] = { + 'word': word, + 'order': order, + 'mode': mode_, + 'p': str(page), + 's_mode': s_mode_, + 'type': type_, + 'lang': lang_, + } + if ai_type is not None: + params.update({'ai_type': str(ai_type)}) + if ratio_ is not None: params.update({'ratio': ratio_}) - if scd_: + if scd_ is not None: params.update({'scd': scd_.strftime('%Y-%m-%d')}) - if ecd_: + if ecd_ is not None: params.update({'ecd': ecd_.strftime('%Y-%m-%d')}) - if blt_: - params.update({'blt': blt_}) - if bgt_: - params.update({'bgt': bgt_}) + if blt_ is not None: + params.update({'blt': str(blt_)}) + if bgt_ is not None: + params.update({'bgt': str(bgt_)}) searching_url = f'{cls._get_root_url()}/ajax/search/{mode}/{word}' searching_data = await cls._get_json(url=searching_url, params=params) @@ -130,9 +137,15 @@ async def search( @classmethod async def search_by_default_popular_condition(cls, word: str, *, page: int = 1) -> PixivSearchingResultModel: - """Pixiv 搜索 (使用热度作为过滤条件筛选条件) (需要pixiv高级会员)""" + """Pixiv 搜索 (默认使用 illust/safe 作为过滤条件, 按热度排序) (需要pixiv高级会员)""" return await cls.search( - word=word, mode='illustrations', page=page, order='popular_d', mode_='safe', type_='illust', ai_type=1 + word=word, + mode='illustrations', + page=page, + order='popular_d', + mode_='safe', + type_='illust', + ai_type=1, ) @classmethod @@ -217,7 +230,7 @@ def __init__(self, pid: int): self.recommend_url = f'{self.data_url}/recommend/init' # 实例缓存 - self.artwork_model: Optional[PixivArtworkCompleteDataModel] = None + self.artwork_model: PixivArtworkCompleteDataModel | None = None def __repr__(self) -> str: return f'{self.__class__.__name__}(pid={self.pid})' @@ -244,19 +257,23 @@ async def query_artwork(self) -> PixivArtworkCompleteDataModel: artwork_data = await self._query_data() except ValidationError: raise + except WebSourceException as e: + raise WebSourceException(e.status_code, f'Query {self!r} data failed') from e except Exception as e: - raise WebSourceException(f'Query artwork(pid={self.pid}) data failed, {e}') from e + raise WebSourceException(404, f'Query {self!r} data failed, {e!r}') from e if artwork_data.error: - raise PixivApiError(f'Query artwork(pid={self.pid}) data failed, {artwork_data.message}') + raise WebSourceException(404, f'Query {self!r} data failed, {artwork_data.message}') try: page_data = await self._query_page_date() except ValidationError: raise + except WebSourceException as e: + raise WebSourceException(e.status_code, f'Query {self!r} data failed') from e except Exception as e: - raise WebSourceException(f'Query artwork(pid={self.pid}) page failed, {e}') from e + raise WebSourceException(404, f'Query {self!r} page failed, {e!r}') from e if page_data.error: - raise PixivApiError(f'Query artwork(pid={self.pid}) page failed, {page_data.message}') + raise WebSourceException(404, f'Query {self!r} page failed, {page_data.message}') # 处理作品tag tags = artwork_data.body.tags.all_tags @@ -290,12 +307,9 @@ async def query_artwork(self) -> PixivArtworkCompleteDataModel: # 如果是动图额外处理动图资源 illust_type = artwork_data.body.illustType if illust_type == 2: - try: - ugoira_data = await self._query_ugoira_meta() - if ugoira_data.error: - raise PixivApiError(f'Query artwork(pid={self.pid}) ugoira meta failed, {ugoira_data.message}') - except Exception as e: - raise WebSourceException(f'Query artwork(pid={self.pid}) ugoira meta failed, {e}') from e + ugoira_data = await self._query_ugoira_meta() + if ugoira_data.error: + raise WebSourceException(404, f'Query {self!r} ugoira meta failed, {ugoira_data.message}') ugoira_meta = ugoira_data.body else: ugoira_meta = None @@ -356,9 +370,10 @@ def __init__(self, uid: int): self.user_url = f'{self._get_root_url()}/users/{uid}' self.data_url = f'{self._get_root_url()}/ajax/user/{uid}' self.profile_url = f'{self._get_root_url()}/ajax/user/{uid}/profile/all' + self.follow_user_url = f'https://www.pixiv.net/ajax/user/{self.uid}/following' # 实例缓存 - self.user_model: Optional[PixivUserModel] = None + self.user_model: PixivUserModel | None = None def __repr__(self) -> str: return f'{self.__class__.__name__}(uid={self.uid})' @@ -398,11 +413,11 @@ async def query_user_data(self) -> PixivUserModel: if not isinstance(self.user_model, PixivUserModel): _user_data = await self._query_user_data() if _user_data.error: - raise PixivApiError(f'Query user(uid={self.uid}) data failed, {_user_data.message}') + raise WebSourceException(404, f'Query {self!r} data failed, {_user_data.message}') _user_artwork_data = await self._query_user_artwork_data() if _user_artwork_data.error: - raise PixivApiError(f'Query user(uid={self.uid}) artwork data failed, {_user_artwork_data.message}') + raise WebSourceException(404, f'Query {self!r} artwork data failed, {_user_artwork_data.message}') _data = { 'user_id': _user_data.body.userId, @@ -421,8 +436,33 @@ async def query_user_data(self) -> PixivUserModel: async def query_user_bookmarks(self, page: int = 1) -> PixivBookmark: """获取该用户的收藏, 默认每页 48 张作品""" + page = 1 if page < 1 else page return await self.query_bookmarks(uid=self.uid, offset=48 * (page - 1)) + async def query_user_following_users( + self, + offset: int = 24, + limit: int = 24, + *, + rest: Literal['show', 'hide'] = 'show', + tag: str | None = None, + accepting_requests: int = 0, + lang: str = 'zh', + ) -> PixivFollowUser: + """获取用户作品信息""" + params = { + 'offset': offset, + 'limit': limit, + 'rest': rest, + 'acceptingRequests': accepting_requests, + 'lang': lang, + } + if tag is not None: + params.update({'tag': tag}) + + following_user_data = await self._get_json(url=self.follow_user_url, params=params) + return PixivFollowUser.model_validate(following_user_data) + __all__ = [ 'PixivArtwork', diff --git a/src/utils/pixiv_api/pixivision.py b/src/utils/pixiv_api/pixivision.py index fa8c5b8d..d72c1fed 100644 --- a/src/utils/pixiv_api/pixivision.py +++ b/src/utils/pixiv_api/pixivision.py @@ -8,7 +8,7 @@ @Software : PyCharm """ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from .api_base import BasePixivAPI from .helper import PixivParser @@ -48,12 +48,12 @@ def _get_tag_url(cls) -> str: async def download_resource( cls, url: str, - save_folder: "TemporaryResource", - custom_file_name: Optional[str] = None, + save_folder: 'TemporaryResource', + custom_file_name: str | None = None, *, subdir: str | None = None, ignore_exist_file: bool = False - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" return await cls._download_resource( save_folder=save_folder, diff --git a/src/utils/process_utils/__init__.py b/src/utils/process_utils/__init__.py index f20d8d9b..d175b4a6 100644 --- a/src/utils/process_utils/__init__.py +++ b/src/utils/process_utils/__init__.py @@ -10,34 +10,35 @@ import asyncio import inspect +import random from asyncio import Future +from collections.abc import Awaitable, Callable, Coroutine, Sequence from functools import wraps -from typing import Any, Awaitable, Callable, Coroutine, Literal, ParamSpec, Sequence, TypeVar, overload +from typing import Any, Literal, overload from nonebot import logger -P = ParamSpec("P") -T = TypeVar("T") -R = TypeVar("R") - -def run_async_delay(delay_time: float = 5): +def run_async_delay(delay_time: float = 5, *, random_sigma: float | None = None): """一个用于包装 async function 使其延迟运行的装饰器 :param delay_time: 延迟的时间, 单位秒 + :param random_sigma: 启用延迟随机分布的标准差 """ - def decorator(func: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Coroutine[None, None, R]]: + def decorator[** P, R](func: Callable[P, Coroutine[None, None, R]]) -> Callable[P, Coroutine[None, None, R]]: if not inspect.iscoroutinefunction(func): raise ValueError('The decorated function must be coroutine function') @wraps(func) async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + delay = abs(random.gauss(delay_time, random_sigma)) if random_sigma is not None else delay_time _module = inspect.getmodule(func) logger.opt(colors=True).debug( f'Decorator RunAsyncDelay | {_module.__name__ if _module is not None else "Unknown"}.' - f'{func.__name__} > will delay execution after {delay_time} second(s)') - await asyncio.sleep(delay=delay_time) + f'{func.__name__} > will delay execution after {delay:.2f} second(s)' + ) + await asyncio.sleep(delay=delay) return await func(*args, **kwargs) return _wrapper @@ -46,7 +47,7 @@ async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: @overload -async def semaphore_gather( +async def semaphore_gather[T]( tasks: Sequence[Future[T] | Coroutine[Any, Any, T] | Awaitable[T]], semaphore_num: int, *, @@ -57,7 +58,7 @@ async def semaphore_gather( @overload -async def semaphore_gather( +async def semaphore_gather[T]( tasks: Sequence[Future[T] | Coroutine[Any, Any, T] | Awaitable[T]], semaphore_num: int, *, @@ -68,7 +69,7 @@ async def semaphore_gather( @overload -async def semaphore_gather( +async def semaphore_gather[T]( tasks: Sequence[Future[T] | Coroutine[Any, Any, T] | Awaitable[T]], semaphore_num: int, *, @@ -78,7 +79,7 @@ async def semaphore_gather( ... -async def semaphore_gather( +async def semaphore_gather[T]( tasks: Sequence[Future[T] | Coroutine[Any, Any, T] | Awaitable[T]], semaphore_num: int, *, @@ -150,5 +151,5 @@ async def _wrap_coro( __all__ = [ 'run_async_delay', - 'semaphore_gather' + 'semaphore_gather', ] diff --git a/src/utils/statistics_tools/plots.py b/src/utils/statistics_tools/plots.py index f40fa535..133d8027 100644 --- a/src/utils/statistics_tools/plots.py +++ b/src/utils/statistics_tools/plots.py @@ -9,15 +9,17 @@ """ import sys -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING -from matplotlib import font_manager, pyplot as plt +from matplotlib import font_manager +from matplotlib import pyplot as plt from .consts import STATISTICS_TOOLS_RESOURCE if TYPE_CHECKING: from matplotlib.axes import Axes from matplotlib.figure import Figure + from src.resource import TemporaryResource # 添加中文字体 @@ -46,11 +48,11 @@ def get_font_names() -> list[str]: def create_simple_figure( *, - num: Optional[int] = None, - figsize: Optional[tuple[float, float]] = None, - dpi: Optional[float] = None, + num: int | None = None, + figsize: tuple[float, float] | None = None, + dpi: float | None = None, **kwargs -) -> "Figure": +) -> 'Figure': """Create an empty figure with no Axes""" fig = plt.figure(num=num, figsize=figsize, dpi=dpi, **kwargs) return fig @@ -58,23 +60,23 @@ def create_simple_figure( def create_simple_subplots_figure( *, - figsize: Optional[tuple[float, float]] = None, - dpi: Optional[float] = None, -) -> tuple["Figure", "Axes"]: + figsize: tuple[float, float] | None = None, + dpi: float | None = None, +) -> tuple['Figure', 'Axes']: """Create a figure with a single Axes""" fig, ax = plt.subplots(nrows=1, ncols=1, figsize=figsize, dpi=dpi) return fig, ax def output_figure( - fig: "Figure", + fig: 'Figure', output_filename: str, *, dpi: int = 300, format_: str = 'JPG', bbox_inches: str = 'tight', **kwargs -) -> "TemporaryResource": +) -> 'TemporaryResource': """保存并导出生成的图表""" output_file = STATISTICS_TOOLS_RESOURCE.default_output_folder(output_filename) with output_file.open('wb') as f: diff --git a/src/utils/statistics_tools/sample.py b/src/utils/statistics_tools/sample.py index 4ddc09b9..d7de9c13 100644 --- a/src/utils/statistics_tools/sample.py +++ b/src/utils/statistics_tools/sample.py @@ -18,10 +18,11 @@ if TYPE_CHECKING: from numpy.typing import NDArray + from src.resource import TemporaryResource -def run_figure_example() -> "TemporaryResource": +def run_figure_example() -> 'TemporaryResource': x = np.linspace(0, 2, 100) # Sample data. # Note that even in the OO-style, we use `.pyplot.figure` to create the Figure. @@ -37,7 +38,7 @@ def run_figure_example() -> "TemporaryResource": return output_figure(fig, 'sample.jpg') -def invest_test(step: int = 100, init_balance: float = 1000000.0) -> "NDArray": +def invest_test(step: int = 100, init_balance: float = 1000000.0) -> 'NDArray': total_balance = init_balance rand_ar = np.random.rand(step) balance_ar = np.array(init_balance) @@ -50,7 +51,7 @@ def invest_test(step: int = 100, init_balance: float = 1000000.0) -> "NDArray": return balance_ar -def run_invest_test(step: int = 100, times: int = 10) -> "TemporaryResource": +def run_invest_test(step: int = 100, times: int = 10) -> 'TemporaryResource': fig, ax = create_simple_subplots_figure(figsize=(32, 16)) ax.set_yscale('log') for _ in range(times): @@ -67,7 +68,7 @@ def run_invest_test(step: int = 100, times: int = 10) -> "TemporaryResource": return output_figure(fig, 'invest_test.jpg') -def coin_test(init_balance: int = 0) -> tuple["NDArray", int]: +def coin_test(init_balance: int = 0) -> tuple['NDArray', int]: total_balance = init_balance balance_ar = np.array(init_balance) step = 0 @@ -84,7 +85,7 @@ def coin_test(init_balance: int = 0) -> tuple["NDArray", int]: return balance_ar, step -def run_coin_test(times: int = 10) -> "TemporaryResource": +def run_coin_test(times: int = 10) -> 'TemporaryResource': fig, ax = create_simple_subplots_figure(figsize=(8, 6)) data: list[int] = [int(coin_test()[0][-1]) for _ in range(times)] diff --git a/src/utils/tencent_cloud_api/__init__.py b/src/utils/tencent_cloud_api/__init__.py index 6c31f901..b3bdbc0f 100644 --- a/src/utils/tencent_cloud_api/__init__.py +++ b/src/utils/tencent_cloud_api/__init__.py @@ -10,7 +10,6 @@ from .api import TencentNLP, TencentTMT - __all__ = [ 'TencentNLP', 'TencentTMT' diff --git a/src/utils/tencent_cloud_api/api/__init__.py b/src/utils/tencent_cloud_api/api/__init__.py index 794fae49..c8032284 100644 --- a/src/utils/tencent_cloud_api/api/__init__.py +++ b/src/utils/tencent_cloud_api/api/__init__.py @@ -11,7 +11,6 @@ from .nlp import TencentNLP from .tmt import TencentTMT - __all__ = [ 'TencentNLP', 'TencentTMT' diff --git a/src/utils/tencent_cloud_api/api/base.py b/src/utils/tencent_cloud_api/api/base.py index bf1a4ebd..c5f89cb9 100644 --- a/src/utils/tencent_cloud_api/api/base.py +++ b/src/utils/tencent_cloud_api/api/base.py @@ -11,14 +11,14 @@ import hashlib import hmac import json -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Any -from src.service.omega_requests import OmegaRequests +from src.utils import OmegaRequests from ..config import tencent_cloud_config -class BaseTencentCloudAPI(object): +class BaseTencentCloudAPI: """腾讯云 API 基类""" __slots__ = ( @@ -63,7 +63,7 @@ def __upgrade_signed_header( ) -> None: # 初始化请求时间戳 self._request_timestamp = int(datetime.now().timestamp()) - self._date = datetime.fromtimestamp(self._request_timestamp, tz=timezone.utc).strftime('%Y-%m-%d') + self._date = datetime.fromtimestamp(self._request_timestamp, tz=UTC).strftime('%Y-%m-%d') self._credential_scope = f'{self._date}/{self._service}/tc3_request' # 生成签名 Headers 内容 @@ -126,7 +126,7 @@ def __sign_v3(self, payload: dict[str, Any]) -> str: def __sign(key: bytes | bytearray, msg: str) -> bytes: return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest() - secret_date = __sign(f'TC3{self._secret_key}'.encode('utf-8'), self._date) + secret_date = __sign(f'TC3{self._secret_key}'.encode(), self._date) secret_service = __sign(secret_date, self._service) secret_signing = __sign(secret_service, 'tc3_request') diff --git a/src/utils/tencent_cloud_api/api/nlp.py b/src/utils/tencent_cloud_api/api/nlp.py index cc8c16d8..cd45e972 100644 --- a/src/utils/tencent_cloud_api/api/nlp.py +++ b/src/utils/tencent_cloud_api/api/nlp.py @@ -8,22 +8,23 @@ @Software : PyCharm """ -from typing import Literal, Sequence +from collections.abc import Sequence +from typing import Literal from .base import BaseTencentCloudAPI from ..model.nlp import ( + TencentCloudAnalyzeSentimentResponse, + TencentCloudClassifyContentResponse, TencentCloudComposeCoupletResponse, TencentCloudComposePoetryResponse, - TencentCloudTextEmbellishResponse, - TencentCloudTextWritingResponse, - TencentCloudGenerateKeywordSentenceResponse, - TencentCloudSentenceCorrectionResponse, - TencentCloudClassifyContentResponse, - TencentCloudAnalyzeSentimentResponse, TencentCloudEvaluateSentenceSimilarityResponse, TencentCloudEvaluateWordSimilarityResponse, + TencentCloudGenerateKeywordSentenceResponse, TencentCloudParseWordsResponse, TencentCloudRetrieveSimilarWordsResponse, + TencentCloudSentenceCorrectionResponse, + TencentCloudTextEmbellishResponse, + TencentCloudTextWritingResponse, ) diff --git a/src/utils/tencent_cloud_api/api/tmt.py b/src/utils/tencent_cloud_api/api/tmt.py index 7073b372..d99b898f 100644 --- a/src/utils/tencent_cloud_api/api/tmt.py +++ b/src/utils/tencent_cloud_api/api/tmt.py @@ -8,10 +8,10 @@ @Software : PyCharm """ -from typing import Optional, Sequence +from collections.abc import Sequence from .base import BaseTencentCloudAPI -from ..model.tmt import TencentCloudTextTranslateResponse, TencentCloudTextTranslateBatchResponse +from ..model.tmt import TencentCloudTextTranslateBatchResponse, TencentCloudTextTranslateResponse class TencentTMT(BaseTencentCloudAPI): @@ -32,7 +32,7 @@ async def text_translate( source: str = 'auto', target: str = 'zh', project_id: int = 0, - untranslated_text: Optional[str] = None + untranslated_text: str | None = None ) -> TencentCloudTextTranslateResponse: """文本翻译 diff --git a/src/utils/tencent_cloud_api/model/base_model.py b/src/utils/tencent_cloud_api/model/base_model.py index 753593ae..66e4efe7 100644 --- a/src/utils/tencent_cloud_api/model/base_model.py +++ b/src/utils/tencent_cloud_api/model/base_model.py @@ -8,7 +8,6 @@ @Software : PyCharm """ -from typing import Optional from uuid import UUID from pydantic import BaseModel, ConfigDict @@ -37,7 +36,7 @@ class BaseTencentCloudErrorResponse(BaseModel): - RequestId: 唯一请求 ID, 由服务端生成, 每次请求都会返回(若请求因其他原因未能抵达服务端, 则该次请求不会获得 RequestId), 定位问题时需要提供该次请求的 RequestId """ Error: BaseTencentCloudError - RequestId: Optional[UUID] = None + RequestId: UUID | None = None class BaseTencentCloudSuccessResponse(BaseModel): @@ -45,7 +44,7 @@ class BaseTencentCloudSuccessResponse(BaseModel): - RequestId: 唯一请求 ID, 由服务端生成, 每次请求都会返回(若请求因其他原因未能抵达服务端, 则该次请求不会获得 RequestId), 定位问题时需要提供该次请求的 RequestId """ - RequestId: Optional[UUID] = None + RequestId: UUID | None = None class BaseTencentCloudResponse(BaseModel): diff --git a/src/utils/tencent_cloud_api/model/nlp.py b/src/utils/tencent_cloud_api/model/nlp.py index c74c2bc0..5a6fb053 100644 --- a/src/utils/tencent_cloud_api/model/nlp.py +++ b/src/utils/tencent_cloud_api/model/nlp.py @@ -8,13 +8,13 @@ @Software : PyCharm """ -from typing import Literal, Optional +from typing import Literal from .base_model import ( - BaseTencentCloudModel, BaseTencentCloudErrorResponse, - BaseTencentCloudSuccessResponse, + BaseTencentCloudModel, BaseTencentCloudResponse, + BaseTencentCloudSuccessResponse, ) @@ -38,7 +38,7 @@ class TencentCloudComposeCoupletSuccessResponse(BaseTencentCloudSuccessResponse) """ TopScroll: str Content: list[str] - RandomCause: Optional[str] = None + RandomCause: str | None = None class TencentCloudComposeCoupletResponse(BaseTencentCloudResponse): @@ -66,8 +66,8 @@ class TencentCloudEmbellishList(BaseTencentCloudModel): - Text: 润色后的文本, 注意: 此字段可能返回 null, 表示取不到有效值 - EmbellishType: 润色类型, expansion: 扩写, rewriting: 改写, translation_m2a: 从现代文改写为古文, translation_a2m: 从古文改写为现代文, 注意: 此字段可能返回 null, 表示取不到有效值 """ - Text: Optional[str] = None - EmbellishType: Optional[str] = None + Text: str | None = None + EmbellishType: str | None = None class TencentCloudTextEmbellishSuccessResponse(BaseTencentCloudSuccessResponse): @@ -144,11 +144,11 @@ class TencentCloudCorrectionItem(BaseTencentCloudModel): BeginOffset: int Len: int Word: str - CorrectWord: Optional[list[str]] = None + CorrectWord: list[str] | None = None CorrectionType: int Confidence: int - DescriptionZh: Optional[str] = None - DescriptionEn: Optional[str] = None + DescriptionZh: str | None = None + DescriptionEn: str | None = None class TencentCloudSentenceCorrectionSuccessResponse(BaseTencentCloudSuccessResponse): @@ -172,10 +172,10 @@ class TencentCloudCategory(BaseTencentCloudModel): - Name: 分类中文名, 注意: 此字段可能返回 null, 表示取不到有效值 - Score: 分类置信度, 注意: 此字段可能返回 null, 表示取不到有效值 """ - Id: Optional[int] = None - Label: Optional[str] = None - Name: Optional[str] = None - Score: Optional[float] = None + Id: int | None = None + Label: str | None = None + Name: str | None = None + Score: float | None = None class TencentCloudClassifyContentSuccessResponse(BaseTencentCloudSuccessResponse): diff --git a/src/utils/tencent_cloud_api/model/tmt.py b/src/utils/tencent_cloud_api/model/tmt.py index 690b9b32..1b26444d 100644 --- a/src/utils/tencent_cloud_api/model/tmt.py +++ b/src/utils/tencent_cloud_api/model/tmt.py @@ -10,8 +10,8 @@ from .base_model import ( BaseTencentCloudErrorResponse, - BaseTencentCloudSuccessResponse, BaseTencentCloudResponse, + BaseTencentCloudSuccessResponse, ) diff --git a/src/utils/weibo_api/exception.py b/src/utils/weibo_api/exception.py deleted file mode 100644 index c0aa3cc3..00000000 --- a/src/utils/weibo_api/exception.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -@Author : Ailitonia -@Date : 2023/2/3 23:48 -@FileName : exception -@Project : nonebot2_miya -@Description : Weibo exception -@GitHub : https://github.com/Ailitonia -@Software : PyCharm -""" - -from src.exception import WebSourceException - - -class BaseWeiboError(WebSourceException): - """Weibo 异常基类""" - - -class WeiboApiError(BaseWeiboError): - """Api 返回错误""" - - -__all__ = [ - 'WeiboApiError', -] diff --git a/src/utils/weibo_api/main.py b/src/utils/weibo_api/main.py index 7d8d491c..8f5f4f95 100644 --- a/src/utils/weibo_api/main.py +++ b/src/utils/weibo_api/main.py @@ -10,17 +10,17 @@ from typing import TYPE_CHECKING, Any -from src.utils.common_api import BaseCommonAPI +from src.exception import WebSourceException +from src.utils import BaseCommonAPI from .config import weibo_resource_config -from .exception import WeiboApiError from .helper import parse_weibo_card_from_status_page from .model import ( WeiboCard, WeiboCards, WeiboCardStatus, WeiboExtend, - WeiboRealtimeHotCard, WeiboRealtimeHot, + WeiboRealtimeHotCard, WeiboUserBase, WeiboUserInfo, ) @@ -64,7 +64,7 @@ async def download_resource( *, subdir: str | None = None, ignore_exist_file: bool = False - ) -> "TemporaryResource": + ) -> 'TemporaryResource': """下载任意资源到本地, 保持原始文件名, 直接覆盖同名文件""" return await cls._download_resource( save_folder=weibo_resource_config.default_download_folder, @@ -87,7 +87,7 @@ async def query_user_data(cls, uid: int | str) -> WeiboUserBase: user_info = WeiboUserInfo.model_validate(user_response) if user_info.ok != 1: - raise WeiboApiError(f'Query user(uid={uid}) data failed, {user_info.data}') + raise WebSourceException(404, f'Query user(uid={uid}) data failed, {user_info.data}') return user_info.data.userInfo @@ -114,7 +114,7 @@ async def query_user_weibo_cards(cls, uid: int | str, since_id: int | str | None cards = WeiboCards.model_validate(cards_response) if cards.ok != 1: - raise WeiboApiError(f'Query user(uid={uid}) weibo cards failed, {cards.data}') + raise WebSourceException(404, f'Query user(uid={uid}) weibo cards failed, {cards.data}') return cards.data.cards @@ -137,7 +137,7 @@ async def query_weibo_extend_text(cls, mid: int | str) -> str: extend = WeiboExtend.model_validate(extend_response) if extend.ok != 1 or extend.data.ok != 1: - raise WeiboApiError(f'Query weibo(mid={mid}) extend content failed, {extend}') + raise WebSourceException(404, f'Query weibo(mid={mid}) extend content failed, {extend}') return extend.data.longTextContent @@ -154,7 +154,7 @@ async def query_realtime_hot(cls) -> list[WeiboRealtimeHotCard]: realtime_hot = WeiboRealtimeHot.model_validate(realtime_hot_response) if realtime_hot.ok != 1: - raise WeiboApiError(f'Query realtime hot failed, {realtime_hot.data}') + raise WebSourceException(404, f'Query realtime hot failed, {realtime_hot.data}') return realtime_hot.data.cards diff --git a/src/utils/weibo_api/model.py b/src/utils/weibo_api/model.py index bf9be03f..d08c29da 100644 --- a/src/utils/weibo_api/model.py +++ b/src/utils/weibo_api/model.py @@ -9,7 +9,7 @@ """ from datetime import datetime -from typing import Optional, Literal +from typing import Literal, Optional from lxml import etree from pydantic import BaseModel, ConfigDict, field_validator, model_validator @@ -58,7 +58,7 @@ class _UserData(WeiboBaseModel): profile_ext: str scheme: AnyUrl showAppTips: int - tabsInfo: Optional[dict] = None + tabsInfo: dict | None = None userInfo: WeiboUserBase @@ -100,26 +100,26 @@ class _MblogPic(WeiboBaseModel): class _PagePic(WeiboBaseModel): """page_info.page_pic model""" url: AnyUrl - pid: Optional[str] = None - source: Optional[int] = None - is_self_cover: Optional[int] = None - type: Optional[int] = None - width: Optional[int | str] = None - height: Optional[int | str] = None + pid: str | None = None + source: int | None = None + is_self_cover: int | None = None + type: int | None = None + width: int | str | None = None + height: int | str | None = None class _PageInfo(WeiboBaseModel): """page_info model""" type: str - object_type: Optional[str] = None + object_type: str | None = None page_pic: _PagePic - page_url: Optional[AnyUrl] = None - page_title: Optional[str] = None - title: Optional[str] = None - content1: Optional[str] = None - content2: Optional[str] = None - url_ori: Optional[AnyUrl] = None - object_id: Optional[str] = None + page_url: AnyUrl | None = None + page_title: str | None = None + title: str | None = None + content1: str | None = None + content2: str | None = None + url_ori: AnyUrl | None = None + object_id: str | None = None @property def pic_url(self) -> AnyUrl: @@ -133,19 +133,19 @@ class _WeiboCardMbLog(WeiboBaseModel): id: int mid: str can_edit: bool - show_additional_indication: Optional[int] = None + show_additional_indication: int | None = None text: str - textLength: Optional[int] = None + textLength: int | None = None source: str favorited: bool pic_ids: list[str] - thumbnail_pic: Optional[AnyUrl] = None - bmiddle_pic: Optional[AnyUrl] = None - original_pic: Optional[AnyUrl] = None + thumbnail_pic: AnyUrl | None = None + bmiddle_pic: AnyUrl | None = None + original_pic: AnyUrl | None = None is_paid: bool mblog_vip_type: int user: WeiboUserBase - retweeted_status: Optional["_WeiboCardMbLog"] = None + retweeted_status: Optional['_WeiboCardMbLog'] = None reposts_count: int comments_count: int reprint_cmt_count: int @@ -154,21 +154,21 @@ class _WeiboCardMbLog(WeiboBaseModel): isLongText: bool mlevel: int show_mlevel: int - darwin_tags: Optional[list] = None - hot_page: Optional[dict] = None - mblogtype: Optional[int] = None - rid: Optional[str] = None - extern_safe: Optional[int] = None - number_display_strategy: Optional[dict] = None - content_auth: Optional[int] = None - comment_manage_info: Optional[dict] = None + darwin_tags: list | None = None + hot_page: dict | None = None + mblogtype: int | None = None + rid: str | None = None + extern_safe: int | None = None + number_display_strategy: dict | None = None + content_auth: int | None = None + comment_manage_info: dict | None = None pic_num: int - new_comment_style: Optional[int] = None - region_name: Optional[str] = None - region_opt: Optional[int] = None - page_info: Optional[_PageInfo] = None - edit_config: Optional[dict] = None - pics: Optional[list[_MblogPic]] = None + new_comment_style: int | None = None + region_name: str | None = None + region_opt: int | None = None + page_info: _PageInfo | None = None + edit_config: dict | None = None + pics: list[_MblogPic] | None = None bid: str @field_validator('text') @@ -220,9 +220,9 @@ class WeiboCard(WeiboBaseModel): """单条微博内容(data.cards.card model)""" card_type: str mblog: _WeiboCardMbLog - itemid: Optional[str] = None - profile_type_id: Optional[str] = None - scheme: Optional[str] = None + itemid: str | None = None + profile_type_id: str | None = None + scheme: str | None = None class _CardListInfo(WeiboBaseModel): @@ -284,53 +284,53 @@ class _HotCardlistInfo(WeiboBaseModel): """realtime hot data.cardlistInfo""" starttime: int can_shared: int - cardlist_menus: Optional[list] = None + cardlist_menus: list | None = None config: dict page_type: str cardlist_head_cards: list[dict] - enable_load_imge_scrolling: Optional[int] = None + enable_load_imge_scrolling: int | None = None nick: str page_title: str search_request_id: str v_p: str containerid: str refresh_configs: dict - headbg_animation: Optional[str] = None + headbg_animation: str | None = None total: int page_size: int select_id: str title_top: str show_style: int - page: Optional[int] = None + page: int | None = None class _HotCardGroup(WeiboBaseModel): """realtime hot data.cards.card_group""" card_type: int - icon: Optional[AnyUrl] = None - icon_height: Optional[int] = None - icon_width: Optional[int] = None - itemid: Optional[str] = None - pic: Optional[AnyUrl] = None - desc: Optional[str] = None - desc_extr: Optional[str] = None - actionlog: Optional[dict] = None + icon: AnyUrl | None = None + icon_height: int | None = None + icon_width: int | None = None + itemid: str | None = None + pic: AnyUrl | None = None + desc: str | None = None + desc_extr: str | None = None + actionlog: dict | None = None scheme: AnyUrl - display_arrow: Optional[int] = None - is_show_arrow: Optional[int] = None - left_tag_img: Optional[AnyUrl] = None - title: Optional[str] = None - title_sub: Optional[str] = None - sub_title: Optional[str] = None + display_arrow: int | None = None + is_show_arrow: int | None = None + left_tag_img: AnyUrl | None = None + title: str | None = None + title_sub: str | None = None + sub_title: str | None = None class WeiboRealtimeHotCard(WeiboBaseModel): """realtime hot data.cards""" - itemid: Optional[str] = None + itemid: str | None = None card_group: list[_HotCardGroup] show_type: int card_type: int - title: Optional[str] = None + title: str | None = None class _RealtimeHotData(WeiboBaseModel): diff --git a/src/utils/zip_utils/__init__.py b/src/utils/zip_utils/__init__.py index 9a125d5c..3bcb45fd 100644 --- a/src/utils/zip_utils/__init__.py +++ b/src/utils/zip_utils/__init__.py @@ -10,7 +10,7 @@ import pathlib import zipfile -from typing import Sequence +from collections.abc import Sequence import py7zr from nonebot.utils import run_sync @@ -19,7 +19,7 @@ from .config import zip_utils_config, zip_utils_resource_config -class ZipUtils(object): +class ZipUtils: def __init__(self, file_name: str, *, folder: TemporaryResource | None = None): if folder is not None and folder.is_dir: storage_folder: TemporaryResource = folder diff --git a/static/docs/fortune/event.json b/static/docs/fortune/event.json index 6f90570b..2f4e15e9 100644 --- a/static/docs/fortune/event.json +++ b/static/docs/fortune/event.json @@ -30,7 +30,7 @@ "bad": "吃土中" }, { - "name": "吃人", + "name": "吃(招)人", "good": "你面前这位有成为神龙的潜质", "bad": "这人会用Aegisub吗?" }, @@ -139,6 +139,11 @@ "good": "迎接第一缕阳光", "bad": "才4点,再睡一会" }, + { + "name": "早起", + "good": "早起的鸟儿有虫吃", + "bad": "早起的虫子被鸟吃" + }, { "name": "早睡", "good": "第二天精神饱满", @@ -224,6 +229,11 @@ "good": "新工作待遇大幅提升", "bad": "待遇还不如之前的" }, + { + "name": "提交辞职申请", + "good": "公司找到了一个更能干更便宜的家伙", + "bad": "你的下一份工作未必比现在强" + }, { "name": "和女神聊天", "good": "今天天气不错", @@ -234,6 +244,11 @@ "good": "今天北斗七星汇聚,裤子造的又快又好", "bad": "写好会发现github上已经有了更好的" }, + { + "name": "写超过5行的方法", + "good": "你的代码组织的很好,长一点没关系", + "bad": "你的代码将混乱不堪,你自己都看不懂" + }, { "name": "给测试妹子埋个bug", "good": "下辈子的幸福就靠这个bug了", @@ -394,6 +409,11 @@ "good": "被精准戳中性癖", "bad": "更新的全是你不喜欢的类型" }, + { + "name": "升级传统手艺", + "good": "避免缓冲区溢出", + "bad": "强撸灰飞烟灭" + }, { "name": "前往女仆咖啡厅", "good": "感受身心上的治愈", @@ -528,5 +548,125 @@ "name": "出门", "good": "今天会是个好天气", "bad": "中途突降暴雨" + }, + { + "name": "加班", + "good": "让你的简历更加丰富", + "bad": "让你的发际线更加丰富" + }, + { + "name": "开会", + "good": "开会是团队协作的润滑剂", + "bad": "开会是时间管理的滑铁卢" + }, + { + "name": "点外卖", + "good": "今天优惠券还挺多", + "bad": "选择困难,浪费你的午休时间" + }, + { + "name": "使用优惠券", + "good": "让你的钱包更丰满", + "bad": "让你的选择更骨感" + }, + { + "name": "网购", + "good": "让你的购物车满载而归", + "bad": "让你的信用卡满载而归" + }, + { + "name": "刷短视频", + "good": "创作的灵感源源不断", + "bad": "就再看一条……怎么就3点了?" + }, + { + "name": "写代码", + "good": "代码写得好,Bug自然少", + "bad": "" + }, + { + "name": "调试", + "good": "调试是程序员的修行", + "bad": "调试是程序员的炼狱" + }, + { + "name": "技术讨论", + "good": "增长见识", + "bad": "增长争执" + }, + { + "name": "使用版本控制系统", + "good": "让历史有迹可悔", + "bad": "让每一处冲突都痛彻心扉" + }, + { + "name": "处理用户反馈", + "good": "产品改进的指南针", + "bad": "给产品崩溃的油门一脚踩到底" + }, + { + "name": "代码复用", + "good": "提高开发效率", + "bad": "提高Bug复现率" + }, + { + "name": "图书馆自习", + "good": "知识的海洋", + "bad": "补觉的天堂" + }, + { + "name": "小组讨论", + "good": "集思广益,团队合作", + "bad": "提前体验项目经理的牛马日常" + }, + { + "name": "考试周", + "good": "临阵磨枪,平稳过线", + "bad": "临时抱佛脚,佛踢你一脚" + }, + { + "name": "尝试新餐厅", + "good": "探索新口味,好像还不错", + "bad": "踩雷高分榜,钱包和胃都受伤" + }, + { + "name": "吃轻食", + "good": "营养均衡,适合追求健康的你", + "bad": "你健康了,但你的钱包没有" + }, + { + "name": "喝咖啡", + "good": "提神醒脑,效率翻倍", + "bad": "咖啡因过量,晚上失眠" + }, + { + "name": "健身", + "good": "锻炼身体,保持活力", + "bad": "过度运动,肌肉酸痛" + }, + { + "name": "出门逛逛", + "good": "购物放松,发现新乐趣", + "bad": "冲动消费,月底吃土" + }, + { + "name": "烹饪", + "good": "自己动手,享受美食", + "bad": "这怎么和教程上的颜色不一样?" + }, + { + "name": "旅行", + "good": "短途旅行,放松身心", + "bad": "长途跋涉,身心俱疲" + }, + { + "name": "抚摸猫咪", + "good": "温柔抚摸,增进感情", + "bad": "用力过猛,咬你一口" + }, + { + "name": "游泳", + "good": "享受水中的自由", + "bad": "水温过低,导致抽筋" } ] \ No newline at end of file diff --git a/static/docs/wordcloud/stop_words/baidu_stop_words.txt b/static/docs/wordcloud/stop_words/baidu_stop_words.txt new file mode 100644 index 00000000..7bf86ad3 --- /dev/null +++ b/static/docs/wordcloud/stop_words/baidu_stop_words.txt @@ -0,0 +1,1396 @@ +-- +? +“ +” +》 +-- +able +about +above +according +accordingly +across +actually +after +afterwards +again +against +ain't +all +allow +allows +almost +alone +along +already +also +although +always +am +among +amongst +an +and +another +any +anybody +anyhow +anyone +anything +anyway +anyways +anywhere +apart +appear +appreciate +appropriate +are +aren't +around +as +a's +aside +ask +asking +associated +at +available +away +awfully +be +became +because +become +becomes +becoming +been +before +beforehand +behind +being +believe +below +beside +besides +best +better +between +beyond +both +brief +but +by +came +can +cannot +cant +can't +cause +causes +certain +certainly +changes +clearly +c'mon +co +com +come +comes +concerning +consequently +consider +considering +contain +containing +contains +corresponding +could +couldn't +course +c's +currently +definitely +described +despite +did +didn't +different +do +does +doesn't +doing +done +don't +down +downwards +during +each +edu +eg +eight +either +else +elsewhere +enough +entirely +especially +et +etc +even +ever +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +far +few +fifth +first +five +followed +following +follows +for +former +formerly +forth +four +from +further +furthermore +get +gets +getting +given +gives +go +goes +going +gone +got +gotten +greetings +had +hadn't +happens +hardly +has +hasn't +have +haven't +having +he +hello +help +hence +her +here +hereafter +hereby +herein +here's +hereupon +hers +herself +he's +hi +him +himself +his +hither +hopefully +how +howbeit +however +i'd +ie +if +ignored +i'll +i'm +immediate +in +inasmuch +inc +indeed +indicate +indicated +indicates +inner +insofar +instead +into +inward +is +isn't +it +it'd +it'll +its +it's +itself +i've +just +keep +keeps +kept +know +known +knows +last +lately +later +latter +latterly +least +less +lest +let +let's +like +liked +likely +little +look +looking +looks +ltd +mainly +many +may +maybe +me +mean +meanwhile +merely +might +more +moreover +most +mostly +much +must +my +myself +name +namely +nd +near +nearly +necessary +need +needs +neither +never +nevertheless +new +next +nine +no +nobody +non +none +noone +nor +normally +not +nothing +novel +now +nowhere +obviously +of +off +often +oh +ok +okay +old +on +once +one +ones +only +onto +or +other +others +otherwise +ought +our +ours +ourselves +out +outside +over +overall +own +particular +particularly +per +perhaps +placed +please +plus +possible +presumably +probably +provides +que +quite +qv +rather +rd +re +really +reasonably +regarding +regardless +regards +relatively +respectively +right +said +same +saw +say +saying +says +second +secondly +see +seeing +seem +seemed +seeming +seems +seen +self +selves +sensible +sent +serious +seriously +seven +several +shall +she +should +shouldn't +since +six +so +some +somebody +somehow +someone +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specified +specify +specifying +still +sub +such +sup +sure +take +taken +tell +tends +th +than +thank +thanks +thanx +that +thats +that's +the +their +theirs +them +themselves +then +thence +there +thereafter +thereby +therefore +therein +theres +there's +thereupon +these +they +they'd +they'll +they're +they've +think +third +this +thorough +thoroughly +those +though +three +through +throughout +thru +thus +to +together +too +took +toward +towards +tried +tries +truly +try +trying +t's +twice +two +un +under +unfortunately +unless +unlikely +until +unto +up +upon +us +use +used +useful +uses +using +usually +value +various +very +via +viz +vs +want +wants +was +wasn't +way +we +we'd +welcome +well +we'll +went +were +we're +weren't +we've +what +whatever +what's +when +whence +whenever +where +whereafter +whereas +whereby +wherein +where's +whereupon +wherever +whether +which +while +whither +who +whoever +whole +whom +who's +whose +why +will +willing +wish +with +within +without +wonder +won't +would +wouldn't +yes +yet +you +you'd +you'll +your +you're +yours +yourself +yourselves +you've +zero +zt +ZT +zz +ZZ +一 +一下 +一些 +一切 +一则 +一天 +一定 +一方面 +一旦 +一时 +一来 +一样 +一次 +一片 +一直 +一致 +一般 +一起 +一边 +一面 +万一 +上下 +上升 +上去 +上来 +上述 +上面 +下列 +下去 +下来 +下面 +不一 +不久 +不仅 +不会 +不但 +不光 +不单 +不变 +不只 +不可 +不同 +不够 +不如 +不得 +不怕 +不惟 +不成 +不拘 +不敢 +不断 +不是 +不比 +不然 +不特 +不独 +不管 +不能 +不要 +不论 +不足 +不过 +不问 +与 +与其 +与否 +与此同时 +专门 +且 +两者 +严格 +严重 +个 +个人 +个别 +中小 +中间 +丰富 +临 +为 +为主 +为了 +为什么 +为什麽 +为何 +为着 +主张 +主要 +举行 +乃 +乃至 +么 +之 +之一 +之前 +之后 +之後 +之所以 +之类 +乌乎 +乎 +乘 +也 +也好 +也是 +也罢 +了 +了解 +争取 +于 +于是 +于是乎 +云云 +互相 +产生 +人们 +人家 +什么 +什么样 +什麽 +今后 +今天 +今年 +今後 +仍然 +从 +从事 +从而 +他 +他人 +他们 +他的 +代替 +以 +以上 +以下 +以为 +以便 +以免 +以前 +以及 +以后 +以外 +以後 +以来 +以至 +以至于 +以致 +们 +任 +任何 +任凭 +任务 +企图 +伟大 +似乎 +似的 +但 +但是 +何 +何况 +何处 +何时 +作为 +你 +你们 +你的 +使得 +使用 +例如 +依 +依照 +依靠 +促进 +保持 +俺 +俺们 +倘 +倘使 +倘或 +倘然 +倘若 +假使 +假如 +假若 +做到 +像 +允许 +充分 +先后 +先後 +先生 +全部 +全面 +兮 +共同 +关于 +其 +其一 +其中 +其二 +其他 +其余 +其它 +其实 +其次 +具体 +具体地说 +具体说来 +具有 +再者 +再说 +冒 +冲 +决定 +况且 +准备 +几 +几乎 +几时 +凭 +凭借 +出去 +出来 +出现 +分别 +则 +别 +别的 +别说 +到 +前后 +前者 +前进 +前面 +加之 +加以 +加入 +加强 +十分 +即 +即令 +即使 +即便 +即或 +即若 +却不 +原来 +又 +及 +及其 +及时 +及至 +双方 +反之 +反应 +反映 +反过来 +反过来说 +取得 +受到 +变成 +另 +另一方面 +另外 +只是 +只有 +只要 +只限 +叫 +叫做 +召开 +叮咚 +可 +可以 +可是 +可能 +可见 +各 +各个 +各人 +各位 +各地 +各种 +各级 +各自 +合理 +同 +同一 +同时 +同样 +后来 +后面 +向 +向着 +吓 +吗 +否则 +吧 +吧哒 +吱 +呀 +呃 +呕 +呗 +呜 +呜呼 +呢 +周围 +呵 +呸 +呼哧 +咋 +和 +咚 +咦 +咱 +咱们 +咳 +哇 +哈 +哈哈 +哉 +哎 +哎呀 +哎哟 +哗 +哟 +哦 +哩 +哪 +哪个 +哪些 +哪儿 +哪天 +哪年 +哪怕 +哪样 +哪边 +哪里 +哼 +哼唷 +唉 +啊 +啐 +啥 +啦 +啪达 +喂 +喏 +喔唷 +嗡嗡 +嗬 +嗯 +嗳 +嘎 +嘎登 +嘘 +嘛 +嘻 +嘿 +因 +因为 +因此 +因而 +固然 +在 +在下 +地 +坚决 +坚持 +基本 +处理 +复杂 +多 +多少 +多数 +多次 +大力 +大多数 +大大 +大家 +大批 +大约 +大量 +失去 +她 +她们 +她的 +好的 +好象 +如 +如上所述 +如下 +如何 +如其 +如果 +如此 +如若 +存在 +宁 +宁可 +宁愿 +宁肯 +它 +它们 +它们的 +它的 +安全 +完全 +完成 +实现 +实际 +宣布 +容易 +密切 +对 +对于 +对应 +将 +少数 +尔后 +尚且 +尤其 +就 +就是 +就是说 +尽 +尽管 +属于 +岂但 +左右 +巨大 +巩固 +己 +已经 +帮助 +常常 +并 +并不 +并不是 +并且 +并没有 +广大 +广泛 +应当 +应用 +应该 +开外 +开始 +开展 +引起 +强烈 +强调 +归 +当 +当前 +当时 +当然 +当着 +形成 +彻底 +彼 +彼此 +往 +往往 +待 +後来 +後面 +得 +得出 +得到 +心里 +必然 +必要 +必须 +怎 +怎么 +怎么办 +怎么样 +怎样 +怎麽 +总之 +总是 +总的来看 +总的来说 +总的说来 +总结 +总而言之 +恰恰相反 +您 +意思 +愿意 +慢说 +成为 +我 +我们 +我的 +或 +或是 +或者 +战斗 +所 +所以 +所有 +所谓 +打 +扩大 +把 +抑或 +拿 +按 +按照 +换句话说 +换言之 +据 +掌握 +接着 +接著 +故 +故此 +整个 +方便 +方面 +旁人 +无宁 +无法 +无论 +既 +既是 +既然 +时候 +明显 +明确 +是 +是否 +是的 +显然 +显著 +普通 +普遍 +更加 +曾经 +替 +最后 +最大 +最好 +最後 +最近 +最高 +有 +有些 +有关 +有利 +有力 +有所 +有效 +有时 +有点 +有的 +有着 +有著 +望 +朝 +朝着 +本 +本着 +来 +来着 +极了 +构成 +果然 +果真 +某 +某个 +某些 +根据 +根本 +欢迎 +正在 +正如 +正常 +此 +此外 +此时 +此间 +毋宁 +每 +每个 +每天 +每年 +每当 +比 +比如 +比方 +比较 +毫不 +没有 +沿 +沿着 +注意 +深入 +清楚 +满足 +漫说 +焉 +然则 +然后 +然後 +然而 +照 +照着 +特别是 +特殊 +特点 +现代 +现在 +甚么 +甚而 +甚至 +用 +由 +由于 +由此可见 +的 +的话 +目前 +直到 +直接 +相似 +相信 +相反 +相同 +相对 +相对而言 +相应 +相当 +相等 +省得 +看出 +看到 +看来 +看看 +看见 +真是 +真正 +着 +着呢 +矣 +知道 +确定 +离 +积极 +移动 +突出 +突然 +立即 +第 +等 +等等 +管 +紧接着 +纵 +纵令 +纵使 +纵然 +练习 +组成 +经 +经常 +经过 +结合 +结果 +给 +绝对 +继续 +继而 +维持 +综上所述 +罢了 +考虑 +者 +而 +而且 +而况 +而外 +而已 +而是 +而言 +联系 +能 +能否 +能够 +腾 +自 +自个儿 +自从 +自各儿 +自家 +自己 +自身 +至 +至于 +良好 +若 +若是 +若非 +范围 +莫若 +获得 +虽 +虽则 +虽然 +虽说 +行为 +行动 +表明 +表示 +被 +要 +要不 +要不是 +要不然 +要么 +要是 +要求 +规定 +觉得 +认为 +认真 +认识 +让 +许多 +论 +设使 +设若 +该 +说明 +诸位 +谁 +谁知 +赶 +起 +起来 +起见 +趁 +趁着 +越是 +跟 +转动 +转变 +转贴 +较 +较之 +边 +达到 +迅速 +过 +过去 +过来 +运用 +还是 +还有 +这 +这个 +这么 +这么些 +这么样 +这么点儿 +这些 +这会儿 +这儿 +这就是说 +这时 +这样 +这点 +这种 +这边 +这里 +这麽 +进入 +进步 +进而 +进行 +连 +连同 +适应 +适当 +适用 +逐步 +逐渐 +通常 +通过 +造成 +遇到 +遭到 +避免 +那 +那个 +那么 +那么些 +那么样 +那些 +那会儿 +那儿 +那时 +那样 +那边 +那里 +那麽 +部分 +鄙人 +采取 +里面 +重大 +重新 +重要 +鉴于 +问题 +防止 +阿 +附近 +限制 +除 +除了 +除此之外 +除非 +随 +随着 +随著 +集中 +需要 +非但 +非常 +非徒 +靠 +顺 +顺着 +首先 +高兴 +是不是 +说说 + diff --git a/static/docs/wordcloud/stop_words/cn_stop_words.txt b/static/docs/wordcloud/stop_words/cn_stop_words.txt new file mode 100644 index 00000000..4210f280 --- /dev/null +++ b/static/docs/wordcloud/stop_words/cn_stop_words.txt @@ -0,0 +1,746 @@ +$ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +? +_ +“ +” +、 +。 +《 +》 +一 +一些 +一何 +一切 +一则 +一方面 +一旦 +一来 +一样 +一般 +一转眼 +万一 +上 +上下 +下 +不 +不仅 +不但 +不光 +不单 +不只 +不外乎 +不如 +不妨 +不尽 +不尽然 +不得 +不怕 +不惟 +不成 +不拘 +不料 +不是 +不比 +不然 +不特 +不独 +不管 +不至于 +不若 +不论 +不过 +不问 +与 +与其 +与其说 +与否 +与此同时 +且 +且不说 +且说 +两者 +个 +个别 +临 +为 +为了 +为什么 +为何 +为止 +为此 +为着 +乃 +乃至 +乃至于 +么 +之 +之一 +之所以 +之类 +乌乎 +乎 +乘 +也 +也好 +也罢 +了 +二来 +于 +于是 +于是乎 +云云 +云尔 +些 +亦 +人 +人们 +人家 +什么 +什么样 +今 +介于 +仍 +仍旧 +从 +从此 +从而 +他 +他人 +他们 +以 +以上 +以为 +以便 +以免 +以及 +以故 +以期 +以来 +以至 +以至于 +以致 +们 +任 +任何 +任凭 +似的 +但 +但凡 +但是 +何 +何以 +何况 +何处 +何时 +余外 +作为 +你 +你们 +使 +使得 +例如 +依 +依据 +依照 +便于 +俺 +俺们 +倘 +倘使 +倘或 +倘然 +倘若 +借 +假使 +假如 +假若 +傥然 +像 +儿 +先不先 +光是 +全体 +全部 +兮 +关于 +其 +其一 +其中 +其二 +其他 +其余 +其它 +其次 +具体地说 +具体说来 +兼之 +内 +再 +再其次 +再则 +再有 +再者 +再者说 +再说 +冒 +冲 +况且 +几 +几时 +凡 +凡是 +凭 +凭借 +出于 +出来 +分别 +则 +则甚 +别 +别人 +别处 +别是 +别的 +别管 +别说 +到 +前后 +前此 +前者 +加之 +加以 +即 +即令 +即使 +即便 +即如 +即或 +即若 +却 +去 +又 +又及 +及 +及其 +及至 +反之 +反而 +反过来 +反过来说 +受到 +另 +另一方面 +另外 +另悉 +只 +只当 +只怕 +只是 +只有 +只消 +只要 +只限 +叫 +叮咚 +可 +可以 +可是 +可见 +各 +各个 +各位 +各种 +各自 +同 +同时 +后 +后者 +向 +向使 +向着 +吓 +吗 +否则 +吧 +吧哒 +吱 +呀 +呃 +呕 +呗 +呜 +呜呼 +呢 +呵 +呵呵 +呸 +呼哧 +咋 +和 +咚 +咦 +咧 +咱 +咱们 +咳 +哇 +哈 +哈哈 +哉 +哎 +哎呀 +哎哟 +哗 +哟 +哦 +哩 +哪 +哪个 +哪些 +哪儿 +哪天 +哪年 +哪怕 +哪样 +哪边 +哪里 +哼 +哼唷 +唉 +唯有 +啊 +啐 +啥 +啦 +啪达 +啷当 +喂 +喏 +喔唷 +喽 +嗡 +嗡嗡 +嗬 +嗯 +嗳 +嘎 +嘎登 +嘘 +嘛 +嘻 +嘿 +嘿嘿 +因 +因为 +因了 +因此 +因着 +因而 +固然 +在 +在下 +在于 +地 +基于 +处在 +多 +多么 +多少 +大 +大家 +她 +她们 +好 +如 +如上 +如上所述 +如下 +如何 +如其 +如同 +如是 +如果 +如此 +如若 +始而 +孰料 +孰知 +宁 +宁可 +宁愿 +宁肯 +它 +它们 +对 +对于 +对待 +对方 +对比 +将 +小 +尔 +尔后 +尔尔 +尚且 +就 +就是 +就是了 +就是说 +就算 +就要 +尽 +尽管 +尽管如此 +岂但 +己 +已 +已矣 +巴 +巴巴 +并 +并且 +并非 +庶乎 +庶几 +开外 +开始 +归 +归齐 +当 +当地 +当然 +当着 +彼 +彼时 +彼此 +往 +待 +很 +得 +得了 +怎 +怎么 +怎么办 +怎么样 +怎奈 +怎样 +总之 +总的来看 +总的来说 +总的说来 +总而言之 +恰恰相反 +您 +惟其 +慢说 +我 +我们 +或 +或则 +或是 +或曰 +或者 +截至 +所 +所以 +所在 +所幸 +所有 +才 +才能 +打 +打从 +把 +抑或 +拿 +按 +按照 +换句话说 +换言之 +据 +据此 +接着 +故 +故此 +故而 +旁人 +无 +无宁 +无论 +既 +既往 +既是 +既然 +时候 +是 +是以 +是的 +曾 +替 +替代 +最 +有 +有些 +有关 +有及 +有时 +有的 +望 +朝 +朝着 +本 +本人 +本地 +本着 +本身 +来 +来着 +来自 +来说 +极了 +果然 +果真 +某 +某个 +某些 +某某 +根据 +欤 +正值 +正如 +正巧 +正是 +此 +此地 +此处 +此外 +此时 +此次 +此间 +毋宁 +每 +每当 +比 +比及 +比如 +比方 +没奈何 +沿 +沿着 +漫说 +焉 +然则 +然后 +然而 +照 +照着 +犹且 +犹自 +甚且 +甚么 +甚或 +甚而 +甚至 +甚至于 +用 +用来 +由 +由于 +由是 +由此 +由此可见 +的 +的确 +的话 +直到 +相对而言 +省得 +看 +眨眼 +着 +着呢 +矣 +矣乎 +矣哉 +离 +竟而 +第 +等 +等到 +等等 +简言之 +管 +类如 +紧接着 +纵 +纵令 +纵使 +纵然 +经 +经过 +结果 +给 +继之 +继后 +继而 +综上所述 +罢了 +者 +而 +而且 +而况 +而后 +而外 +而已 +而是 +而言 +能 +能否 +腾 +自 +自个儿 +自从 +自各儿 +自后 +自家 +自己 +自打 +自身 +至 +至于 +至今 +至若 +致 +般的 +若 +若夫 +若是 +若果 +若非 +莫不然 +莫如 +莫若 +虽 +虽则 +虽然 +虽说 +被 +要 +要不 +要不是 +要不然 +要么 +要是 +譬喻 +譬如 +让 +许多 +论 +设使 +设或 +设若 +诚如 +诚然 +该 +说来 +诸 +诸位 +诸如 +谁 +谁人 +谁料 +谁知 +贼死 +赖以 +赶 +起 +起见 +趁 +趁着 +越是 +距 +跟 +较 +较之 +边 +过 +还 +还是 +还有 +还要 +这 +这一来 +这个 +这么 +这么些 +这么样 +这么点儿 +这些 +这会儿 +这儿 +这就是说 +这时 +这样 +这次 +这般 +这边 +这里 +进而 +连 +连同 +逐步 +通过 +遵循 +遵照 +那 +那个 +那么 +那么些 +那么样 +那些 +那会儿 +那儿 +那时 +那样 +那般 +那边 +那里 +都 +鄙人 +鉴于 +针对 +阿 +除 +除了 +除外 +除开 +除此之外 +除非 +随 +随后 +随时 +随着 +难道说 +非但 +非徒 +非特 +非独 +靠 +顺 +顺着 +首先 +! +, +: +; +? diff --git a/static/docs/wordcloud/stop_words/default_stop_words.txt b/static/docs/wordcloud/stop_words/default_stop_words.txt new file mode 100644 index 00000000..0557dd62 --- /dev/null +++ b/static/docs/wordcloud/stop_words/default_stop_words.txt @@ -0,0 +1,1621 @@ +$ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +? +_ +“ +” +、 +。 +《 +》 +able +about +above +according +accordingly +across +actually +after +afterwards +again +against +ain't +all +allow +allows +almost +alone +along +already +also +although +always +am +among +amongst +an +and +another +any +anybody +anyhow +anyone +anything +anyway +anyways +anywhere +apart +appear +appreciate +appropriate +are +aren't +around +as +a's +aside +ask +asking +associated +at +available +away +awfully +be +became +because +become +becomes +becoming +been +before +beforehand +behind +being +believe +below +beside +besides +best +better +between +beyond +both +brief +but +by +came +can +cannot +cant +can't +cause +causes +certain +certainly +changes +clearly +c'mon +co +com +come +comes +concerning +consequently +consider +considering +contain +containing +contains +corresponding +could +couldn't +course +c's +currently +definitely +described +despite +did +didn't +different +do +does +doesn't +doing +done +don't +down +downwards +during +each +edu +eg +eight +either +else +elsewhere +enough +entirely +especially +et +etc +even +ever +every +everybody +everyone +everything +everywhere +ex +exactly +example +except +far +few +fifth +first +five +followed +following +follows +for +former +formerly +forth +four +from +further +furthermore +get +gets +getting +given +gives +go +goes +going +gone +got +gotten +greetings +had +hadn't +happens +hardly +has +hasn't +have +haven't +having +he +hello +help +hence +her +here +hereafter +hereby +herein +here's +hereupon +hers +herself +he's +hi +him +himself +his +hither +hopefully +how +howbeit +however +i'd +ie +if +ignored +i'll +i'm +immediate +in +inasmuch +inc +indeed +indicate +indicated +indicates +inner +insofar +instead +into +inward +is +isn't +it +it'd +it'll +its +it's +itself +i've +just +keep +keeps +kept +know +known +knows +last +lately +later +latter +latterly +least +less +lest +let +let's +like +liked +likely +little +look +looking +looks +ltd +mainly +many +may +maybe +me +mean +meanwhile +merely +might +more +moreover +most +mostly +much +must +my +myself +name +namely +nd +near +nearly +necessary +need +needs +neither +never +nevertheless +new +next +nine +no +nobody +non +none +noone +nor +normally +not +nothing +novel +now +nowhere +obviously +of +off +often +oh +ok +okay +old +on +once +one +ones +only +onto +or +other +others +otherwise +ought +our +ours +ourselves +out +outside +over +overall +own +particular +particularly +per +perhaps +placed +please +plus +possible +presumably +probably +provides +que +quite +qv +rather +rd +re +really +reasonably +regarding +regardless +regards +relatively +respectively +right +said +same +saw +say +saying +says +second +secondly +see +seeing +seem +seemed +seeming +seems +seen +self +selves +sensible +sent +serious +seriously +seven +several +shall +she +should +shouldn't +since +six +so +some +somebody +somehow +someone +something +sometime +sometimes +somewhat +somewhere +soon +sorry +specified +specify +specifying +still +sub +such +sup +sure +take +taken +tell +tends +th +than +thank +thanks +thanx +that +thats +that's +the +their +theirs +them +themselves +then +thence +there +thereafter +thereby +therefore +therein +theres +there's +thereupon +these +they +they'd +they'll +they're +they've +think +third +this +thorough +thoroughly +those +though +three +through +throughout +thru +thus +to +together +too +took +toward +towards +tried +tries +truly +try +trying +t's +twice +two +un +under +unfortunately +unless +unlikely +until +unto +up +upon +us +use +used +useful +uses +using +usually +value +various +very +via +viz +vs +want +wants +was +wasn't +way +we +we'd +welcome +well +we'll +went +were +we're +weren't +we've +what +whatever +what's +when +whence +whenever +where +whereafter +whereas +whereby +wherein +where's +whereupon +wherever +whether +which +while +whither +who +whoever +whole +whom +who's +whose +why +will +willing +wish +with +within +without +wonder +won't +would +wouldn't +yes +yet +you +you'd +you'll +your +you're +yours +yourself +yourselves +you've +zero +zt +ZT +zz +ZZ +一 +一下 +一些 +一何 +一切 +一则 +一天 +一定 +一方面 +一旦 +一时 +一来 +一样 +一次 +一片 +一直 +一致 +一般 +一起 +一转眼 +一边 +一面 +万一 +上 +上下 +上升 +上去 +上来 +上述 +上面 +下 +下列 +下去 +下来 +下面 +不 +不一 +不久 +不仅 +不会 +不但 +不光 +不单 +不变 +不只 +不可 +不同 +不外乎 +不够 +不如 +不妨 +不尽 +不尽然 +不得 +不怕 +不惟 +不成 +不拘 +不敢 +不料 +不断 +不是 +不比 +不然 +不特 +不独 +不管 +不能 +不至于 +不若 +不要 +不论 +不足 +不过 +不问 +与 +与其 +与其说 +与否 +与此同时 +专门 +且 +且不说 +且说 +两者 +严格 +严重 +个 +个人 +个别 +中小 +中间 +丰富 +临 +为 +为主 +为了 +为什么 +为什麽 +为何 +为止 +为此 +为着 +主张 +主要 +举行 +乃 +乃至 +乃至于 +么 +之 +之一 +之前 +之后 +之後 +之所以 +之类 +乌乎 +乎 +乘 +也 +也好 +也是 +也罢 +了 +了解 +争取 +二来 +于 +于是 +于是乎 +云云 +云尔 +互相 +些 +亦 +产生 +人 +人们 +人家 +什么 +什么样 +什麽 +今 +今后 +今天 +今年 +今後 +介于 +仍 +仍旧 +仍然 +从 +从事 +从此 +从而 +他 +他人 +他们 +他的 +代替 +以 +以上 +以下 +以为 +以便 +以免 +以前 +以及 +以后 +以外 +以後 +以故 +以期 +以来 +以至 +以至于 +以致 +们 +任 +任何 +任凭 +任务 +企图 +伟大 +似乎 +似的 +但 +但凡 +但是 +何 +何以 +何况 +何处 +何时 +余外 +作为 +你 +你们 +你的 +使 +使得 +使用 +例如 +依 +依据 +依照 +依靠 +便于 +促进 +保持 +俺 +俺们 +倘 +倘使 +倘或 +倘然 +倘若 +借 +假使 +假如 +假若 +做到 +傥然 +像 +儿 +允许 +充分 +先不先 +先后 +先後 +先生 +光是 +全体 +全部 +全面 +兮 +共同 +关于 +其 +其一 +其中 +其二 +其他 +其余 +其它 +其实 +其次 +具体 +具体地说 +具体说来 +具有 +兼之 +内 +再 +再其次 +再则 +再有 +再者 +再者说 +再说 +冒 +冲 +决定 +况且 +准备 +几 +几乎 +几时 +凡 +凡是 +凭 +凭借 +出于 +出去 +出来 +出现 +分别 +则 +则甚 +别 +别人 +别处 +别是 +别的 +别管 +别说 +到 +前后 +前此 +前者 +前进 +前面 +加之 +加以 +加入 +加强 +十分 +即 +即令 +即使 +即便 +即如 +即或 +即若 +却 +却不 +原来 +去 +又 +又及 +及 +及其 +及时 +及至 +双方 +反之 +反应 +反映 +反而 +反过来 +反过来说 +取得 +受到 +变成 +另 +另一方面 +另外 +另悉 +只 +只当 +只怕 +只是 +只有 +只消 +只要 +只限 +叫 +叫做 +召开 +叮咚 +可 +可以 +可是 +可能 +可见 +各 +各个 +各人 +各位 +各地 +各种 +各级 +各自 +合理 +同 +同一 +同时 +同样 +后 +后来 +后者 +后面 +向 +向使 +向着 +吓 +吗 +否则 +吧 +吧哒 +吱 +呀 +呃 +呕 +呗 +呜 +呜呼 +呢 +周围 +呵 +呵呵 +呸 +呼哧 +咋 +和 +咚 +咦 +咧 +咱 +咱们 +咳 +哇 +哈 +哈哈 +哉 +哎 +哎呀 +哎哟 +哗 +哟 +哦 +哩 +哪 +哪个 +哪些 +哪儿 +哪天 +哪年 +哪怕 +哪样 +哪边 +哪里 +哼 +哼唷 +唉 +唯有 +啊 +啐 +啥 +啦 +啪达 +啷当 +喂 +喏 +喔唷 +喽 +嗡 +嗡嗡 +嗬 +嗯 +嗳 +嘎 +嘎登 +嘘 +嘛 +嘻 +嘿 +嘿嘿 +因 +因为 +因了 +因此 +因着 +因而 +固然 +在 +在下 +在于 +地 +坚决 +坚持 +基于 +基本 +处在 +处理 +复杂 +多 +多么 +多少 +多数 +多次 +大 +大力 +大多数 +大大 +大家 +大批 +大约 +大量 +失去 +她 +她们 +她的 +好 +好的 +好象 +如 +如上 +如上所述 +如下 +如何 +如其 +如同 +如是 +如果 +如此 +如若 +始而 +存在 +孰料 +孰知 +宁 +宁可 +宁愿 +宁肯 +它 +它们 +它们的 +它的 +安全 +完全 +完成 +实现 +实际 +宣布 +容易 +密切 +对 +对于 +对应 +对待 +对方 +对比 +将 +小 +少数 +尔 +尔后 +尔尔 +尚且 +尤其 +就 +就是 +就是了 +就是说 +就算 +就要 +尽 +尽管 +尽管如此 +属于 +岂但 +左右 +巨大 +巩固 +己 +已 +已矣 +已经 +巴 +巴巴 +帮助 +常常 +并 +并不 +并不是 +并且 +并没有 +并非 +广大 +广泛 +应当 +应用 +应该 +庶乎 +庶几 +开外 +开始 +开展 +引起 +强烈 +强调 +归 +归齐 +当 +当前 +当地 +当时 +当然 +当着 +形成 +彻底 +彼 +彼时 +彼此 +往 +往往 +待 +很 +後来 +後面 +得 +得了 +得出 +得到 +心里 +必然 +必要 +必须 +怎 +怎么 +怎么办 +怎么样 +怎奈 +怎样 +怎麽 +总之 +总是 +总的来看 +总的来说 +总的说来 +总结 +总而言之 +恰恰相反 +您 +惟其 +意思 +愿意 +慢说 +成为 +我 +我们 +我的 +或 +或则 +或是 +或曰 +或者 +战斗 +截至 +所 +所以 +所在 +所幸 +所有 +所谓 +才 +才能 +打 +打从 +扩大 +把 +抑或 +拿 +按 +按照 +换句话说 +换言之 +据 +据此 +掌握 +接着 +接著 +故 +故此 +故而 +整个 +方便 +方面 +旁人 +无 +无宁 +无法 +无论 +既 +既往 +既是 +既然 +时候 +明显 +明确 +是 +是不是 +是以 +是否 +是的 +显然 +显著 +普通 +普遍 +更加 +曾 +曾经 +替 +替代 +最 +最后 +最大 +最好 +最後 +最近 +最高 +有 +有些 +有关 +有利 +有力 +有及 +有所 +有效 +有时 +有点 +有的 +有着 +有著 +望 +朝 +朝着 +本 +本人 +本地 +本着 +本身 +来 +来着 +来自 +来说 +极了 +构成 +果然 +果真 +某 +某个 +某些 +某某 +根据 +根本 +欢迎 +欤 +正值 +正在 +正如 +正巧 +正常 +正是 +此 +此地 +此处 +此外 +此时 +此次 +此间 +毋宁 +每 +每个 +每天 +每年 +每当 +比 +比及 +比如 +比方 +比较 +毫不 +没奈何 +没有 +沿 +沿着 +注意 +深入 +清楚 +满足 +漫说 +焉 +然则 +然后 +然後 +然而 +照 +照着 +特别是 +特殊 +特点 +犹且 +犹自 +现代 +现在 +甚且 +甚么 +甚或 +甚而 +甚至 +甚至于 +用 +用来 +由 +由于 +由是 +由此 +由此可见 +的 +的确 +的话 +目前 +直到 +直接 +相似 +相信 +相反 +相同 +相对 +相对而言 +相应 +相当 +相等 +省得 +看 +看出 +看到 +看来 +看看 +看见 +真是 +真正 +眨眼 +着 +着呢 +矣 +矣乎 +矣哉 +知道 +确定 +离 +积极 +移动 +突出 +突然 +立即 +竟而 +第 +等 +等到 +等等 +简言之 +管 +类如 +紧接着 +纵 +纵令 +纵使 +纵然 +练习 +组成 +经 +经常 +经过 +结合 +结果 +给 +绝对 +继之 +继后 +继续 +继而 +维持 +综上所述 +罢了 +考虑 +者 +而 +而且 +而况 +而后 +而外 +而已 +而是 +而言 +联系 +能 +能否 +能够 +腾 +自 +自个儿 +自从 +自各儿 +自后 +自家 +自己 +自打 +自身 +至 +至于 +至今 +至若 +致 +般的 +良好 +若 +若夫 +若是 +若果 +若非 +范围 +莫不然 +莫如 +莫若 +获得 +虽 +虽则 +虽然 +虽说 +行为 +行动 +表明 +表示 +被 +要 +要不 +要不是 +要不然 +要么 +要是 +要求 +规定 +觉得 +譬喻 +譬如 +认为 +认真 +认识 +让 +许多 +论 +设使 +设或 +设若 +诚如 +诚然 +该 +说明 +说来 +说说 +诸 +诸位 +诸如 +谁 +谁人 +谁料 +谁知 +贼死 +赖以 +赶 +起 +起来 +起见 +趁 +趁着 +越是 +距 +跟 +转动 +转变 +转贴 +较 +较之 +边 +达到 +迅速 +过 +过去 +过来 +运用 +还 +还是 +还有 +还要 +这 +这一来 +这个 +这么 +这么些 +这么样 +这么点儿 +这些 +这会儿 +这儿 +这就是说 +这时 +这样 +这次 +这点 +这种 +这般 +这边 +这里 +这麽 +进入 +进步 +进而 +进行 +连 +连同 +适应 +适当 +适用 +逐步 +逐渐 +通常 +通过 +造成 +遇到 +遭到 +遵循 +遵照 +避免 +那 +那个 +那么 +那么些 +那么样 +那些 +那会儿 +那儿 +那时 +那样 +那般 +那边 +那里 +那麽 +部分 +都 +鄙人 +采取 +里面 +重大 +重新 +重要 +鉴于 +针对 +问题 +防止 +阿 +附近 +限制 +除 +除了 +除外 +除开 +除此之外 +除非 +随 +随后 +随时 +随着 +随著 +难道说 +集中 +需要 +非但 +非常 +非徒 +非特 +非独 +靠 +顺 +顺着 +首先 +高兴 +! +, +: +; +? diff --git a/static/docs/wordcloud/stop_words/hit_stop_words.txt b/static/docs/wordcloud/stop_words/hit_stop_words.txt new file mode 100644 index 00000000..58ea97fd --- /dev/null +++ b/static/docs/wordcloud/stop_words/hit_stop_words.txt @@ -0,0 +1,767 @@ +——— +》), +)÷(1- +”, +)、 +=( +: +→ +℃ +& +* +一一 +~~~~ +’ +. +『 +.一 +./ +-- +』 +=″ +【 +[*] +}> +[⑤]] +[①D] +c] +ng昉 +* +// +[ +] +[②e] +[②g] +={ +} +,也 +‘ +A +[①⑥] +[②B] +[①a] +[④a] +[①③] +[③h] +③] +1. +-- +[②b] +’‘ +××× +[①⑧] +0:2 +=[ +[⑤b] +[②c] +[④b] +[②③] +[③a] +[④c] +[①⑤] +[①⑦] +[①g] +∈[ +[①⑨] +[①④] +[①c] +[②f] +[②⑧] +[②①] +[①C] +[③c] +[③g] +[②⑤] +[②②] +一. +[①h] +.数 +[] +[①B] +数/ +[①i] +[③e] +[①①] +[④d] +[④e] +[③b] +[⑤a] +[①A] +[②⑧] +[②⑦] +[①d] +[②j] +〕〔 +][ +:// +′∈ +[②④ +[⑤e] +12% +b] +... +................... +…………………………………………………③ +ZXFITL +[③F] +」 +[①o] +]∧′=[ +∪φ∈ +′| +{- +②c +} +[③①] +R.L. +[①E] +Ψ +-[*]- +↑ +.日 +[②d] +[② +[②⑦] +[②②] +[③e] +[①i] +[①B] +[①h] +[①d] +[①g] +[①②] +[②a] +f] +[⑩] +a] +[①e] +[②h] +[②⑥] +[③d] +[②⑩] +e] +〉 +】 +元/吨 +[②⑩] +2.3% +5:0 +[①] +:: +[②] +[③] +[④] +[⑤] +[⑥] +[⑦] +[⑧] +[⑨] +…… +—— +? +、 +。 +“ +” +《 +》 +! +, +: +; +? +. +, +. +' +? +· +——— +── +? +— +< +> +( +) +〔 +〕 +[ +] +( +) +- ++ +~ +× +/ +/ +① +② +③ +④ +⑤ +⑥ +⑦ +⑧ +⑨ +⑩ +Ⅲ +В +" +; +# +@ +γ +μ +φ +φ. +× +Δ +■ +▲ +sub +exp +sup +sub +Lex +# +% +& +' ++ ++ξ +++ +- +-β +< +<± +<Δ +<λ +<φ +<< += += +=☆ +=- +> +>λ +_ +~± +~+ +[⑤f] +[⑤d] +[②i] +≈ +[②G] +[①f] +LI +㈧ +[- +...... +〉 +[③⑩] +第二 +一番 +一直 +一个 +一些 +许多 +种 +有的是 +也就是说 +末##末 +啊 +阿 +哎 +哎呀 +哎哟 +唉 +俺 +俺们 +按 +按照 +吧 +吧哒 +把 +罢了 +被 +本 +本着 +比 +比方 +比如 +鄙人 +彼 +彼此 +边 +别 +别的 +别说 +并 +并且 +不比 +不成 +不单 +不但 +不独 +不管 +不光 +不过 +不仅 +不拘 +不论 +不怕 +不然 +不如 +不特 +不惟 +不问 +不只 +朝 +朝着 +趁 +趁着 +乘 +冲 +除 +除此之外 +除非 +除了 +此 +此间 +此外 +从 +从而 +打 +待 +但 +但是 +当 +当着 +到 +得 +的 +的话 +等 +等等 +地 +第 +叮咚 +对 +对于 +多 +多少 +而 +而况 +而且 +而是 +而外 +而言 +而已 +尔后 +反过来 +反过来说 +反之 +非但 +非徒 +否则 +嘎 +嘎登 +该 +赶 +个 +各 +各个 +各位 +各种 +各自 +给 +根据 +跟 +故 +故此 +固然 +关于 +管 +归 +果然 +果真 +过 +哈 +哈哈 +呵 +和 +何 +何处 +何况 +何时 +嘿 +哼 +哼唷 +呼哧 +乎 +哗 +还是 +还有 +换句话说 +换言之 +或 +或是 +或者 +极了 +及 +及其 +及至 +即 +即便 +即或 +即令 +即若 +即使 +几 +几时 +己 +既 +既然 +既是 +继而 +加之 +假如 +假若 +假使 +鉴于 +将 +较 +较之 +叫 +接着 +结果 +借 +紧接着 +进而 +尽 +尽管 +经 +经过 +就 +就是 +就是说 +据 +具体地说 +具体说来 +开始 +开外 +靠 +咳 +可 +可见 +可是 +可以 +况且 +啦 +来 +来着 +离 +例如 +哩 +连 +连同 +两者 +了 +临 +另 +另外 +另一方面 +论 +嘛 +吗 +慢说 +漫说 +冒 +么 +每 +每当 +们 +莫若 +某 +某个 +某些 +拿 +哪 +哪边 +哪儿 +哪个 +哪里 +哪年 +哪怕 +哪天 +哪些 +哪样 +那 +那边 +那儿 +那个 +那会儿 +那里 +那么 +那么些 +那么样 +那时 +那些 +那样 +乃 +乃至 +呢 +能 +你 +你们 +您 +宁 +宁可 +宁肯 +宁愿 +哦 +呕 +啪达 +旁人 +呸 +凭 +凭借 +其 +其次 +其二 +其他 +其它 +其一 +其余 +其中 +起 +起见 +起见 +岂但 +恰恰相反 +前后 +前者 +且 +然而 +然后 +然则 +让 +人家 +任 +任何 +任凭 +如 +如此 +如果 +如何 +如其 +如若 +如上所述 +若 +若非 +若是 +啥 +上下 +尚且 +设若 +设使 +甚而 +甚么 +甚至 +省得 +时候 +什么 +什么样 +使得 +是 +是的 +首先 +谁 +谁知 +顺 +顺着 +似的 +虽 +虽然 +虽说 +虽则 +随 +随着 +所 +所以 +他 +他们 +他人 +它 +它们 +她 +她们 +倘 +倘或 +倘然 +倘若 +倘使 +腾 +替 +通过 +同 +同时 +哇 +万一 +往 +望 +为 +为何 +为了 +为什么 +为着 +喂 +嗡嗡 +我 +我们 +呜 +呜呼 +乌乎 +无论 +无宁 +毋宁 +嘻 +吓 +相对而言 +像 +向 +向着 +嘘 +呀 +焉 +沿 +沿着 +要 +要不 +要不然 +要不是 +要么 +要是 +也 +也罢 +也好 +一 +一般 +一旦 +一方面 +一来 +一切 +一样 +一则 +依 +依照 +矣 +以 +以便 +以及 +以免 +以至 +以至于 +以致 +抑或 +因 +因此 +因而 +因为 +哟 +用 +由 +由此可见 +由于 +有 +有的 +有关 +有些 +又 +于 +于是 +于是乎 +与 +与此同时 +与否 +与其 +越是 +云云 +哉 +再说 +再者 +在 +在下 +咱 +咱们 +则 +怎 +怎么 +怎么办 +怎么样 +怎样 +咋 +照 +照着 +者 +这 +这边 +这儿 +这个 +这会儿 +这就是说 +这里 +这么 +这么点儿 +这么些 +这么样 +这时 +这些 +这样 +正如 +吱 +之 +之类 +之所以 +之一 +只是 +只限 +只要 +只有 +至 +至于 +诸位 +着 +着呢 +自 +自从 +自个儿 +自各儿 +自己 +自家 +自身 +综上所述 +总的来看 +总的来说 +总的说来 +总而言之 +总之 +纵 +纵令 +纵然 +纵使 +遵照 +作为 +兮 +呃 +呗 +咚 +咦 +喏 +啐 +喔唷 +嗬 +嗯 +嗳 diff --git a/static/docs/wordcloud/stop_words/scu_stop_words.txt b/static/docs/wordcloud/stop_words/scu_stop_words.txt new file mode 100644 index 00000000..14eb732a --- /dev/null +++ b/static/docs/wordcloud/stop_words/scu_stop_words.txt @@ -0,0 +1,976 @@ +打开天窗说亮话 +到目前为止 +赶早不赶晚 +常言说得好 +何乐而不为 +毫无保留地 +由此可见 +这就是说 +这么点儿 +综上所述 +总的来看 +总的来说 +总的说来 +总而言之 +相对而言 +除此之外 +反过来说 +恰恰相反 +如上所述 +换句话说 +具体地说 +具体说来 +另一方面 +与此同时 +一则通过 +毫无例外 +不然的话 +从此以后 +从古到今 +从古至今 +从今以后 +大张旗鼓 +从无到有 +从早到晚 +弹指之间 +不亦乐乎 +不知不觉 +不止一次 +不择手段 +不可开交 +不可抗拒 +不仅仅是 +不管怎样 +挨家挨户 +长此下去 +长话短说 +除此而外 +除此以外 +除此之外 +得天独厚 +川流不息 +长期以来 +挨门挨户 +挨门逐户 +多多少少 +多多益善 +二话不说 +更进一步 +二话没说 +分期分批 +风雨无阻 +归根到底 +归根结底 +反之亦然 +大面儿上 +倒不如说 +成年累月 +换句话说 +或多或少 +简而言之 +接连不断 +尽如人意 +尽心竭力 +尽心尽力 +尽管如此 +据我所知 +具体地说 +具体来说 +具体说来 +近几年来 +每时每刻 +屡次三番 +三番两次 +三番五次 +三天两头 +另一方面 +老老实实 +年复一年 +恰恰相反 +顷刻之间 +穷年累月 +千万千万 +日复一日 +如此等等 +如前所述 +如上所述 +一方面 +切不可 +顷刻间 +全身心 +另方面 +另一个 +猛然间 +默默地 +就是说 +近年来 +尽可能 +接下来 +简言之 +急匆匆 +即是说 +基本上 +换言之 +充其极 +充其量 +暗地里 +反之则 +比如说 +背地里 +背靠背 +并没有 +不得不 +不得了 +不得已 +不仅仅 +不经意 +不能不 +不外乎 +不由得 +不怎么 +不至于 +策略地 +差不多 +常言道 +常言说 +多年来 +多年前 +差一点 +敞开儿 +抽冷子 +大不了 +反倒是 +反过来 +大体上 +当口儿 +倒不如 +怪不得 +动不动 +看起来 +看上去 +看样子 +够瞧的 +到了儿 +呆呆地 +来不及 +来得及 +到头来 +连日来 +于是乎 +为什么 +这会儿 +换言之 +那会儿 +那么些 +那么样 +什么样 +反过来 +紧接着 +就是说 +要不然 +要不是 +一方面 +以至于 +自个儿 +自各儿 +之所以 +这么些 +这么样 +怎么办 +怎么样 +谁知 +顺着 +似的 +虽然 +虽说 +虽则 +随着 +所以 +他们 +他人 +它们 +她们 +倘或 +倘然 +倘若 +倘使 +要么 +要是 +也罢 +也好 +以便 +依照 +以及 +以免 +以至 +以致 +抑或 +因此 +因而 +因为 +由于 +有的 +有关 +有些 +于是 +与否 +与其 +越是 +云云 +一般 +一旦 +一来 +一切 +一样 +同时 +万一 +为何 +为了 +为着 +嗡嗡 +我们 +呜呼 +乌乎 +无论 +无宁 +沿着 +毋宁 +向着 +照着 +怎么 +咱们 +在下 +再说 +再者 +怎样 +这边 +这儿 +这个 +这里 +这么 +这时 +这些 +这样 +正如 +之类 +之一 +只是 +只限 +只要 +只有 +至于 +诸位 +着呢 +纵令 +纵然 +纵使 +遵照 +作为 +喔唷 +自从 +自己 +自家 +自身 +总之 +要不 +哎呀 +哎哟 +俺们 +按照 +吧哒 +罢了 +本着 +比方 +比如 +鄙人 +彼此 +别的 +别说 +并且 +不比 +不成 +不单 +不但 +不独 +不管 +不光 +不过 +不仅 +不拘 +不论 +不怕 +不然 +不如 +不特 +不惟 +不问 +不只 +朝着 +趁着 +除非 +除了 +此间 +此外 +从而 +但是 +当着 +的话 +等等 +叮咚 +对于 +多少 +而况 +而且 +而是 +而外 +而言 +而已 +尔后 +反之 +非但 +非徒 +否则 +嘎登 +各个 +各位 +各种 +各自 +根据 +故此 +固然 +关于 +果然 +果真 +哈哈 +何处 +何况 +何时 +哼唷 +呼哧 +还是 +还有 +或是 +或者 +极了 +及其 +及至 +即便 +即或 +即令 +即若 +即使 +既然 +既是 +继而 +加之 +假如 +假若 +假使 +鉴于 +几时 +较之 +接着 +结果 +进而 +尽管 +经过 +就是 +可见 +可是 +可以 +况且 +开始 +开外 +来着 +例如 +连同 +两者 +另外 +慢说 +漫说 +每当 +莫若 +某个 +某些 +哪边 +哪儿 +哪个 +哪里 +哪年 +哪怕 +哪天 +哪些 +哪样 +那边 +那儿 +那个 +那里 +那么 +那时 +那些 +那样 +乃至 +宁可 +宁肯 +宁愿 +你们 +啪达 +旁人 +凭借 +其次 +其二 +其他 +其它 +其一 +其余 +其中 +起见 +起见 +岂但 +前后 +前者 +然而 +然后 +然则 +人家 +任何 +任凭 +如此 +如果 +如何 +如其 +如若 +若非 +若是 +上下 +尚且 +设若 +设使 +甚而 +甚么 +甚至 +省得 +时候 +什么 +使得 +是的 +首先 +首先 +其次 +再次 +最后 +您们 +它们 +她们 +他们 +我们 +你是 +您是 +我是 +他是 +她是 +它是 +不是 +你们 +啊哈 +啊呀 +啊哟 +挨次 +挨个 +挨着 +哎呀 +哎哟 +俺们 +按理 +按期 +默然 +按时 +按说 +按照 +暗中 +暗自 +昂然 +八成 +倍感 +倍加 +本人 +本身 +本着 +并非 +别人 +必定 +比起 +比如 +比照 +鄙人 +毕竟 +必将 +必须 +并肩 +并没 +并排 +并且 +并无 +勃然 +不必 +不常 +不大 +不单 +不但 +而且 +不得 +不迭 +不定 +不独 +不对 +不妨 +不管 +不光 +不过 +不会 +不仅 +不拘 +不力 +不了 +不料 +不论 +不满 +不免 +不起 +不巧 +不然 +不日 +不少 +不胜 +不时 +不是 +不同 +不能 +不要 +不外 +不下 +不限 +不消 +不已 +不再 +不曾 +不止 +不只 +才能 +彻夜 +趁便 +趁机 +趁热 +趁势 +趁早 +趁着 +成心 +乘机 +乘势 +乘隙 +乘虚 +诚然 +迟早 +充分 +出来 +出去 +除此 +除非 +除开 +除了 +除去 +除却 +除外 +处处 +传说 +传闻 +纯粹 +此后 +此间 +此外 +此中 +次第 +匆匆 +从不 +从此 +从而 +从宽 +从来 +从轻 +从速 +从头 +从未 +从小 +从新 +从严 +从优 +从中 +从重 +凑巧 +存心 +达旦 +打从 +大大 +大抵 +大都 +大多 +大凡 +大概 +大家 +大举 +大略 +大约 +大致 +待到 +单纯 +单单 +但是 +但愿 +当场 +当儿 +当即 +当然 +当庭 +当头 +当下 +当真 +当中 +当着 +倒是 +到处 +到底 +到头 +得起 +的话 +的确 +等到 +等等 +顶多 +动辄 +陡然 +独自 +断然 +对于 +顿时 +多次 +多多 +多亏 +而后 +而论 +而且 +而是 +而外 +而言 +而已 +而又 +尔等 +反倒 +反而 +反手 +反之 +方才 +方能 +非常 +非但 +非得 +分头 +奋勇 +愤然 +更为 +更加 +根据 +个人 +各式 +刚才 +敢情 +该当 +嘎嘎 +否则 +赶快 +敢于 +刚好 +刚巧 +高低 +格外 +隔日 +隔夜 +公然 +过于 +果然 +果真 +光是 +关于 +共总 +姑且 +故此 +故而 +故意 +固然 +惯常 +毫不 +毫无 +很多 +何须 +好在 +何必 +何尝 +何妨 +何苦 +何况 +何止 +很少 +轰然 +后来 +呼啦 +哗啦 +互相 +忽地 +忽然 +话说 +或是 +伙同 +豁然 +恍然 +还是 +或许 +或者 +基本 +基于 +极大 +极度 +极端 +极力 +极其 +极为 +即便 +即将 +及其 +及至 +即刻 +即令 +即使 +几度 +几番 +几乎 +几经 +既然 +继而 +继之 +加上 +加以 +加之 +假如 +假若 +假使 +间或 +将才 +简直 +鉴于 +将近 +将要 +交口 +较比 +较为 +较之 +皆可 +截然 +截至 +藉以 +借此 +借以 +届时 +尽快 +近来 +进而 +进来 +进去 +尽管 +尽量 +尽然 +就算 +居然 +就此 +就地 +竟然 +究竟 +经常 +尽早 +精光 +经过 +就是 +局外 +举凡 +据称 +据此 +据实 +据说 +可好 +看来 +开外 +绝不 +决不 +据悉 +决非 +绝顶 +绝对 +绝非 +可见 +可能 +可是 +可以 +恐怕 +来讲 +来看 +快要 +况且 +拦腰 +牢牢 +老是 +累次 +累年 +理当 +理该 +理应 +例如 +立地 +立刻 +立马 +立时 +联袂 +连连 +连日 +路经 +临到 +连声 +连同 +连袂 +另外 +另行 +屡次 +屡屡 +缕缕 +率尔 +率然 +略加 +略微 +略为 +论说 +马上 +猛然 +没有 +每当 +每逢 +每每 +莫不 +莫非 +莫如 +莫若 +哪怕 +那么 +那末 +那些 +乃至 +难道 +难得 +难怪 +难说 +你们 +凝神 +宁可 +宁肯 +宁愿 +偶而 +偶尔 +碰巧 +譬如 +偏偏 +平素 +迫于 +扑通 +其次 +其后 +其实 +其它 +起初 +起来 +起首 +起头 +起先 +岂但 +岂非 +岂止 +恰逢 +恰好 +恰恰 +恰巧 +恰如 +恰似 +前后 +前者 +切莫 +切切 +切勿 +亲口 +亲身 +亲手 +亲眼 +亲自 +顷刻 +请勿 +取道 +权时 +全都 +全力 +全年 +全然 +然而 +然后 +人家 +人人 +仍旧 +仍然 +日见 +日渐 +日益 +日臻 +如常 +如次 +如果 +如今 +如期 +如若 +如上 +如下 +上来 +上去 +瑟瑟 +沙沙 +啊 +哎 +唉 +俺 +按 +吧 +把 +甭 +别 +嘿 +很 +乎 +会 +或 +既 +及 +啦 +了 +们 +你 +您 +哦 +砰 +啊 +你 +我 +他 +她 +它 diff --git a/tools/manual_rate_pixiv_artwork_gui/__init__.py b/tools/manual_rate_pixiv_artwork_gui/__init__.py new file mode 100644 index 00000000..c82dcfab --- /dev/null +++ b/tools/manual_rate_pixiv_artwork_gui/__init__.py @@ -0,0 +1,27 @@ +""" +@Author : Ailitonia +@Date : 2024/9/8 17:05 +@FileName : manual_rate_pixiv_artwork +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from .data_source import ( + ArtworkRecommendPixivArtworkSource, + LocalPixivArtworkSource, + NonRatingPixivArtworkSource, + RecommendPixivArtworkSource, + SearchPopularPixivArtworkSource, +) +from .ui_main import ManualRatingPixivArtworkMain + +__all__ = [ + 'LocalPixivArtworkSource', + 'NonRatingPixivArtworkSource', + 'ManualRatingPixivArtworkMain', + 'RecommendPixivArtworkSource', + 'ArtworkRecommendPixivArtworkSource', + 'SearchPopularPixivArtworkSource', +] diff --git a/tools/manual_rate_pixiv_artwork_gui/data_source.py b/tools/manual_rate_pixiv_artwork_gui/data_source.py new file mode 100644 index 00000000..8d2a6e3a --- /dev/null +++ b/tools/manual_rate_pixiv_artwork_gui/data_source.py @@ -0,0 +1,583 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 23:21 +@FileName : data_source +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +import abc +import asyncio +import inspect +import re +import threading +from collections.abc import Callable, Coroutine +from datetime import datetime +from functools import wraps +from tkinter import Tk, filedialog, simpledialog +from typing import IO, TYPE_CHECKING, Any, Self, cast + +from PIL import Image, ImageTk +from nonebot.log import logger +from pydantic import BaseModel, ConfigDict + +from src.compat import dump_json_as, parse_json_as +from src.resource import AnyResource, TemporaryResource +from src.service.artwork_collection import PixivArtworkCollection +from .model import CustomImportArtwork + +if TYPE_CHECKING: + from os import PathLike + from tkinter.ttk import Entry, Label + + from src.database.internal.artwork_collection import ArtworkCollection as DBArtworkCollection + +type SourceOpenFp = str | bytes | PathLike[str] | PathLike[str] | IO[bytes] + + +class OutputPath: + TMP_DIR = TemporaryResource('manual_rate_pixiv_artwork') + + def __init__(self, working_name: str): + self._working_timestamp = datetime.now().strftime('%Y%m%d-%H%M%S') + self._output_dir = self.TMP_DIR(working_name) + + @property + def output_dir(self) -> TemporaryResource: + return self._output_dir(self._working_timestamp) + + @property + def cache_dir(self) -> TemporaryResource: + return self._output_dir('working_dir_cache') + + @property + def import_data_file(self) -> TemporaryResource: + return self.output_dir('custom_import_collected_artworks.json') + + +class CurrentArtwork(BaseModel): + """当前进行分级的作品""" + pid: int + source_path: str + + model_config = ConfigDict(extra='ignore', from_attributes=True, frozen=True, coerce_numbers_to_str=True) + + +class BasePixivArtworkSource(abc.ABC): + """待分级作品源基类""" + + __slots__ = ( + '_async_loop', + '_current_source', + '_current_source_image', + '_remaining_source', + '_working_path', + '_output_path', + ) + + if TYPE_CHECKING: + _current_source: CurrentArtwork + _remaining_source: list[CurrentArtwork] + _working_path: str + + def __init__(self) -> None: + self._async_loop = asyncio.new_event_loop() + self._remaining_source = [] + self._output_path = OutputPath(self.source_type) + + @property + @abc.abstractmethod + def source_type(self) -> str: + raise NotImplementedError + + @property + @abc.abstractmethod + def title_name(self) -> str: + raise NotImplementedError + + @property + def current_source_path(self) -> str: + return self._current_source.source_path + + @property + def working_path(self) -> str: + return self._working_path + + @staticmethod + def _run_in_async_event_loop[** P, T](func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, None]: + """装饰一个异步方法, 使其在 async event loop 中执行""" + if not inspect.iscoroutinefunction(func): + raise TypeError(f'{func.__name__} is not coroutine function') + + @wraps(func) + def _wrapper(*args: P.args, **kwargs: P.kwargs): + self: Self = cast(Self, args[0]) + coro = func(*args, **kwargs) + + if self._async_loop.is_running(): + self._async_loop.create_task(coro=coro) + else: + threading.Thread(target=self._async_loop.run_until_complete, args=(coro,)).start() + + return _wrapper + + @abc.abstractmethod + async def _load_current_source(self) -> SourceOpenFp: + """内部方法, 加载目标作品图片""" + raise NotImplementedError + + async def _load_current( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + *, + rs_width: int = 1024, + rs_height: int = 768, + ) -> None: + """内部方法, 加载的目标作品图片并在控件上显示""" + image = Image.open(await self._load_current_source()).convert('RGB') + + width, height = image.size + scale = min(rs_width / width, rs_height / height) + image = image.resize((int(width * scale), int(height * scale)), Image.Resampling.LANCZOS) + box = (int(abs(width * scale - rs_width) / 2), int(abs(height * scale - rs_height) / 2)) + background = Image.new(mode='RGB', size=(rs_width, rs_height), color=(0, 0, 0)) + background.paste(image, box=box) + + self._current_source_image = ImageTk.PhotoImage(background) + image_label.config(image=self._current_source_image) # type: ignore + + image.close() + background.close() + + show_current_entry.delete(0, 'end') + show_current_entry.insert(0, self._current_source.source_path) + show_remaining_entry.delete(0, 'end') + show_remaining_entry.insert(0, str(len(self._remaining_source))) + + @_run_in_async_event_loop + async def load_current( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + *, + rs_width: int = 1024, + rs_height: int = 768, + ) -> None: + """加载的目标作品图片并在控件上显示, 同时可用于立即刷新图片控件""" + await self._load_current( + image_label, show_current_entry, show_remaining_entry, rs_width=rs_width, rs_height=rs_height + ) + + @abc.abstractmethod + async def _select_current_source(self) -> None: + """内部方法, 选择开始时的目标作品""" + raise NotImplementedError + + @abc.abstractmethod + async def _init_working_path(self) -> None: + """内部方法, 初始化工作路径, 预处理和缓存待处理作品列表""" + raise NotImplementedError + + @_run_in_async_event_loop + async def _select_current( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + """选择开始时的目标作品, 初始化工作目录""" + await self._select_current_source() + await self._init_working_path() + await self.next_image_async(image_label, show_current_entry, show_remaining_entry) + + def select_current( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + """选择开始时的目标作品, 初始化工作目录, 并在输出控件中显示作品位置""" + self._select_current(image_label, show_current_entry, show_remaining_entry) + + async def next_image_async( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + """异步加载待处理作品列表中的下一个作品, 刷新控件显示""" + if self._remaining_source: + self._current_source = self._remaining_source.pop(0) + await self._load_current(image_label, show_current_entry, show_remaining_entry) + + def next_image( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + """加载待处理作品列表中的下一个作品, 刷新控件显示""" + if self._remaining_source: + self._current_source = self._remaining_source.pop(0) + self.load_current(image_label, show_current_entry, show_remaining_entry) + + @_run_in_async_event_loop + async def _merge_all_output(self) -> None: + import_artworks_data: list[CustomImportArtwork] = [] + + for file in self._output_path.output_dir.list_current_files(): + async with file.async_open('r', encoding='utf-8') as af: + import_artworks_data.append(CustomImportArtwork.model_validate_json(await af.read())) + + async with self._output_path.import_data_file.async_open('w', encoding='utf-8') as af: + await af.write(dump_json_as(list[CustomImportArtwork], import_artworks_data)) + + def merge(self) -> None: + self._merge_all_output() + logger.info(f'Merge all rating data into {self._output_path.import_data_file.resolve_path}') + + async def _generate_output(self, rating: int, *, classification: int = 3) -> None: + aid = str(self._current_source.pid) + data = CustomImportArtwork(origin='pixiv', classification=classification, aid=aid, rating=rating) + async with self._output_path.output_dir(f'{aid}.json').async_open('w', encoding='utf-8') as af: + await af.write(data.model_dump_json()) + + @_run_in_async_event_loop + async def _set_current( + self, + rating: int, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + *, + classification: int = 3, + ) -> None: + aid = str(self._current_source.pid) + try: + artwork = PixivArtworkCollection(artwork_id=aid) + artwork_info = await artwork.artwork_proxy.query() + + rating = 3 if artwork_info.rating == 3 else rating + classification = 1 if artwork_info.classification == 1 else classification + + await artwork.add_and_upgrade_artwork_into_database( + classification=classification, rating=rating, force_update_mark=True + ) + await self._generate_output(rating=rating, classification=classification) + + logger.opt(colors=True).success( + f'Set classification={classification} rating={rating} succeed, ' + f'artwork(id={aid}, title={artwork_info.title}, username={artwork_info.uname})' + ) + except Exception as e: + logger.error(f'Set artwork(id={aid}) rating failed, {repr(e)}') + finally: + await self.next_image_async(image_label, show_current_entry, show_remaining_entry) + + def set_current_general( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + self._set_current(0, image_label, show_current_entry, show_remaining_entry) + + def set_current_sensitive( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + self._set_current(1, image_label, show_current_entry, show_remaining_entry) + + def set_current_questionable( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + self._set_current(2, image_label, show_current_entry, show_remaining_entry) + + def set_current_explicit( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + self._set_current(3, image_label, show_current_entry, show_remaining_entry) + + def set_current_reset( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + self._set_current(-1, image_label, show_current_entry, show_remaining_entry, classification=0) + + def set_current_ignored( + self, + image_label: 'Label', + show_current_entry: 'Entry', + show_remaining_entry: 'Entry', + ) -> None: + self._set_current(-1, image_label, show_current_entry, show_remaining_entry, classification=-2) + + +class LocalPixivArtworkSource(BasePixivArtworkSource): + """本地图片""" + + @property + def source_type(self) -> str: + return 'local_pixiv_image' + + @property + def title_name(self) -> str: + return '本地图片' + + async def _load_current_source(self) -> SourceOpenFp: + return self._current_source.source_path + + async def _select_current_source(self) -> None: + current_file = AnyResource(filedialog.askopenfilename()) + self._current_source = CurrentArtwork.model_validate({ + 'pid': current_file.path.name.split('_')[0], + 'source_path': current_file.resolve_path, + }) + self._working_path = AnyResource(current_file.path.parent).resolve_path + + async def _init_working_path(self) -> None: + working_dir = AnyResource(self._working_path) + + # 文件列表缓存 + working_dir_all_files_cache = self._output_path.cache_dir( + f'{working_dir.path.parent.name}-{working_dir.path.name}.txt' + ) + + # 加载文件列表缓存 + if working_dir_all_files_cache.is_file: + async with working_dir_all_files_cache.async_open('r', encoding='utf-8') as af: + cache_files = parse_json_as(list[CurrentArtwork], await af.read()) + working_dir_all_files = sorted( + (x for x in cache_files if x.pid >= self._current_source.pid), + key=lambda x: x.pid + ) + logger.info(f'发现目录缓存, 已载入, 共计 {len(cache_files)}, 剩余待处理 {len(working_dir_all_files)}') + else: + exists_files = sorted( + ( + CurrentArtwork.model_validate({ + 'pid': x.path.name.split('_')[0], + 'source_path': x.resolve_path, + }) + for x in working_dir.list_current_files() + if re.match(r'^\d+?_p0\d*?\.(jpg|jpeg|png|JPG|JPEG|PNG)$', x.path.name) + ), + key=lambda x: x.pid + ) + working_dir_all_files = sorted( + (x for x in exists_files if x.pid >= self._current_source.pid), + key=lambda x: x.pid + ) + logger.info(f'已筛选目录作品文件, 共计 {len(exists_files)}, 剩余待处理 {len(working_dir_all_files)}') + + self._remaining_source = working_dir_all_files + + # 保存文件清单缓存 + async with working_dir_all_files_cache.async_open('w', encoding='utf-8') as af: + await af.write(dump_json_as(list[CurrentArtwork], self._remaining_source)) + + +class BaseDatabasePixivArtworkSource(BasePixivArtworkSource, abc.ABC): + """从数据中获取作品方法基类""" + + @property + def source_type(self) -> str: + return 'database_pixiv_artwork' + + @abc.abstractmethod + async def query_some_artworks_from_database(self) -> list['DBArtworkCollection']: + """从数据库中获取作品的条件方法""" + raise NotImplementedError + + async def _load_current_source(self) -> SourceOpenFp: + logger.info(f'获取作品 {self._current_source.pid} 图片中, 请稍候') + file = await PixivArtworkCollection(artwork_id=self._current_source.pid).artwork_proxy.get_page_file() + return file.resolve_path + + async def _select_current_source(self) -> None: + pass + + async def _init_working_path(self) -> None: + artworks = await self.query_some_artworks_from_database() + logger.info(f'已从数据中获取作品 {len(artworks)} 个, 正在初始化处理队列') + + self._remaining_source = sorted( + ( + CurrentArtwork.model_validate({ + 'pid': x.aid, + 'source_path': x.cover_page, + }) + for x in artworks + ), + key=lambda x: x.pid, + reverse=True + ) + + +class NonRatingPixivArtworkSource(BaseDatabasePixivArtworkSource): + """从数据中获取尚未分级的作品作品""" + + @property + def title_name(self) -> str: + return '数据库中未分级作品' + + async def query_some_artworks_from_database(self) -> list['DBArtworkCollection']: + return await PixivArtworkCollection.query_by_condition( + keywords=None, num=200, + allow_classification_range=(-1, 0), allow_rating_range=(-1, 3), + order_mode='aid_desc', + ) + + +class BaseCustomPixivArtworkSource(BasePixivArtworkSource, abc.ABC): + """自定义任意处理作品方法基类""" + + @property + def source_type(self) -> str: + return 'custom_pixiv_artwork' + + @abc.abstractmethod + async def query_some_artworks(self) -> list[int]: + """自定义获取作品 ID 的条件方法""" + raise NotImplementedError + + async def _load_current_source(self) -> SourceOpenFp: + logger.info(f'获取作品 {self._current_source.pid} 图片中, 请稍候') + file = await PixivArtworkCollection(artwork_id=self._current_source.pid).artwork_proxy.get_page_file() + return file.resolve_path + + async def _select_current_source(self) -> None: + pass + + async def _init_working_path(self) -> None: + artworks = await self.query_some_artworks() + logger.info(f'已从自定义来源获取作品 {len(artworks)} 个, 正在初始化处理队列') + + self._remaining_source = sorted( + ( + CurrentArtwork.model_validate({ + 'pid': x, + 'source_path': f'https://www.pixiv.net/artworks/{x}', + }) + for x in artworks + ), + key=lambda x: x.pid, + reverse=True + ) + + +class RecommendPixivArtworkSource(BaseCustomPixivArtworkSource): + """Pixiv 首页推荐作品""" + + @property + def title_name(self) -> str: + return 'Pixiv 首页推荐作品' + + async def query_some_artworks(self) -> list[int]: + from src.utils.pixiv_api.pixiv import PixivCommon + + logger.info('正在从 Pixiv 发现/推荐获取作品来源, 请稍候') + discovery_result = await PixivCommon.query_discovery_artworks() + top_result = await PixivCommon.query_top_illust() + aids = [str(x) for x in (discovery_result.recommend_pids + top_result.recommend_pids)] + + return [int(x) for x in await PixivArtworkCollection.query_not_exists_aids(aids, exclude_classification=3)] + + +class ArtworkRecommendPixivArtworkSource(BaseCustomPixivArtworkSource): + """Pixiv 作品相关推荐作品""" + + @property + def title_name(self) -> str: + return 'Pixiv 作品相关推荐作品' + + @staticmethod + def _ask_pid() -> int: + tmp_root = Tk() # Create a new temporary "parent", but make it invisible + tmp_root.withdraw() + + pid = simpledialog.askinteger('目标作品', '请输入想要获取相关推荐的作品 PID', parent=tmp_root) + while pid is None: + pid = simpledialog.askinteger('目标作品', 'PID 不能为空, 请输入想要获取相关推荐的作品 PID', parent=tmp_root) + + tmp_root.destroy() + return pid + + async def query_some_artworks(self) -> list[int]: + from src.utils.pixiv_api.pixiv import PixivArtwork + + pid = self._ask_pid() + + logger.info(f'正在从获取作品 {pid} 相关推荐, 请稍候') + recommend_result = await PixivArtwork(pid=pid).query_recommend(init_limit=100) + aids = [str(x.id) for x in recommend_result.illusts] + + return [int(x) for x in await PixivArtworkCollection.query_not_exists_aids(aids, exclude_classification=3)] + + +class SearchPopularPixivArtworkSource(BaseCustomPixivArtworkSource): + """Pixiv 搜索作品""" + + @property + def title_name(self) -> str: + return 'Pixiv 搜索' + + @staticmethod + def _ask_keyword() -> str: + tmp_root = Tk() # Create a new temporary "parent", but make it invisible + tmp_root.withdraw() + + keyword = simpledialog.askstring('搜索关键词', '请输入搜索关键词', parent=tmp_root) + while keyword is None: + keyword = simpledialog.askstring('搜索关键词', '关键词不能为空, 请输入搜索关键词', parent=tmp_root) + + tmp_root.destroy() + return keyword + + @staticmethod + def _ask_page() -> int: + tmp_root = Tk() # Create a new temporary "parent", but make it invisible + tmp_root.withdraw() + + page = simpledialog.askinteger('页码', '请输入请求的搜索结果页码', parent=tmp_root) + page = 1 if page is None else page + + tmp_root.destroy() + return page + + async def query_some_artworks(self) -> list[int]: + from src.utils.pixiv_api.pixiv import PixivArtwork + + keyword = self._ask_keyword() + page = self._ask_page() + + logger.info(f'正在从 {keyword!r} 搜索结果 Page-{page} 获取作品信息, 请稍候') + search_result = await PixivArtwork.search_by_default_popular_condition(word=keyword, page=page) + aids = [str(x.id) for x in search_result.searching_result] + + return [int(x) for x in await PixivArtworkCollection.query_not_exists_aids(aids, exclude_classification=3)] + + +__all__ = [ + 'BasePixivArtworkSource', + 'LocalPixivArtworkSource', + 'NonRatingPixivArtworkSource', + 'RecommendPixivArtworkSource', + 'ArtworkRecommendPixivArtworkSource', + 'SearchPopularPixivArtworkSource', +] diff --git a/tools/manual_rate_pixiv_artwork_gui/model.py b/tools/manual_rate_pixiv_artwork_gui/model.py new file mode 100644 index 00000000..481245ef --- /dev/null +++ b/tools/manual_rate_pixiv_artwork_gui/model.py @@ -0,0 +1,39 @@ +""" +@Author : Ailitonia +@Date : 2024/9/8 17:06 +@FileName : model +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Literal, TypeAlias + +from pydantic import BaseModel, ConfigDict + +ALLOW_ARTWORK_ORIGIN: TypeAlias = Literal[ + 'pixiv', + 'danbooru', + 'gelbooru', + 'behoimi', + 'konachan', + 'yandere', + 'local_collected_artwork', + 'none', +] + + +class CustomImportArtwork(BaseModel): + """手动导入/更新作品信息""" + origin: ALLOW_ARTWORK_ORIGIN + aid: str + classification: int + rating: int + + model_config = ConfigDict(extra='ignore', frozen=True, coerce_numbers_to_str=True) + + +__all__ = [ + 'CustomImportArtwork', +] diff --git a/tools/manual_rate_pixiv_artwork_gui/ui_main.py b/tools/manual_rate_pixiv_artwork_gui/ui_main.py new file mode 100644 index 00000000..023c34f4 --- /dev/null +++ b/tools/manual_rate_pixiv_artwork_gui/ui_main.py @@ -0,0 +1,133 @@ +""" +@Author : Ailitonia +@Date : 2024/9/8 17:11 +@FileName : ui_main +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from tkinter import StringVar, Tk, messagebox, ttk + +from .data_source import BasePixivArtworkSource + + +class ManualRatingPixivArtworkMain[T: BasePixivArtworkSource]: + + def __init__(self, source: T) -> None: + self.root: Tk = Tk() + self.source: T = source + + # 构造布局 + self.root.title(f'Pixiv 作品分级 - {self.source.title_name}') + + # 顶部功能区布局框架 + top_frm = ttk.Frame(self.root, padding=5) + top_frm.pack(side='top', fill='x') + + # 右侧布局按钮框架 + button_frm = ttk.Frame(top_frm, padding=5) + button_frm.pack(side='right') + + # 初始化工作目录和文件列表显示框架 + info_frm = ttk.Frame(top_frm, padding=5) + info_frm.pack(side='top', anchor='center', expand=True, fill='x') + + # 选择图片及加载工作目录组件 + file_frm = ttk.Frame(info_frm, padding=5) + file_frm.pack(side='top', fill='x') + ttk.Label(file_frm, text='当前文件: ').pack(side='left') + self._file_entry = ttk.Entry(file_frm, textvariable=StringVar()) + self._file_entry.pack(fill='x') + + remaining_num_frm = ttk.Frame(info_frm, padding=5) + remaining_num_frm.pack(side='top', fill='x') + ttk.Label(remaining_num_frm, text='剩余文件数: ').pack(side='left') + self._remaining_num_entry = ttk.Entry(remaining_num_frm, textvariable=StringVar()) + self._remaining_num_entry.pack(fill='x') + + # 底部图片显示及分级按钮布局框架 + separator = ttk.Separator(self.root, orient='horizontal') + separator.pack(fill='x', pady=2) + show_frm = ttk.Frame(self.root, padding=5) + show_frm.pack(side='left', fill='x') + rate_frm = ttk.Frame(self.root, padding=5) + rate_frm.pack(side='right', fill='x') + + # 图片显示 + self._image_label = ttk.Label(show_frm) + self._image_label.pack() + + # 主要显示控件元组 + show_components = (self._image_label, self._file_entry, self._remaining_num_entry) + + # 主要流程按钮 + ttk.Button( + button_frm, + text='选择图片并初始化', + command=lambda: self.source.select_current(*show_components) + ).pack() + ttk.Button(button_frm, text='生成导入文件', command=self.source.merge).pack() + ttk.Button(button_frm, text='退出', command=self._shutdown).pack() + + # 分级按钮 + ttk.Button( + rate_frm, text='(0) General | 萌图', padding=6, + command=lambda: self.source.set_current_general(*show_components) + ).pack(anchor='center') + self.root.bind('', lambda x: self.source.set_current_general(*show_components)) + + ttk.Button( + rate_frm, text='(1) Sensitive | 涩图', padding=6, + command=lambda: self.source.set_current_sensitive(*show_components) + ).pack(anchor='center') + self.root.bind('', lambda x: self.source.set_current_sensitive(*show_components)) + + ttk.Button( + rate_frm, text='(2) Questionable | 软色情', padding=6, + command=lambda: self.source.set_current_questionable(*show_components) + ).pack(anchor='center') + self.root.bind('', lambda x: self.source.set_current_questionable(*show_components)) + + ttk.Button( + rate_frm, text='(3) Explicit | R18', padding=6, + command=lambda: self.source.set_current_explicit(*show_components) + ).pack(anchor='center') + self.root.bind('', lambda x: self.source.set_current_explicit(*show_components)) + + ttk.Button( + rate_frm, text='(P) Pass | 跳过', padding=6, + command=lambda: self.source.next_image(*show_components) + ).pack(anchor='center') + self.root.bind('', lambda x: self.source.next_image(*show_components)) + + ttk.Button( + rate_frm, text='(R) Reset | 重置', padding=6, + command=lambda: self.source.set_current_reset(*show_components) + ).pack(anchor='center') + self.root.bind('', lambda x: self.source.set_current_reset(*show_components)) + + ttk.Button( + rate_frm, text='(I) Ignored | 忽略', padding=6, + command=lambda: self.source.set_current_ignored(*show_components) + ).pack(anchor='center') + + # 拦截关闭按钮处理 + self.root.protocol('WM_DELETE_WINDOW', self._shutdown) + + def _shutdown(self) -> None: + ok_exist = messagebox.askokcancel(message='退出前记得生成导出文件, 确认要退出吗?', icon='question', + title='退出确认') + if not ok_exist: + return + + self.root.destroy() + + def run(self): + self.root.mainloop() + + +__all__ = [ + 'ManualRatingPixivArtworkMain', +] diff --git a/tools/migration_from_old_version/import_to_v090_from_v082.py b/tools/migration_from_old_version/import_to_v090_from_v082.py index a2b1a157..e8bae3c5 100644 --- a/tools/migration_from_old_version/import_to_v090_from_v082.py +++ b/tools/migration_from_old_version/import_to_v090_from_v082.py @@ -50,7 +50,7 @@ async def _wrapper(*args, **kwargs): async def read_json(file_name: str): - async with aiofiles.open(folder.joinpath(file_name), 'r', encoding='utf8') as af: + async with aiofiles.open(folder.joinpath(file_name), encoding='utf8') as af: content = json.loads(await af.read()) return content diff --git a/tools/migration_from_old_version/import_to_v1_from_v092.py b/tools/migration_from_old_version/import_to_v1_from_v092.py index eefdd3d1..2040cbcd 100644 --- a/tools/migration_from_old_version/import_to_v1_from_v092.py +++ b/tools/migration_from_old_version/import_to_v1_from_v092.py @@ -8,14 +8,13 @@ @Software : PyCharm """ -from datetime import datetime, date -from typing import Optional +from datetime import date, datetime import ujson as json from nonebot.log import logger from pydantic import BaseModel, ConfigDict -from src.database import BotSelfDAL, SubscriptionSourceDAL, EmailBoxDAL, begin_db_session +from src.database import BotSelfDAL, SubscriptionSourceDAL, begin_db_session from src.resource import TemporaryResource from src.service.omega_api import register_get_route from src.service.omega_base import OmegaEntity @@ -30,9 +29,9 @@ class _BotSelf(DateBaseModel): self_id: str bot_type: str bot_status: int - bot_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + bot_info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None class _SubscriptionSource(DateBaseModel): @@ -40,7 +39,7 @@ class _SubscriptionSource(DateBaseModel): sub_type: str sub_id: str sub_user_name: str - sub_info: Optional[str] = None + sub_info: str | None = None class _EmailBox(DateBaseModel): @@ -57,8 +56,8 @@ class _EntityInfo(DateBaseModel): entity_type: str entity_id: str parent_id: str - entity_name: Optional[str] = None - entity_info: Optional[str] = None + entity_name: str | None = None + entity_info: str | None = None class _EntityFriendship(DateBaseModel): @@ -75,19 +74,19 @@ class _EntityAuthSetting(DateBaseModel): plugin: str node: str available: int - value: Optional[str] = None + value: str | None = None class _EntityBoundMailbox(DateBaseModel): address: str - bind_info: Optional[str] = None + bind_info: str | None = None class _EntitySubscribed(DateBaseModel): sub_type: str sub_id: str sub_user_name: str - sub_info: Optional[str] + sub_info: str | None class _Entity(DateBaseModel): @@ -131,19 +130,6 @@ async def input_v092_data(): available_subscription_source = await sub_dal.query_all() logger.success('subscription source data import completed') - logger.info('start import email box data') - async with begin_db_session() as session: - email_dal = EmailBoxDAL(session=session) - for mailbox in data.email_box: - await email_dal.add(address=mailbox.address, server_host=mailbox.server_host, protocol=mailbox.protocol, port=mailbox.port, password=mailbox.password) - await email_dal.commit_session() - - async with begin_db_session() as session: - email_dal = EmailBoxDAL(session=session) - available_mailbox = await email_dal.query_all() - available_mailbox_map = {x.address: x for x in available_mailbox} - logger.success('email box data import completed') - logger.info('start import entity data') for entity_data in data.entities: async with begin_db_session() as session: @@ -171,13 +157,6 @@ async def input_v092_data(): module=auth.module, plugin=auth.plugin, node=auth.node, available=auth.available, value=auth.value ) - bound_mailbox = entity_data.bound_mailbox - for mailbox in bound_mailbox: - await entity.bind_email_box( - email_box=available_mailbox_map.get(mailbox.address), - bind_info=f'{entity.entity_name}-{mailbox.address}' - ) - subscribed_source = entity_data.subscribed_source for source in subscribed_source: target_source = [ diff --git a/tools/migration_from_old_version/output_from_v092_to_v1.py b/tools/migration_from_old_version/output_from_v092_to_v1.py index 94176af9..5f61807f 100644 --- a/tools/migration_from_old_version/output_from_v092_to_v1.py +++ b/tools/migration_from_old_version/output_from_v092_to_v1.py @@ -8,20 +8,15 @@ @Software : PyCharm """ -from datetime import datetime, date +from datetime import date, datetime from enum import Enum, unique -from typing import Literal, Optional - -from pydantic import BaseModel, Field +from typing import Literal from omega_miya.database.internal.entity import BaseInternalEntity - -from omega_miya.database.schemas import BotSelf, RelatedEntity -from omega_miya.database.schemas import SubscriptionSource -from omega_miya.database.schemas import EmailBox - -from omega_miya.service.omega_api import register_get_route +from omega_miya.database.schemas import BotSelf, EmailBox, RelatedEntity, SubscriptionSource from omega_miya.local_resource import TmpResource +from omega_miya.service.omega_api import register_get_route +from pydantic import BaseModel, Field class DateBaseModel(BaseModel): @@ -51,9 +46,9 @@ class _BotSelf(DateBaseModel): self_id: str bot_type: _BotType bot_status: int - bot_info: Optional[str] = None - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None + bot_info: str | None = None + created_at: datetime | None = None + updated_at: datetime | None = None class _Data(DateBaseModel): data: list[_BotSelf] @@ -78,7 +73,7 @@ class _SubscriptionSource(DateBaseModel): sub_type: _SubscriptionSourceType sub_id: str sub_user_name: str - sub_info: Optional[str] = None + sub_info: str | None = None class _Data(DateBaseModel): data: list[_SubscriptionSource] @@ -120,8 +115,8 @@ class _EntityInfo(DateBaseModel): entity_type: _EntityType entity_id: str parent_id: str - entity_name: Optional[str] = None - entity_info: Optional[str] = None + entity_name: str | None = None + entity_info: str | None = None class _EntityFriendship(DateBaseModel): status: str = 'normal' @@ -136,17 +131,17 @@ class _EntityAuthSetting(DateBaseModel): plugin: str node: str available: int - value: Optional[str] = None + value: str | None = None class _EntityBoundMailbox(DateBaseModel): address: str - bind_info: Optional[str] = None + bind_info: str | None = None class _EntitySubscribed(DateBaseModel): sub_type: str sub_id: str sub_user_name: str - sub_info: Optional[str] + sub_info: str | None class _Entity(DateBaseModel): info: _EntityInfo diff --git a/tools/pixiv_artwork_downloader/__init__.py b/tools/pixiv_artwork_downloader/__init__.py new file mode 100644 index 00000000..74299cd0 --- /dev/null +++ b/tools/pixiv_artwork_downloader/__init__.py @@ -0,0 +1,15 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 00:52 +@FileName : pixiv_artwork_downloader +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from .downloader import PixivArtworkDownloader + +__all__ = [ + 'PixivArtworkDownloader', +] diff --git a/tools/pixiv_artwork_downloader/consts.py b/tools/pixiv_artwork_downloader/consts.py new file mode 100644 index 00000000..6b339cf0 --- /dev/null +++ b/tools/pixiv_artwork_downloader/consts.py @@ -0,0 +1,19 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 00:57 +@FileName : consts +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from typing import Literal + +DOWNLOADER_SETTING_NAME: Literal['pixiv_artwork_downloader'] = 'pixiv_artwork_downloader' +LAST_FOLLOWING_SETTING_KEY: Literal['last_latest_following_artwork'] = 'last_latest_following_artwork' + +__all__ = [ + 'DOWNLOADER_SETTING_NAME', + 'LAST_FOLLOWING_SETTING_KEY', +] diff --git a/tools/pixiv_artwork_downloader/downloader.py b/tools/pixiv_artwork_downloader/downloader.py new file mode 100644 index 00000000..76cb48fe --- /dev/null +++ b/tools/pixiv_artwork_downloader/downloader.py @@ -0,0 +1,306 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 00:58 +@FileName : downloader +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +import re +import sys +from asyncio import sleep as async_sleep +from collections.abc import Sequence +from copy import deepcopy +from datetime import datetime +from typing import TYPE_CHECKING, Literal + +from nonebot.log import logger + +from src.exception import WebSourceException +from src.resource import AnyResource, BaseResource, TemporaryResource +from src.service.artwork_collection import PixivArtworkCollection +from src.utils.pixiv_api import PixivUser +from src.utils.process_utils import semaphore_gather +from .utils import get_last_follow_illust_pid, set_last_follow_illust_pid + +if TYPE_CHECKING: + from pathlib import Path + + from src.service.artwork_proxy.models import ArtworkData + + +class PixivArtworkDownloader: + + def __init__(self, fast_mode: bool = False): + self._fast_mode = fast_mode + self.__output_file: TemporaryResource + + @classmethod + def get_output_dir(cls) -> TemporaryResource: + return TemporaryResource('pixiv_artwork_downloader') + + def set_output_file(self, category: str, filename: str) -> None: + self.__output_file: TemporaryResource = self.get_output_dir()(category, filename) + + @property + def output_file(self) -> TemporaryResource: + return self.__output_file + + @staticmethod + async def download_any_url[T: BaseResource]( + url: str, + save_folder: T, + *, + subdir: str | None = None, + ignore_exist_file: bool = True, + ) -> T: + """下载任意资源到任意位置""" + return await PixivUser._download_resource( + save_folder=save_folder, # type: ignore + url=url, subdir=subdir, ignore_exist_file=ignore_exist_file + ) + + async def _handle_artwork_data(self, pid: int) -> 'ArtworkData': + """获取作品信息并写入数据库""" + artwork = PixivArtworkCollection(artwork_id=pid) + try: + artwork_data = await artwork.artwork_proxy.query() + except WebSourceException as e: + if e.status_code == 404: + raise e + + # 请求过快可能暂时被流控, 暂停一下重试一次 + logger.opt(colors=True).warning(f'Query {artwork} failed and will retry again > {e!r}') + if self._fast_mode: + await async_sleep(20) + else: + await async_sleep(60) + artwork_data = await artwork.artwork_proxy.query() + + # 作品信息写入数据库 + await artwork.add_artwork_into_database_ignore_exists() + + return artwork_data + + async def _handle_output_artworks_download_url( + self, + artwork_data: 'ArtworkData', + output_all_urls: bool = False, + ) -> None: + """向输出文件写入作品原图下载链接""" + async with self.output_file.async_open('a', encoding='utf8') as af: + if output_all_urls: + await af.write('\n'.join(x.original_file.url for x in artwork_data.pages)) + await af.write('\n') + else: + await af.write(artwork_data.cover_page_url) + await af.write('\n') + + # 写入动图原始资源下载链接 + if artwork_data.extra_resource: + await af.write('\n'.join(x for x in artwork_data.extra_resource)) + await af.write('\n') + + async def _handle_output_artworks( + self, + pids: Sequence[int], + *, + enable_filter: bool = True, + ) -> None: + """向输出文件批量写入作品原图下载链接, 为应对 pixiv 流控, 对获取作品信息进行分段处理""" + prepare_pids = list(deepcopy(pids)) + handle_pids: list[int] = [] + while prepare_pids: + while len(handle_pids) < 20: + try: + handle_pids.append(prepare_pids.pop()) + except IndexError: + break + + tasks = [self._handle_artwork_data(pid=pid) for pid in handle_pids] + handle_pids.clear() + + artworks_result = await semaphore_gather(tasks=tasks, semaphore_num=20, filter_exception=True) + for artwork_data in artworks_result: + # 根据筛选条件写入图片 url + if not enable_filter: + await self._handle_output_artworks_download_url(artwork_data, output_all_urls=True) + elif artwork_data.rating == 3 and artwork_data.like_count is not None and artwork_data.like_count > 1666: + await self._handle_output_artworks_download_url(artwork_data, output_all_urls=True) + elif artwork_data.rating == 3: + await self._handle_output_artworks_download_url(artwork_data, output_all_urls=False) + elif artwork_data.like_count is not None and artwork_data.like_count >= 666: + await self._handle_output_artworks_download_url(artwork_data, output_all_urls=True) + else: + await self._handle_output_artworks_download_url(artwork_data, output_all_urls=False) + + if prepare_pids: + logger.info( + f'获取作品下载链接中, 剩余: {len(prepare_pids)}, 预计时间: {int(len(prepare_pids) * 1.52)} 秒') + if self._fast_mode: + await async_sleep(int((len(prepare_pids) if len(prepare_pids) < 20 else 20) * 0.1)) + else: + await async_sleep(int((len(prepare_pids) if len(prepare_pids) < 20 else 20) * 1.5)) + + @staticmethod + async def _get_new_follow_illust(up_pid: int | None = None) -> list[int]: + """获取所有关注用户的作品""" + ids: set[int] = set() + + for page in range(1, 85): # 只能获取到前 5000 张更新作品, 已关注作品页一页 60 个作品, 最多到 84 页, 后面全是重复的 + try: + logger.info(f'Querying follow artwork page: {page}') + illust_result = await PixivUser.query_following_user_latest_illust(page=page) + ids.update(illust_result.illust_ids) + + if up_pid and up_pid in ids: + logger.info(f'Found end artwork: {up_pid} in page: {page}') + break + + except Exception as e: + logger.error(f'Get follow latest artwork failed in page {page}, error: {e}') + sys.exit(str(e)) + + return sorted(ids) + + @staticmethod + async def _get_all_bookmark_illust( + uid: int | None = None, + *, + rest: Literal['show', 'hide'] = 'show', + before: int | None = None, + limit: int = 100, + ) -> list[int]: + """获取用户收藏的所有作品""" + ids: set[int] = set() + + bookmark_data = await PixivUser.query_bookmarks(uid=uid, rest=rest) + before = min(bookmark_data.total, before) if before is not None else bookmark_data.total + for index in range(0, before // limit + 1): + offset = index * limit + logger.info(f'Querying {rest} bookmark illust from {offset} to {offset + limit}') + bookmark_data = await PixivUser.query_bookmarks(uid=uid, offset=offset, limit=limit, rest=rest) + ids.update(bookmark_data.illust_ids) + + return sorted(ids) + + async def output_follow_main(self) -> None: + """获取已关注用户作品, 导出所有作品原图下载链接""" + # 获取现在最新的一个已关注用户作品, 稍后将写入数据库作为下一次获取的分界线 + illust_result = await PixivUser.query_following_user_latest_illust(page=1) + now_up_pid = illust_result.illust_ids[0] + + # 读取上次截止的最后一个已关注用户作品, 以此为界开始获取本次更新的作品 + last_up_pid = await get_last_follow_illust_pid() + logger.info(f'Last follow artwork up pid: {last_up_pid}, starting get new follow artwork') + + pids = await self._get_new_follow_illust(up_pid=last_up_pid) + logger.info('Querying new follow artwork completed, waiting for rate limiting cooldown...') + await async_sleep(60) + + output_file_name = f'download_url_{datetime.now().strftime("%Y%m%d-%H%M%S")}.txt' + self.set_output_file(category='following', filename=output_file_name) + await self._handle_output_artworks(pids=pids, enable_filter=True) + + await set_last_follow_illust_pid(pid=now_up_pid) + logger.success(f'Follow artwork update is all got completed, this time up pid: {now_up_pid}') + + async def _download_user_artworks(self, user_id: int) -> None: + """下载用户作品""" + + def _rename(user_name: str) -> str: + return re.sub(r'\W', '_', user_name) + + # 获取用户作品 + user_data = await PixivUser(uid=user_id).query_user_data() + logger.info( + f'Querying user(uid={user_id}, {user_data.name}) artworks list completed, ' + f'total: {len(user_data.manga_illusts)}, start query artwork data...' + ) + await async_sleep(30) + + rename_username = _rename(user_data.name) + output_file_name = f'user_{rename_username}({user_id})_artworks_{datetime.now().strftime("%Y%m%d-%H%M%S")}.txt' + self.set_output_file(category='user', filename=output_file_name) + + # 获取用户所有作品信息 + await self._handle_output_artworks(pids=user_data.manga_illusts, enable_filter=False) + + logger.info(f'Querying user(uid={user_id}, {user_data.name}) artworks data completed, start downloading...') + + download_folder = self.get_output_dir()('user', f'{user_id}-{rename_username}') + async with self.output_file.async_open('r', encoding='utf8') as af: + tasks = [ + self.download_any_url(url=url, save_folder=download_folder, ignore_exist_file=True) + for url in await af.readlines() + ] + + await semaphore_gather(tasks=tasks, semaphore_num=8) + logger.success(f'Downloading user(uid={user_id}, {user_data.name}) artworks completed') + + async def download_users_artworks_main(self, user_ids: Sequence[int]) -> None: + for i, user_id in enumerate(user_ids): + try: + logger.info(f'Querying user(uid={user_id}) artworks, now: {i + 1}/{len(user_ids)}') + await self._download_user_artworks(user_id=user_id) + logger.success(f'Downloading user(uid={user_id}) artworks completed') + except Exception as e: + logger.error(f'Downloading user(uid={user_id}) artworks failed, error: {e}') + continue + logger.success('Downloaded all users artworks completed') + + async def _download_bookmark_artworks( + self, + download_dir: 'Path', + uid: int | None = None, + *, + rest: Literal['show', 'hide'] = 'show', + before: int | None = None, + ) -> None: + """下载收藏的全部作品""" + pids = await self._get_all_bookmark_illust(uid=uid, rest=rest, before=before) + + logger.info(f'Querying {rest} bookmark {uid} completed, waiting for rate limiting cooldown...') + await async_sleep(30) + + output_file_name = f'download_url_{uid}_{rest}_bookmark_{datetime.now().strftime("%Y%m%d-%H%M%S")}.txt' + self.set_output_file(category='bookmark', filename=output_file_name) + + # 获取所有作品信息 + await self._handle_output_artworks(pids=pids, enable_filter=False) + + logger.info(f'Querying user(uid={uid}) {rest} bookmark illust data completed, start downloading...') + + download_folder = AnyResource(download_dir) + async with self.output_file.async_open('r', encoding='utf8') as af: + tasks = [ + self.download_any_url(url=url, save_folder=download_folder, ignore_exist_file=True) + for url in await af.readlines() + ] + + await semaphore_gather(tasks=tasks, semaphore_num=8) + logger.success(f'Downloading user(uid={uid}) {rest} bookmark illust completed') + + async def download_bookmark_main( + self, + download_dir: 'Path', + uid: int | None = None, + *, + before: int | None = None, + ): + try: + await self._download_bookmark_artworks(download_dir=download_dir, uid=uid, rest='show', before=before) + except Exception as e: + logger.error(f'Downloading user(uid={uid}) show bookmark illust failed, error: {e}') + + try: + await self._download_bookmark_artworks(download_dir=download_dir, uid=uid, rest='hide', before=before) + except Exception as e: + logger.error(f'Downloading user(uid={uid}) hide bookmark illust failed, error: {e}') + + +__all__ = [ + 'PixivArtworkDownloader', +] diff --git a/tools/pixiv_artwork_downloader/utils.py b/tools/pixiv_artwork_downloader/utils.py new file mode 100644 index 00000000..c100a8f6 --- /dev/null +++ b/tools/pixiv_artwork_downloader/utils.py @@ -0,0 +1,51 @@ +""" +@Author : Ailitonia +@Date : 2024/9/9 00:53 +@FileName : utils +@Project : ailitonia-toolkit +@Description : +@GitHub : https://github.com/Ailitonia +@Software : PyCharm +""" + +from datetime import datetime + +from nonebot.log import logger +from sqlalchemy.exc import NoResultFound + +from src.database import SystemSettingDAL, begin_db_session +from .consts import DOWNLOADER_SETTING_NAME, LAST_FOLLOWING_SETTING_KEY + + +async def set_last_follow_illust_pid(pid: int) -> None: + """保存上次关注用户的最新作品""" + async with begin_db_session() as session: + await SystemSettingDAL(session=session).upsert( + setting_name=DOWNLOADER_SETTING_NAME, + setting_key=LAST_FOLLOWING_SETTING_KEY, + setting_value=str(pid), + info=f'last scan time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}', + ) + + +async def get_last_follow_illust_pid() -> int | None: + """读取上次关注用户的最新作品""" + async with begin_db_session() as session: + try: + setting = await SystemSettingDAL(session=session).query_unique( + setting_name=DOWNLOADER_SETTING_NAME, + setting_key=LAST_FOLLOWING_SETTING_KEY, + ) + last_pid = int(setting.setting_value) + info = setting.info + except NoResultFound: + last_pid = None + info = 'No result found' + logger.info(f'Read last scan pid: {last_pid}, {info}') + return last_pid + + +__all__ = [ + 'set_last_follow_illust_pid', + 'get_last_follow_illust_pid', +]