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',
+]