mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83bcd14012 | |||
| 70f53a2c76 | |||
| 6df70f621e | |||
| 8ba3f3c3f4 | |||
| a5c4e7df5c | |||
| 66db7a8a28 | |||
|
|
716e392a3a | ||
|
|
e47f1bb6f9 | ||
| 03d34c5572 | |||
| 04b480ef52 | |||
| 5563b01937 | |||
| 504edb08de | |||
| c283f1ca49 | |||
| 0171953264 | |||
| 7515daccc7 | |||
| 17690e673f | |||
| e9b3c30a13 | |||
| 42484b9c2c | |||
| 42828f23f6 | |||
| d0af2e83c4 | |||
| 5534456b22 | |||
|
|
1928506021 | ||
|
|
67da935849 | ||
|
|
e1e8743c48 | ||
| e5556bad1d | |||
| 889405ea6b | |||
| 66e1850297 | |||
| f39faced7e | |||
| fffa07dc03 | |||
| 0467b3e5df | |||
| f6cc0229ba | |||
| e2708b661d | |||
| 65d019a6d3 | |||
| be1b07d5dc | |||
| c92bc3aaad | |||
| d4b887ef83 | |||
|
|
695ff13aa2 | ||
| ec1001b3bb | |||
| b545b12255 | |||
| b2505e0979 | |||
| 38defe37cd | |||
| 7a3d7c908c | |||
| bc37a015d6 | |||
|
|
fd85140c99 | ||
|
|
80f4316564 | ||
|
|
3b9c0c89b1 | ||
| c02fdfc47f | |||
| 93b169fa40 | |||
| 5cb428ed71 | |||
|
|
ec392ee384 | ||
|
|
d037cf6d44 | ||
|
|
6964e9b655 | ||
|
|
7a032bf947 | ||
|
|
9a91e5ef5b | ||
|
|
5b58697fce | ||
|
|
b14cebe832 | ||
|
|
4306195ee5 | ||
|
|
ac9c6e79d9 | ||
|
|
ed035c65c1 | ||
| dc8bc9b306 | |||
| 454dd57007 | |||
| b396a6d450 | |||
| 7f584a46eb | |||
| 27518c0408 | |||
|
|
d2a3801dac | ||
| 563564ac8d | |||
| 87c87ad231 | |||
| 30515d1907 | |||
|
|
bd0a8ea447 | ||
|
|
1db1e6dbba | ||
|
|
9040aa9fba |
11
.gitignore
vendored
11
.gitignore
vendored
@@ -8,3 +8,14 @@ Untitled*
|
||||
*_cache*
|
||||
*backup*
|
||||
*.pyc
|
||||
node_modules
|
||||
.prettier*
|
||||
package.json
|
||||
pnpm-lock.yaml
|
||||
*.drawio.svg
|
||||
package-lock.json
|
||||
*Zone.Identifier
|
||||
.env*
|
||||
bot.py
|
||||
TODO
|
||||
*.fish
|
||||
|
||||
@@ -13,10 +13,11 @@ __plugin_meta__ = PluginMetadata(
|
||||
description='一个用于查询 Tetris 相关游戏玩家数据的插件',
|
||||
usage='发送 {游戏名} --help 查询使用方法',
|
||||
type='application',
|
||||
homepage='https://github.com/shoucandanghehe/nonebot-plugin-tetris-stats',
|
||||
homepage='https://github.com/A-minos/nonebot-plugin-tetris-stats',
|
||||
extra={
|
||||
'orm_version_location': migrations,
|
||||
},
|
||||
)
|
||||
|
||||
from . import game_data_processor # noqa: F401, E402
|
||||
from .utils import host # noqa: F401, E402
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Rename field
|
||||
|
||||
迁移 ID: 09d4bb60160d
|
||||
父迁移: b9d65badc713
|
||||
创建时间: 2024-04-23 23:42:04.541672
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = '09d4bb60160d'
|
||||
down_revision: str | Sequence[str] | None = 'b9d65badc713'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||
batch_op.alter_column('create_time', new_column_name='update_time', existing_type=sa.DateTime())
|
||||
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_create_time')
|
||||
op.create_index(
|
||||
batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_update_time'),
|
||||
'nonebot_plugin_tetris_stats_iorank',
|
||||
['update_time'],
|
||||
unique=False,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||
batch_op.alter_column('update_time', new_column_name='create_time')
|
||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_update_time'))
|
||||
op.create_index(
|
||||
'ix_nonebot_plugin_tetris_stats_iorank_create_time',
|
||||
'nonebot_plugin_tetris_stats_iorank',
|
||||
['create_time'],
|
||||
unique=False,
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,43 @@
|
||||
"""add field
|
||||
|
||||
迁移 ID: 0d50142b780f
|
||||
父迁移: 09d4bb60160d
|
||||
创建时间: 2024-04-24 14:55:08.064098
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = '0d50142b780f'
|
||||
down_revision: str | Sequence[str] | None = '09d4bb60160d'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('file_hash', sa.String(length=128), nullable=True))
|
||||
batch_op.create_index(
|
||||
batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_file_hash'), ['file_hash'], unique=False
|
||||
)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_file_hash'))
|
||||
batch_op.drop_column('file_hash')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Correct the data in HistoricalData
|
||||
|
||||
迁移 ID: 8a91210ce14d
|
||||
父迁移: 0d50142b780f
|
||||
创建时间: 2024-05-06 08:16:38.487214
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from alembic import op
|
||||
from nonebot.log import logger
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.automap import automap_base
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
revision: str = '8a91210ce14d'
|
||||
down_revision: str | Sequence[str] | None = '0d50142b780f'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
from nonebot_plugin_tetris_stats.version import __version__
|
||||
|
||||
if __version__ != '1.0.3':
|
||||
logger.critical('本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移')
|
||||
raise RuntimeError('本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移')
|
||||
|
||||
from nonebot.compat import PYDANTIC_V2, type_validate_json
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from rich.progress import (
|
||||
BarColumn,
|
||||
MofNCompleteColumn,
|
||||
Progress,
|
||||
TaskProgressColumn,
|
||||
TextColumn,
|
||||
TimeRemainingColumn,
|
||||
)
|
||||
|
||||
from nonebot_plugin_tetris_stats.game_data_processor.schemas import BaseProcessedData
|
||||
|
||||
Base = automap_base() # noqa: N806
|
||||
Base.prepare(autoload_with=op.get_bind())
|
||||
HistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
|
||||
if PYDANTIC_V2:
|
||||
|
||||
def model_to_json(value: BaseModel) -> str:
|
||||
return value.model_dump_json(by_alias=True)
|
||||
else:
|
||||
|
||||
def model_to_json(value: BaseModel) -> str:
|
||||
return value.json(by_alias=True)
|
||||
|
||||
models = BaseProcessedData.__subclasses__()
|
||||
|
||||
def json_to_model(value: str) -> BaseModel:
|
||||
for i in models:
|
||||
try:
|
||||
return type_validate_json(i, value)
|
||||
except ValidationError: # noqa: PERF203
|
||||
...
|
||||
raise ValueError
|
||||
|
||||
with Session(op.get_bind()) as session:
|
||||
count = session.query(HistoricalData).count()
|
||||
with Progress(
|
||||
TextColumn('[progress.description]{task.description}'),
|
||||
BarColumn(),
|
||||
MofNCompleteColumn(),
|
||||
TaskProgressColumn(),
|
||||
TimeRemainingColumn(),
|
||||
) as progress:
|
||||
task_id = progress.add_task('[cyan]Updateing:', total=count)
|
||||
for i in range(0, count, 100):
|
||||
for j in session.scalars(
|
||||
select(HistoricalData).where(HistoricalData.id > i).order_by(HistoricalData.id).limit(100)
|
||||
):
|
||||
model = json_to_model(j.processed_data)
|
||||
j.processed_data = model_to_json(model)
|
||||
progress.update(task_id, advance=1)
|
||||
session.commit()
|
||||
logger.success('Corrected HistoricalData')
|
||||
|
||||
|
||||
def downgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Add user_unique_identifier field to HistoricalData
|
||||
|
||||
迁移 ID: b7fbdafc339a
|
||||
父迁移: 8a91210ce14d
|
||||
创建时间: 2024-05-07 16:55:29.527215
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from nonebot.log import logger
|
||||
|
||||
revision: str = 'b7fbdafc339a'
|
||||
down_revision: str | Sequence[str] | None = '8a91210ce14d'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
from nonebot_plugin_tetris_stats.version import __version__
|
||||
|
||||
if __version__ != '1.0.4':
|
||||
logger.critical('本迁移需要1.0.4版本, 请先锁定版本至1.0.4版本再执行本迁移')
|
||||
raise RuntimeError('本迁移需要1.0.4版本, 请先锁定版本至1.0.4版本再执行本迁移')
|
||||
from nonebot.compat import type_validate_json
|
||||
from pydantic import ValidationError
|
||||
from rich.progress import (
|
||||
BarColumn,
|
||||
MofNCompleteColumn,
|
||||
Progress,
|
||||
TaskProgressColumn,
|
||||
TextColumn,
|
||||
TimeRemainingColumn,
|
||||
)
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.automap import automap_base
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from nonebot_plugin_tetris_stats.game_data_processor.schemas import BaseUser
|
||||
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('user_unique_identifier', sa.String(length=32), nullable=True))
|
||||
batch_op.create_index(
|
||||
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_user_unique_identifier'),
|
||||
['user_unique_identifier'],
|
||||
unique=False,
|
||||
)
|
||||
Base = automap_base() # noqa: N806
|
||||
connection = op.get_bind()
|
||||
Base.prepare(autoload_with=connection)
|
||||
HistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
|
||||
|
||||
models: list[type[BaseUser]] = BaseUser.__subclasses__()
|
||||
|
||||
def json_to_model(value: str) -> BaseUser:
|
||||
for i in models:
|
||||
try:
|
||||
return type_validate_json(i, value)
|
||||
except ValidationError: # noqa: PERF203
|
||||
...
|
||||
raise ValueError
|
||||
|
||||
with Session(op.get_bind()) as session:
|
||||
count = session.query(HistoricalData).count()
|
||||
with Progress(
|
||||
TextColumn('[progress.description]{task.description}'),
|
||||
BarColumn(),
|
||||
MofNCompleteColumn(),
|
||||
TaskProgressColumn(),
|
||||
TimeRemainingColumn(),
|
||||
) as progress:
|
||||
task_id = progress.add_task('[cyan]Updateing:', total=count)
|
||||
for i in range(0, count, 100):
|
||||
for j in session.scalars(
|
||||
select(HistoricalData).where(HistoricalData.id > i).order_by(HistoricalData.id).limit(100)
|
||||
):
|
||||
model = json_to_model(j.game_user)
|
||||
try:
|
||||
j.user_unique_identifier = model.unique_identifier
|
||||
except ValueError:
|
||||
session.delete(j)
|
||||
progress.update(task_id, advance=1)
|
||||
session.commit()
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
||||
batch_op.alter_column('user_unique_identifier', existing_type=sa.VARCHAR(length=32), nullable=False)
|
||||
logger.success('database upgrade success')
|
||||
|
||||
|
||||
def downgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_user_unique_identifier'))
|
||||
batch_op.drop_column('user_unique_identifier')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,3 +1,5 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
from nonebot_plugin_orm import AsyncSession
|
||||
from sqlalchemy import select
|
||||
|
||||
@@ -5,6 +7,11 @@ from ..utils.typing import GameType
|
||||
from .models import Bind
|
||||
|
||||
|
||||
class BindStatus(Enum):
|
||||
SUCCESS = auto()
|
||||
UPDATE = auto()
|
||||
|
||||
|
||||
async def query_bind_info(
|
||||
session: AsyncSession,
|
||||
chat_platform: str,
|
||||
@@ -27,7 +34,7 @@ async def create_or_update_bind(
|
||||
chat_account: str,
|
||||
game_platform: GameType,
|
||||
game_account: str,
|
||||
) -> str:
|
||||
) -> BindStatus:
|
||||
bind = await query_bind_info(
|
||||
session=session,
|
||||
chat_platform=chat_platform,
|
||||
@@ -42,9 +49,9 @@ async def create_or_update_bind(
|
||||
game_account=game_account,
|
||||
)
|
||||
session.add(bind)
|
||||
message = '绑定成功'
|
||||
message = BindStatus.SUCCESS
|
||||
else:
|
||||
bind.game_account = game_account
|
||||
message = '更新绑定成功'
|
||||
message = BindStatus.UPDATE
|
||||
await session.commit()
|
||||
return message
|
||||
|
||||
@@ -3,10 +3,12 @@ from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from nonebot.adapters import Message
|
||||
from nonebot.compat import PYDANTIC_V2, type_validate_json
|
||||
from nonebot_plugin_orm import Model
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from sqlalchemy import JSON, DateTime, Dialect, PickleType, String, TypeDecorator
|
||||
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
||||
from typing_extensions import override
|
||||
|
||||
from ..game_data_processor.schemas import BaseProcessedData, BaseUser
|
||||
from ..utils.typing import CommandType, GameType
|
||||
@@ -15,25 +17,38 @@ from ..utils.typing import CommandType, GameType
|
||||
class PydanticType(TypeDecorator):
|
||||
impl = JSON
|
||||
|
||||
def __init__(self, get_model: Callable[[], Sequence[type[BaseModel]]], *args: Any, **kwargs: Any): # noqa: ANN401
|
||||
@override
|
||||
def __init__(self, get_model: Callable[[], Sequence[type[BaseModel]]], *args: Any, **kwargs: Any):
|
||||
self.get_model = get_model
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str: # noqa: ANN401
|
||||
# 将 Pydantic 模型实例转换为 JSON
|
||||
if isinstance(value, tuple(self.get_model())):
|
||||
return value.json() # type: ignore[union-attr]
|
||||
raise TypeError
|
||||
if PYDANTIC_V2:
|
||||
|
||||
def process_result_value(self, value: Any | None, dialect: Dialect) -> BaseModel: # noqa: ANN401
|
||||
@override
|
||||
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str:
|
||||
# 将 Pydantic 模型实例转换为 JSON
|
||||
if isinstance(value, tuple(self.get_model())):
|
||||
return value.model_dump_json(by_alias=True) # type: ignore[union-attr]
|
||||
raise TypeError
|
||||
else:
|
||||
|
||||
@override
|
||||
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str:
|
||||
# 将 Pydantic 模型实例转换为 JSON
|
||||
if isinstance(value, tuple(self.get_model())):
|
||||
return value.json(by_alias=True) # type: ignore[union-attr]
|
||||
raise TypeError
|
||||
|
||||
@override
|
||||
def process_result_value(self, value: Any | None, dialect: Dialect) -> BaseModel:
|
||||
# 将 JSON 转换回 Pydantic 模型实例
|
||||
if isinstance(value, str | bytes):
|
||||
for i in self.get_model():
|
||||
try:
|
||||
return i.parse_raw(value)
|
||||
return type_validate_json(i, value)
|
||||
except ValidationError: # noqa: PERF203
|
||||
...
|
||||
raise TypeError
|
||||
raise ValueError
|
||||
|
||||
|
||||
class Bind(MappedAsDataclass, Model):
|
||||
@@ -55,6 +70,7 @@ class HistoricalData(MappedAsDataclass, Model):
|
||||
game_platform: Mapped[GameType] = mapped_column(String(32), index=True, init=False)
|
||||
command_type: Mapped[CommandType] = mapped_column(String(16), index=True, init=False)
|
||||
command_args: Mapped[list[str]] = mapped_column(JSON, init=False)
|
||||
user_unique_identifier: Mapped[str] = mapped_column(String(32), index=True, init=False)
|
||||
game_user: Mapped[BaseUser] = mapped_column(PydanticType(get_model=BaseUser.__subclasses__), init=False)
|
||||
processed_data: Mapped[BaseProcessedData] = mapped_column(
|
||||
PydanticType(get_model=BaseProcessedData.__subclasses__), init=False
|
||||
|
||||
@@ -4,6 +4,8 @@ from typing import Any
|
||||
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import AlcMatches, AlconnaMatcher
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
|
||||
|
||||
from ..utils.exception import MessageFormatError
|
||||
from ..utils.recorder import Recorder
|
||||
@@ -41,20 +43,22 @@ class Processor(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def handle_bind(self, platform: str, account: str) -> str:
|
||||
async def handle_bind(
|
||||
self,
|
||||
platform: str,
|
||||
account: str,
|
||||
bot_info: UserInfo,
|
||||
*args: Any, # noqa: ANN401
|
||||
**kwargs: Any, # noqa: ANN401
|
||||
) -> UniMessage:
|
||||
"""处理绑定消息"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def handle_query(self) -> str:
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __del__(self) -> None:
|
||||
finish_time = datetime.now(tz=UTC)
|
||||
if Recorder.is_error_event(self.event_id):
|
||||
@@ -64,6 +68,7 @@ class Processor(ABC):
|
||||
historical_data.game_platform = self.game_platform
|
||||
historical_data.command_type = self.command_type
|
||||
historical_data.command_args = self.command_args
|
||||
historical_data.user_unique_identifier = self.user.unique_identifier
|
||||
historical_data.game_user = self.user
|
||||
historical_data.processed_data = self.processed_data
|
||||
historical_data.finish_time = finish_time
|
||||
|
||||
@@ -5,7 +5,9 @@ from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, O
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
|
||||
from sqlalchemy import func, select
|
||||
|
||||
from ...db import query_bind_info
|
||||
@@ -86,14 +88,12 @@ alc.shortcut('fkosk', {'command': 'io查', 'args': ['我']})
|
||||
|
||||
|
||||
@alc.assign('bind')
|
||||
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
|
||||
proc = Processor(
|
||||
event_id=id(event),
|
||||
user=account,
|
||||
command_args=[],
|
||||
)
|
||||
async def _(bot: Bot, event: Event, matcher: Matcher, account: User, bot_info: UserInfo = BotUserInfo()): # noqa: B008
|
||||
proc = Processor(event_id=id(event), user=account, command_args=[])
|
||||
try:
|
||||
await matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
|
||||
await (
|
||||
await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info)
|
||||
).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
@@ -117,7 +117,7 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(message + await proc.handle_query())
|
||||
await (UniMessage(message) + await proc.handle_query()).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
@@ -131,7 +131,7 @@ async def _(event: Event, matcher: Matcher, account: User):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
@@ -151,19 +151,19 @@ async def _(matcher: Matcher, rank: Rank):
|
||||
.where(IORank.rank == rank)
|
||||
.order_by(
|
||||
func.abs(
|
||||
func.julianday(IORank.create_time)
|
||||
- func.julianday(latest_data.create_time - timedelta(hours=24))
|
||||
func.julianday(IORank.update_time)
|
||||
- func.julianday(latest_data.update_time - timedelta(hours=24))
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
)
|
||||
).one()
|
||||
message = ''
|
||||
if (datetime.now(UTC) - latest_data.create_time.replace(tzinfo=UTC)) > timedelta(hours=7):
|
||||
if (datetime.now(UTC) - latest_data.update_time.replace(tzinfo=UTC)) > timedelta(hours=7):
|
||||
message += 'Warning: 数据超过7小时未更新, 请联系Bot主人查看后台\n'
|
||||
message += f'{rank.upper()} 段 分数线 {latest_data.tr_line:.2f} TR, {latest_data.player_count} 名玩家\n'
|
||||
if compare_data.id != latest_data.id:
|
||||
message += f'对比 {(latest_data.create_time-compare_data.create_time).total_seconds()/3600:.2f} 小时前趋势: {f"↑{difference:.2f}" if (difference:=latest_data.tr_line-compare_data.tr_line) > 0 else f"↓{-difference:.2f}" if difference < 0 else "→"}'
|
||||
message += f'对比 {(latest_data.update_time-compare_data.update_time).total_seconds()/3600:.2f} 小时前趋势: {f"↑{difference:.2f}" if (difference:=latest_data.tr_line-compare_data.tr_line) > 0 else f"↓{-difference:.2f}" if difference < 0 else "→"}'
|
||||
else:
|
||||
message += '暂无对比数据'
|
||||
avg = get_metrics(pps=latest_data.avg_pps, apm=latest_data.avg_apm, vs=latest_data.avg_vs)
|
||||
@@ -188,7 +188,7 @@ async def _(matcher: Matcher, rank: Rank):
|
||||
f'APM: {latest_data.high_apm[1]} By: {latest_data.high_apm[0]["name"].upper()}\n'
|
||||
f'ADPM: {max_vs.adpm} ( {max_vs.vs}vs ) By: {latest_data.high_vs[0]["name"].upper()}\n'
|
||||
'\n'
|
||||
f'数据更新时间: {latest_data.create_time.replace(tzinfo=UTC).astimezone(ZoneInfo("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")}'
|
||||
f'数据更新时间: {latest_data.update_time.replace(tzinfo=UTC).astimezone(ZoneInfo("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")}'
|
||||
)
|
||||
await matcher.finish(message)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from aiocache import Cache as ACache # type: ignore[import-untyped]
|
||||
from nonebot.compat import type_validate_json
|
||||
from nonebot.log import logger
|
||||
from pydantic import parse_raw_as
|
||||
|
||||
from ...utils.request import Request
|
||||
from .schemas.base import FailedModel, SuccessModel
|
||||
@@ -18,7 +18,7 @@ class Cache:
|
||||
cached_data = await cls.cache.get(url)
|
||||
if cached_data is None:
|
||||
response_data = await Request.request(url)
|
||||
parsed_data: SuccessModel | FailedModel = parse_raw_as(SuccessModel | FailedModel, response_data) # type: ignore[arg-type]
|
||||
parsed_data: SuccessModel | FailedModel = type_validate_json(SuccessModel | FailedModel, response_data) # type: ignore[arg-type]
|
||||
if isinstance(parsed_data, SuccessModel):
|
||||
await cls.cache.add(
|
||||
url,
|
||||
|
||||
@@ -23,3 +23,5 @@ RANK_PERCENTILE: dict[Rank, float] = {
|
||||
'd+': 97.5,
|
||||
'd': 100,
|
||||
}
|
||||
TR_MIN = 0
|
||||
TR_MAX = 25000
|
||||
|
||||
@@ -23,9 +23,8 @@ class IORank(MappedAsDataclass, Model):
|
||||
high_pps: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
|
||||
high_apm: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
|
||||
high_vs: Mapped[tuple[dict[str, str], float]] = mapped_column(JSON)
|
||||
create_time: Mapped[datetime] = mapped_column(
|
||||
update_time: Mapped[datetime] = mapped_column(
|
||||
DateTime,
|
||||
default=lambda: datetime.now(tz=UTC),
|
||||
index=True,
|
||||
init=False,
|
||||
)
|
||||
file_hash: Mapped[str | None] = mapped_column(String(128), index=True)
|
||||
|
||||
@@ -1,35 +1,49 @@
|
||||
from asyncio import gather
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import floor
|
||||
from hashlib import md5, sha512
|
||||
from math import ceil, floor
|
||||
from re import match
|
||||
from statistics import mean
|
||||
from typing import Literal
|
||||
from urllib.parse import urlunparse
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from aiofiles import open
|
||||
from nonebot import get_driver
|
||||
from nonebot.compat import type_validate_json
|
||||
from nonebot.utils import run_sync
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_apscheduler import scheduler # type: ignore[import-untyped]
|
||||
from nonebot_plugin_localstore import get_data_file # type: ignore[import-untyped]
|
||||
from nonebot_plugin_orm import get_session
|
||||
from pydantic import parse_raw_as
|
||||
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
|
||||
from sqlalchemy import select
|
||||
from typing_extensions import override
|
||||
from zstandard import ZstdCompressor
|
||||
|
||||
from ...db import create_or_update_bind
|
||||
from ...db import BindStatus, create_or_update_bind
|
||||
from ...db.models import HistoricalData
|
||||
from ...utils.avatar import get_avatar
|
||||
from ...utils.exception import MessageFormatError, RequestError, WhatTheFuckError
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.render import Bind, TETRIOInfo, render
|
||||
from ...utils.request import splice_url
|
||||
from ...utils.retry import retry
|
||||
from ...utils.screenshot import screenshot
|
||||
from .. import Processor as ProcessorMeta
|
||||
from .cache import Cache
|
||||
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
|
||||
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE, TR_MAX, TR_MIN
|
||||
from .model import IORank
|
||||
from .schemas.league_all import FailedModel as LeagueAllFailed
|
||||
from .schemas.base import FailedModel
|
||||
from .schemas.league_all import LeagueAll
|
||||
from .schemas.league_all import ValidUser as LeagueAllUser
|
||||
from .schemas.response import ProcessedData, RawResponse
|
||||
from .schemas.user import User
|
||||
from .schemas.user_info import FailedModel as InfoFailed
|
||||
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, UserInfo
|
||||
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, RatedLeague, UserInfo
|
||||
from .schemas.user_info import SuccessModel as InfoSuccess
|
||||
from .schemas.user_records import FailedModel as RecordsFailed
|
||||
from .schemas.user_records import SoloRecord, UserRecords
|
||||
from .schemas.user_records import MultiRecord, SoloRecord, UserRecords
|
||||
from .schemas.user_records import SuccessModel as RecordsSuccess
|
||||
from .typing import Rank
|
||||
|
||||
@@ -46,79 +60,131 @@ def identify_user_info(info: str) -> User | MessageFormatError:
|
||||
return MessageFormatError('用户名/ID不合法')
|
||||
|
||||
|
||||
def get_value_bounds(values: list[int | float]) -> tuple[int, int]:
|
||||
value_max = 10 * ceil(max(values) / 10)
|
||||
value_min = 10 * floor(min(values) / 10)
|
||||
return value_max, value_min
|
||||
|
||||
|
||||
def get_split(value_max: int, value_min: int) -> tuple[int, int]:
|
||||
offset = 0
|
||||
overflow = 0
|
||||
|
||||
while True:
|
||||
if (new_max_value := value_max + offset + overflow) > TR_MAX:
|
||||
overflow -= 1
|
||||
continue
|
||||
if (new_min_value := value_min - offset + overflow) < TR_MIN:
|
||||
overflow += 1
|
||||
continue
|
||||
if ((new_max_value - new_min_value) / 40).is_integer():
|
||||
split_value = int((value_max + offset - (value_min - offset)) / 4)
|
||||
break
|
||||
offset += 1
|
||||
return split_value, offset + overflow
|
||||
|
||||
|
||||
def get_specified_point(
|
||||
previous_point: TETRIOInfo.TetraLeagueHistory.Data,
|
||||
behind_point: TETRIOInfo.TetraLeagueHistory.Data,
|
||||
point_time: datetime,
|
||||
) -> TETRIOInfo.TetraLeagueHistory.Data:
|
||||
"""根据给出的 previous_point 和 behind_point, 推算 point_time 点处的数据
|
||||
|
||||
Args:
|
||||
previous_point (TETRIOInfo.TetraLeagueHistory.Data): 前面的数据点
|
||||
behind_point (TETRIOInfo.TetraLeagueHistory.Data): 后面的数据点
|
||||
point_time (datetime): 要推算的点的位置
|
||||
|
||||
Returns:
|
||||
TETRIOInfo.TetraLeagueHistory.Data: 要推算的点的数据
|
||||
"""
|
||||
# 求两个点的斜率
|
||||
slope = (behind_point.tr - previous_point.tr) / (
|
||||
datetime.timestamp(behind_point.record_at) - datetime.timestamp(previous_point.record_at)
|
||||
)
|
||||
return TETRIOInfo.TetraLeagueHistory.Data(
|
||||
record_at=point_time,
|
||||
tr=previous_point.tr + slope * (datetime.timestamp(point_time) - datetime.timestamp(previous_point.record_at)),
|
||||
)
|
||||
|
||||
|
||||
class Processor(ProcessorMeta):
|
||||
user: User
|
||||
raw_response: RawResponse
|
||||
processed_data: ProcessedData
|
||||
|
||||
@override
|
||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||
super().__init__(event_id, user, command_args)
|
||||
self.raw_response = RawResponse()
|
||||
self.processed_data = ProcessedData()
|
||||
|
||||
@property
|
||||
@override
|
||||
def game_platform(self) -> Literal['IO']:
|
||||
return GAME_TYPE
|
||||
|
||||
async def handle_bind(self, platform: str, account: str) -> str:
|
||||
@override
|
||||
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
|
||||
"""处理绑定消息"""
|
||||
self.command_type = 'bind'
|
||||
await self.get_user()
|
||||
if self.user.ID is None:
|
||||
raise # FIXME: 不知道怎么才能把这类型给变过来了
|
||||
async with get_session() as session:
|
||||
return await create_or_update_bind(
|
||||
bind_status = await create_or_update_bind(
|
||||
session=session,
|
||||
chat_platform=platform,
|
||||
chat_account=account,
|
||||
game_platform=GAME_TYPE,
|
||||
game_account=self.user.ID,
|
||||
)
|
||||
bot_avatar = await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg')
|
||||
user_info = await self.get_user_info()
|
||||
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||
async with HostPage(
|
||||
await render(
|
||||
'binding',
|
||||
Bind(
|
||||
platform='TETR.IO',
|
||||
status='unknown',
|
||||
user=Bind.People(
|
||||
avatar=f'https://tetr.io/user-content/avatars/{user_info.data.user.id}.jpg?rv={user_info.data.user.avatar_revision}'
|
||||
if user_info.data.user.avatar_revision is not None
|
||||
else f'{{"type":"identicon","hash":"{md5(user_info.data.user.id.encode()).hexdigest()}"}}', # noqa: S324
|
||||
name=user_info.data.user.username.upper(),
|
||||
),
|
||||
bot=Bind.People(
|
||||
avatar=bot_avatar,
|
||||
name=bot_info.user_name,
|
||||
),
|
||||
command='io查我',
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
message = UniMessage.image(
|
||||
raw=await screenshot(urlunparse(('http', get_self_netloc(), f'/host/{page_hash}.html', '', '', '')))
|
||||
)
|
||||
return message
|
||||
|
||||
async def handle_query(self) -> str:
|
||||
@override
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
self.command_type = 'query'
|
||||
await self.get_user()
|
||||
return await self.generate_message()
|
||||
|
||||
async def get_user(self) -> None:
|
||||
"""
|
||||
用于获取 UserName 和 UserID 的函数
|
||||
"""
|
||||
if self.user.name is None:
|
||||
self.user.name = (await self.get_user_info()).data.user.username
|
||||
if self.user.ID is None:
|
||||
self.user.ID = (await self.get_user_info()).data.user.id
|
||||
|
||||
async def get_user_info(self) -> InfoSuccess:
|
||||
"""获取用户数据"""
|
||||
if self.processed_data.user_info is None:
|
||||
self.raw_response.user_info = await Cache.get(
|
||||
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}'])
|
||||
)
|
||||
user_info: UserInfo = parse_raw_as(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
|
||||
if isinstance(user_info, InfoFailed):
|
||||
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
|
||||
self.processed_data.user_info = user_info
|
||||
return self.processed_data.user_info
|
||||
|
||||
async def get_user_records(self) -> RecordsSuccess:
|
||||
"""获取Solo数据"""
|
||||
if self.processed_data.user_records is None:
|
||||
self.raw_response.user_records = await Cache.get(
|
||||
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}/', 'records'])
|
||||
)
|
||||
user_records: UserRecords = parse_raw_as(UserRecords, self.raw_response.user_records) # type: ignore[arg-type]
|
||||
if isinstance(user_records, RecordsFailed):
|
||||
raise RequestError(f'用户Solo数据请求错误:\n{user_records.error}')
|
||||
self.processed_data.user_records = user_records
|
||||
return self.processed_data.user_records
|
||||
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
user_info = await self.get_user_info()
|
||||
user_name = user_info.data.user.username.upper()
|
||||
user_info, user_records = await gather(self.get_user_info(), self.get_user_records())
|
||||
sprint = user_records.data.records.sprint
|
||||
blitz = user_records.data.records.blitz
|
||||
if isinstance(sprint.record, MultiRecord) or isinstance(blitz.record, MultiRecord):
|
||||
raise WhatTheFuckError('单人游戏记录是多人游戏记录')
|
||||
try:
|
||||
return UniMessage.image(raw=await self.make_query_image(self.user, user_info, sprint.record, blitz.record))
|
||||
except TypeError:
|
||||
...
|
||||
# fallback
|
||||
league = user_info.data.user.league
|
||||
user_name = user_info.data.user.username.upper()
|
||||
ret_message = ''
|
||||
if isinstance(league, NeverPlayedLeague):
|
||||
ret_message += f'用户 {user_name} 没有排位统计数据'
|
||||
@@ -135,31 +201,194 @@ class Processor(ProcessorMeta):
|
||||
ret_message += f', 段位分 {round(league.glicko,2)}±{round(league.rd,2)}, 最近十场的数据:'
|
||||
lpm = league.pps * 24
|
||||
ret_message += f"\nL'PM: {round(lpm, 2)} ( {league.pps} pps )"
|
||||
ret_message += f'\nAPM: {league.apm} ( x{round(league.apm/(league.pps*24),2)} )'
|
||||
ret_message += f'\nAPM: {league.apm} ( x{round(league.apm/lpm,2)} )'
|
||||
if league.vs is not None:
|
||||
adpm = league.vs * 0.6
|
||||
ret_message += f'\nADPM: {round(adpm,2)} ( x{round(adpm/lpm,2)} ) ( {league.vs}vs )'
|
||||
user_records = await self.get_user_records()
|
||||
sprint = user_records.data.records.sprint
|
||||
if sprint.record is not None:
|
||||
if not isinstance(sprint.record, SoloRecord):
|
||||
raise WhatTheFuckError('40L记录不是单人记录')
|
||||
ret_message += f'\n40L: {round(sprint.record.endcontext.final_time/1000,2)}s'
|
||||
ret_message += f' ( #{sprint.rank} )' if sprint.rank is not None else ''
|
||||
blitz = user_records.data.records.blitz
|
||||
if blitz.record is not None:
|
||||
if not isinstance(blitz.record, SoloRecord):
|
||||
raise WhatTheFuckError('Blitz记录不是单人记录')
|
||||
ret_message += f'\nBlitz: {blitz.record.endcontext.score}'
|
||||
ret_message += f' ( #{blitz.rank} )' if blitz.rank is not None else ''
|
||||
return ret_message
|
||||
return UniMessage(ret_message)
|
||||
|
||||
@staticmethod
|
||||
async def query_historical_data(user: User, user_info: InfoSuccess) -> list[TETRIOInfo.TetraLeagueHistory.Data]:
|
||||
today = datetime.now(ZoneInfo('Asia/Shanghai')).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
forward = timedelta(days=9)
|
||||
start_time = (today - forward).astimezone(UTC)
|
||||
async with get_session() as session:
|
||||
historical_data = (
|
||||
await session.scalars(
|
||||
select(HistoricalData)
|
||||
.where(HistoricalData.trigger_time >= start_time)
|
||||
.where(HistoricalData.game_platform == GAME_TYPE)
|
||||
.where(HistoricalData.user_unique_identifier == user.unique_identifier)
|
||||
)
|
||||
).all()
|
||||
if historical_data:
|
||||
extra = (
|
||||
await session.scalars(
|
||||
select(HistoricalData)
|
||||
.where(HistoricalData.game_platform == GAME_TYPE)
|
||||
.where(HistoricalData.user_unique_identifier == user.unique_identifier)
|
||||
.order_by(HistoricalData.id.desc())
|
||||
.where(HistoricalData.id < min([i.id for i in historical_data]))
|
||||
.limit(1)
|
||||
)
|
||||
).one_or_none()
|
||||
if extra is not None:
|
||||
historical_data = list(historical_data)
|
||||
historical_data.append(extra)
|
||||
|
||||
histories = [
|
||||
TETRIOInfo.TetraLeagueHistory.Data(
|
||||
record_at=i.processed_data.user_info.cache.cached_at.astimezone(ZoneInfo('Asia/Shanghai')),
|
||||
tr=i.processed_data.user_info.data.user.league.rating,
|
||||
)
|
||||
for i in historical_data
|
||||
if isinstance(i.processed_data, ProcessedData)
|
||||
and i.processed_data.user_info is not None
|
||||
and isinstance(i.processed_data.user_info.data.user.league, RatedLeague)
|
||||
]
|
||||
|
||||
# 按照时间排序
|
||||
histories = sorted(histories, key=lambda x: x.record_at)
|
||||
for index, value in enumerate(histories):
|
||||
# 在历史记录里找有没有今天0点后的数据
|
||||
if value.record_at > today:
|
||||
histories = histories[:index] + [
|
||||
get_specified_point(histories[index - 1], histories[index], today.replace(microsecond=1000))
|
||||
]
|
||||
break
|
||||
else:
|
||||
histories.append(
|
||||
get_specified_point(
|
||||
histories[-1],
|
||||
TETRIOInfo.TetraLeagueHistory.Data(
|
||||
record_at=user_info.cache.cached_at, tr=user_info.data.user.league.rating
|
||||
),
|
||||
today.replace(microsecond=1000),
|
||||
)
|
||||
)
|
||||
if histories[0].record_at < (today - forward):
|
||||
histories[0] = get_specified_point(
|
||||
histories[0],
|
||||
histories[1],
|
||||
today - forward,
|
||||
)
|
||||
else:
|
||||
histories.insert(0, TETRIOInfo.TetraLeagueHistory.Data(record_at=today - forward, tr=histories[0].tr))
|
||||
return histories
|
||||
|
||||
@staticmethod
|
||||
async def make_query_image(
|
||||
user: User, user_info: InfoSuccess, sprint: SoloRecord | None, blitz: SoloRecord | None
|
||||
) -> bytes:
|
||||
league = user_info.data.user.league
|
||||
if not isinstance(league, RatedLeague) or league.vs is None:
|
||||
raise TypeError
|
||||
user_name = user_info.data.user.username.upper()
|
||||
histories = await Processor.query_historical_data(user, user_info)
|
||||
value_max, value_min = get_value_bounds([i.tr for i in histories])
|
||||
split_value, offset = get_split(value_max, value_min)
|
||||
if sprint is not None:
|
||||
duration = timedelta(milliseconds=sprint.endcontext.final_time).total_seconds()
|
||||
sprint_value = f'{duration:.1f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.1f}s' # noqa: PLR2004
|
||||
else:
|
||||
sprint_value = 'N/A'
|
||||
blitz_value = f'{blitz.endcontext.score:,}' if blitz is not None else 'N/A'
|
||||
async with HostPage(
|
||||
await render(
|
||||
'tetrio/info',
|
||||
TETRIOInfo(
|
||||
user=TETRIOInfo.User(
|
||||
avatar=f'https://tetr.io/user-content/avatars/{user_info.data.user.id}.jpg?rv={user_info.data.user.avatar_revision}'
|
||||
if user_info.data.user.avatar_revision is not None
|
||||
else f'{{"type":"identicon","hash":"{md5(user_info.data.user.id.encode()).hexdigest()}"}}', # noqa: S324
|
||||
name=user_name,
|
||||
bio=user_info.data.user.bio,
|
||||
),
|
||||
ranking=TETRIOInfo.Ranking(
|
||||
rating=round(league.glicko, 2),
|
||||
rd=round(league.rd, 2),
|
||||
),
|
||||
tetra_league=TETRIOInfo.TetraLeague(
|
||||
rank=league.rank,
|
||||
tr=round(league.rating, 2),
|
||||
global_rank=league.standing,
|
||||
pps=league.pps,
|
||||
lpm=round(lpm := (league.pps * 24), 2),
|
||||
apm=league.apm,
|
||||
apl=round(league.apm / lpm, 2),
|
||||
vs=league.vs,
|
||||
adpm=round(adpm := (league.vs * 0.6), 2),
|
||||
adpl=round(adpm / lpm, 2),
|
||||
),
|
||||
tetra_league_history=TETRIOInfo.TetraLeagueHistory(
|
||||
data=histories,
|
||||
split_interval=split_value,
|
||||
min_tr=value_min,
|
||||
max_tr=value_max,
|
||||
offset=offset,
|
||||
),
|
||||
radar=TETRIOInfo.Radar(
|
||||
app=(app := (league.apm / (60 * league.pps))),
|
||||
dsps=(dsps := ((league.vs / 100) - (league.apm / 60))),
|
||||
dspp=(dspp := (dsps / league.pps)),
|
||||
ci=150 * dspp - 125 * app + 50 * (league.vs / league.apm) - 25,
|
||||
ge=2 * ((app * dsps) / league.pps),
|
||||
),
|
||||
sprint=sprint_value,
|
||||
blitz=blitz_value,
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
return await screenshot(urlunparse(('http', get_self_netloc(), f'/host/{page_hash}.html', '', '', '')))
|
||||
|
||||
async def get_user(self) -> None:
|
||||
"""
|
||||
用于获取 UserName 和 UserID 的函数
|
||||
"""
|
||||
if self.user.name is None:
|
||||
self.user.name = (await self.get_user_info()).data.user.username
|
||||
if self.user.ID is None:
|
||||
self.user.ID = (await self.get_user_info()).data.user.id
|
||||
|
||||
async def get_user_info(self) -> InfoSuccess:
|
||||
"""获取用户数据"""
|
||||
if self.processed_data.user_info is None:
|
||||
self.raw_response.user_info = await Cache.get(
|
||||
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}'])
|
||||
)
|
||||
user_info: UserInfo = type_validate_json(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
|
||||
if isinstance(user_info, FailedModel):
|
||||
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
|
||||
self.processed_data.user_info = user_info
|
||||
return self.processed_data.user_info
|
||||
|
||||
async def get_user_records(self) -> RecordsSuccess:
|
||||
"""获取Solo数据"""
|
||||
if self.processed_data.user_records is None:
|
||||
self.raw_response.user_records = await Cache.get(
|
||||
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}/', 'records'])
|
||||
)
|
||||
user_records: UserRecords = type_validate_json(UserRecords, self.raw_response.user_records) # type: ignore[arg-type]
|
||||
if isinstance(user_records, FailedModel):
|
||||
raise RequestError(f'用户Solo数据请求错误:\n{user_records.error}')
|
||||
self.processed_data.user_records = user_records
|
||||
return self.processed_data.user_records
|
||||
|
||||
|
||||
@scheduler.scheduled_job('cron', hour='0,6,12,18', minute=0)
|
||||
@retry(exception_type=RequestError, delay=timedelta(minutes=15))
|
||||
async def get_io_rank_data() -> None:
|
||||
league_all: LeagueAll = parse_raw_as(LeagueAll, await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))) # type: ignore[arg-type]
|
||||
if isinstance(league_all, LeagueAllFailed):
|
||||
league_all: LeagueAll = type_validate_json(
|
||||
LeagueAll, # type: ignore[arg-type]
|
||||
(data := await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))),
|
||||
)
|
||||
if isinstance(league_all, FailedModel):
|
||||
raise RequestError(f'排行榜数据请求错误:\n{league_all.error}')
|
||||
|
||||
def pps(user: LeagueAllUser) -> float:
|
||||
@@ -185,6 +414,10 @@ async def get_io_rank_data() -> None:
|
||||
user = sort(users, field)
|
||||
return User(ID=user.id, name=user.username).dict(), field(user)
|
||||
|
||||
data_hash: str | None = await run_sync((await run_sync(sha512)(data)).hexdigest)()
|
||||
async with open(get_data_file('nonebot_plugin_tetris_stats', f'{data_hash}.json.zst'), mode='wb') as file:
|
||||
await file.write(await run_sync(ZstdCompressor(level=12, threads=-1).compress)(data))
|
||||
|
||||
users = [i for i in league_all.data.users if isinstance(i, LeagueAllUser)]
|
||||
rank_to_users: defaultdict[Rank, list[LeagueAllUser]] = defaultdict(list)
|
||||
for i in users:
|
||||
@@ -208,6 +441,8 @@ async def get_io_rank_data() -> None:
|
||||
high_pps=(build_extremes_data(rank_users, pps, _max)),
|
||||
high_apm=(build_extremes_data(rank_users, apm, _max)),
|
||||
high_vs=(build_extremes_data(rank_users, vs, _max)),
|
||||
update_time=league_all.cache.cached_at,
|
||||
file_hash=data_hash,
|
||||
)
|
||||
)
|
||||
async with get_session() as session:
|
||||
@@ -218,6 +453,6 @@ async def get_io_rank_data() -> None:
|
||||
@driver.on_startup
|
||||
async def _() -> None:
|
||||
async with get_session() as session:
|
||||
latest_time = await session.scalar(select(IORank.create_time).order_by(IORank.id.desc()).limit(1))
|
||||
latest_time = await session.scalar(select(IORank.update_time).order_by(IORank.id.desc()).limit(1))
|
||||
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
|
||||
await get_io_rank_data()
|
||||
|
||||
@@ -5,9 +5,19 @@ from .base import FailedModel
|
||||
from .base import SuccessModel as BaseSuccessModel
|
||||
|
||||
|
||||
class _User(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
role: str
|
||||
xp: float
|
||||
supporter: bool
|
||||
verified: bool
|
||||
country: str | None = None
|
||||
|
||||
|
||||
class SuccessModel(BaseSuccessModel):
|
||||
class Data(BaseModel):
|
||||
class ValidUser(BaseModel):
|
||||
class ValidUser(_User):
|
||||
class League(BaseModel):
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
@@ -21,37 +31,23 @@ class SuccessModel(BaseSuccessModel):
|
||||
vs: float
|
||||
decaying: bool
|
||||
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
role: str
|
||||
xp: float
|
||||
league: League
|
||||
supporter: bool
|
||||
verified: bool
|
||||
country: str | None
|
||||
|
||||
class InvalidUser(BaseModel):
|
||||
class InvalidUser(_User):
|
||||
class League(BaseModel):
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
rating: float
|
||||
glicko: float | None
|
||||
rd: float | None
|
||||
glicko: float | None = None
|
||||
rd: float | None = None
|
||||
rank: Rank
|
||||
bestrank: Rank
|
||||
apm: float | None
|
||||
pps: float | None
|
||||
vs: float | None
|
||||
apm: float | None = None
|
||||
pps: float | None = None
|
||||
vs: float | None = None
|
||||
decaying: bool
|
||||
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
role: str
|
||||
xp: float
|
||||
league: League
|
||||
supporter: bool
|
||||
verified: bool
|
||||
country: str | None
|
||||
|
||||
users: list[ValidUser | InvalidUser]
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ class SuccessModel(BaseSuccessModel):
|
||||
class Badge(BaseModel):
|
||||
id: str
|
||||
label: str
|
||||
ts: datetime | None
|
||||
group: str | None = None
|
||||
ts: datetime | Literal[False] | None = None
|
||||
|
||||
class NeverPlayedLeague(BaseModel):
|
||||
gamesplayed: Literal[0]
|
||||
@@ -60,8 +61,8 @@ class SuccessModel(BaseSuccessModel):
|
||||
bestrank: Rank
|
||||
standing: int
|
||||
standing_local: int
|
||||
next_rank: Rank | None
|
||||
prev_rank: Rank | None
|
||||
next_rank: Rank | None = None
|
||||
prev_rank: Rank | None = None
|
||||
next_at: int
|
||||
prev_at: int
|
||||
percentile: float
|
||||
@@ -70,7 +71,7 @@ class SuccessModel(BaseSuccessModel):
|
||||
rd: float
|
||||
apm: float
|
||||
pps: float
|
||||
vs: float | None
|
||||
vs: float | None = None
|
||||
decaying: bool
|
||||
|
||||
class Connections(BaseModel):
|
||||
@@ -78,41 +79,41 @@ class SuccessModel(BaseSuccessModel):
|
||||
id: str
|
||||
username: str
|
||||
|
||||
discord: Discord | None
|
||||
discord: Discord | None = None
|
||||
|
||||
class Distinguishment(BaseModel):
|
||||
type: str # noqa: A003
|
||||
type: str
|
||||
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'banned']
|
||||
ts: datetime | None
|
||||
botmaster: str | None
|
||||
ts: datetime | None = None
|
||||
botmaster: str | None = None
|
||||
badges: list[Badge]
|
||||
xp: float
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
gametime: float
|
||||
country: str | None
|
||||
badstanding: bool | None
|
||||
supporter: bool | None # osk说是必有, 但实际上不是 fk osk
|
||||
country: str | None = None
|
||||
badstanding: bool | None = None
|
||||
supporter: bool | None = None # osk说是必有, 但实际上不是 fk osk
|
||||
supporter_tier: int
|
||||
verified: bool
|
||||
league: NeverPlayedLeague | NeverRatedLeague | RatedLeague
|
||||
avatar_revision: int | None
|
||||
avatar_revision: int | None = None
|
||||
"""This user's avatar ID. Get their avatar at
|
||||
|
||||
https://tetr.io/user-content/avatars/{ USERID }.jpg?rv={ AVATAR_REVISION }"""
|
||||
banner_revision: int | None
|
||||
banner_revision: int | None = None
|
||||
"""This user's banner ID. Get their banner at
|
||||
|
||||
https://tetr.io/user-content/banners/{ USERID }.jpg?rv={ BANNER_REVISION }
|
||||
|
||||
Ignore this field if the user is not a supporter."""
|
||||
bio: str | None
|
||||
bio: str | None = None
|
||||
connections: Connections
|
||||
friend_count: int | None
|
||||
distinguishment: Distinguishment | None
|
||||
friend_count: int | None = None
|
||||
distinguishment: Distinguishment | None = None
|
||||
|
||||
user: User
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ class EndContext(BaseModel):
|
||||
zero: bool
|
||||
locked: bool
|
||||
prev: int
|
||||
frameoffset: int | None
|
||||
frameoffset: int | None = None
|
||||
|
||||
class Clears(BaseModel):
|
||||
singles: int
|
||||
doubles: int
|
||||
triples: int
|
||||
quads: int
|
||||
pentas: int | None
|
||||
pentas: int | None = None
|
||||
realtspins: int
|
||||
minitspins: int
|
||||
minitspinsingles: int
|
||||
@@ -33,7 +33,7 @@ class EndContext(BaseModel):
|
||||
class Garbage(BaseModel):
|
||||
sent: int
|
||||
received: int
|
||||
attack: int | None
|
||||
attack: int | None = None
|
||||
cleared: int
|
||||
|
||||
class Finesse(BaseModel):
|
||||
@@ -46,18 +46,18 @@ class EndContext(BaseModel):
|
||||
level_lines: int
|
||||
level_lines_needed: int
|
||||
inputs: int
|
||||
holds: int | None
|
||||
holds: int | None = None
|
||||
time: Time
|
||||
score: int
|
||||
zenlevel: int | None
|
||||
zenprogress: int | None
|
||||
zenlevel: int | None = None
|
||||
zenprogress: int | None = None
|
||||
level: int
|
||||
combo: int
|
||||
currentcombopower: int | None # WTF
|
||||
currentcombopower: int | None = None # WTF
|
||||
topcombo: int
|
||||
btb: int
|
||||
topbtb: int
|
||||
currentbtbchainpower: int | None # WTF * 2
|
||||
currentbtbchainpower: int | None = None # WTF * 2
|
||||
tspins: int
|
||||
piecesplaced: int
|
||||
clears: Clears
|
||||
@@ -68,45 +68,37 @@ class EndContext(BaseModel):
|
||||
gametype: str
|
||||
|
||||
|
||||
class BaseModeRecord(BaseModel):
|
||||
class SoloRecord(BaseModel):
|
||||
class User(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
class _User(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
|
||||
id: str = Field(..., alias='_id')
|
||||
stream: str
|
||||
replayid: str
|
||||
user: User
|
||||
ts: datetime
|
||||
ismulti: bool | None
|
||||
|
||||
class _Record(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
stream: str
|
||||
replayid: str
|
||||
user: _User
|
||||
ts: datetime
|
||||
ismulti: bool | None = None
|
||||
|
||||
|
||||
class BaseModeRecord(BaseModel):
|
||||
class SoloRecord(_Record):
|
||||
endcontext: EndContext
|
||||
|
||||
class MultiRecord(BaseModel):
|
||||
class User(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
|
||||
id: str = Field(..., alias='_id')
|
||||
stream: str
|
||||
replayid: str
|
||||
user: User
|
||||
ts: datetime
|
||||
ismulti: bool | None
|
||||
class MultiRecord(_Record):
|
||||
endcontext: list[EndContext]
|
||||
|
||||
record: SoloRecord | MultiRecord | None
|
||||
rank: int | None
|
||||
record: SoloRecord | MultiRecord | None = None
|
||||
rank: int | None = None
|
||||
|
||||
|
||||
class SuccessModel(BaseSuccessModel):
|
||||
class Data(BaseModel):
|
||||
class Records(BaseModel):
|
||||
class Sprint(BaseModeRecord):
|
||||
...
|
||||
class Sprint(BaseModeRecord): ...
|
||||
|
||||
class Blitz(BaseModeRecord):
|
||||
...
|
||||
class Blitz(BaseModeRecord): ...
|
||||
|
||||
sprint: Sprint = Field(..., alias='40l')
|
||||
blitz: Blitz
|
||||
|
||||
@@ -2,7 +2,9 @@ from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, O
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo # type: ignore[import-untyped]
|
||||
|
||||
from ...db import query_bind_info
|
||||
from ...utils.exception import HandleNotFinishedError, NeedCatchError
|
||||
@@ -67,14 +69,25 @@ alc = on_alconna(
|
||||
|
||||
|
||||
@alc.assign('bind')
|
||||
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
|
||||
async def _( # noqa: PLR0913
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
matcher: Matcher,
|
||||
account: User,
|
||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
||||
user_info: UserInfo = EventUserInfo(), # noqa: B008
|
||||
):
|
||||
proc = Processor(
|
||||
event_id=id(event),
|
||||
user=account,
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
|
||||
await (
|
||||
await proc.handle_bind(
|
||||
platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info, user_info=user_info
|
||||
)
|
||||
).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
@@ -98,7 +111,7 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(message + await proc.handle_query())
|
||||
await (UniMessage(message) + await proc.handle_query()).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
@@ -112,7 +125,7 @@ async def _(event: Event, matcher: Matcher, account: User):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
|
||||
@@ -2,16 +2,23 @@ from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from io import StringIO
|
||||
from re import match
|
||||
from typing import Literal, NoReturn
|
||||
from urllib.parse import urlencode
|
||||
from typing import Literal
|
||||
from urllib.parse import urlencode, urlunparse
|
||||
|
||||
from lxml import etree
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
|
||||
from pandas import read_html
|
||||
from typing_extensions import override
|
||||
|
||||
from ...db import create_or_update_bind
|
||||
from ...db import BindStatus, create_or_update_bind
|
||||
from ...utils.avatar import get_avatar
|
||||
from ...utils.exception import MessageFormatError, RequestError
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.render import Bind, render
|
||||
from ...utils.request import Request, splice_url
|
||||
from ...utils.screenshot import screenshot
|
||||
from .. import Processor as ProcessorMeta
|
||||
from ..schemas import BaseUser
|
||||
from .constant import BASE_URL, GAME_TYPE
|
||||
@@ -24,6 +31,7 @@ class User(BaseUser):
|
||||
name: str
|
||||
|
||||
@property
|
||||
@override
|
||||
def unique_identifier(self) -> str:
|
||||
return self.name
|
||||
|
||||
@@ -51,33 +59,74 @@ class Processor(ProcessorMeta):
|
||||
raw_response: RawResponse
|
||||
processed_data: ProcessedData
|
||||
|
||||
@override
|
||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||
super().__init__(event_id, user, command_args)
|
||||
self.raw_response = RawResponse()
|
||||
self.processed_data = ProcessedData()
|
||||
|
||||
@property
|
||||
@override
|
||||
def game_platform(self) -> Literal['TOP']:
|
||||
return GAME_TYPE
|
||||
|
||||
async def handle_bind(self, platform: str, account: str) -> str:
|
||||
@override
|
||||
async def handle_bind(self, platform: str, account: str, bot_info: UserInfo, user_info: UserInfo) -> UniMessage:
|
||||
"""处理绑定消息"""
|
||||
self.command_type = 'bind'
|
||||
await self.check_user()
|
||||
async with get_session() as session:
|
||||
return await create_or_update_bind(
|
||||
bind_status = await create_or_update_bind(
|
||||
session=session,
|
||||
chat_platform=platform,
|
||||
chat_account=account,
|
||||
game_platform=GAME_TYPE,
|
||||
game_account=self.user.name,
|
||||
)
|
||||
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||
async with HostPage(
|
||||
await render(
|
||||
'binding',
|
||||
Bind(
|
||||
platform=self.game_platform,
|
||||
status='unknown',
|
||||
user=Bind.People(
|
||||
avatar=await get_avatar(user_info, 'Data URI', None),
|
||||
name=(await self.get_user_name()).upper(),
|
||||
),
|
||||
bot=Bind.People(
|
||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||
name=bot_info.user_name,
|
||||
),
|
||||
command='top查我',
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
message = UniMessage.image(
|
||||
raw=await screenshot(urlunparse(('http', get_self_netloc(), f'/host/{page_hash}.html', '', '', '')))
|
||||
)
|
||||
return message
|
||||
|
||||
async def handle_query(self) -> str:
|
||||
@override
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
self.command_type = 'query'
|
||||
await self.check_user()
|
||||
return await self.generate_message()
|
||||
game_data = await self.get_game_data()
|
||||
message = ''
|
||||
if game_data.day is not None:
|
||||
message += f'用户 {self.user.name} 24小时内统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.day.lpm,2)} ( {round(game_data.day.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.day.apm,2)} ( x{round(game_data.day.apm/game_data.day.lpm,2)} )'
|
||||
else:
|
||||
message += f'用户 {self.user.name} 暂无24小时内统计数据'
|
||||
if game_data.total is not None:
|
||||
message += '\n历史统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.total.lpm,2)} ( {round(game_data.total.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.total.apm,2)} ( x{round(game_data.total.apm/game_data.total.lpm,2)} )'
|
||||
else:
|
||||
message += '\n暂无历史统计数据'
|
||||
return UniMessage(message)
|
||||
|
||||
async def get_user_profile(self) -> str:
|
||||
"""获取用户信息"""
|
||||
@@ -87,10 +136,9 @@ class Processor(ProcessorMeta):
|
||||
self.processed_data.user_profile = self.raw_response.user_profile.decode()
|
||||
return self.processed_data.user_profile
|
||||
|
||||
async def check_user(self) -> None | NoReturn:
|
||||
async def check_user(self) -> None:
|
||||
if 'user not found!' in await self.get_user_profile():
|
||||
raise RequestError('用户不存在!')
|
||||
return None
|
||||
|
||||
async def get_user_name(self) -> str:
|
||||
"""获取用户名"""
|
||||
@@ -115,21 +163,3 @@ class Processor(ProcessorMeta):
|
||||
dataframe = read_html(table, encoding='utf-8', header=0)[0]
|
||||
total = Data(lpm=dataframe['lpm'].mean(), apm=dataframe['apm'].mean()) if len(dataframe) != 0 else None
|
||||
return GameData(day=day, total=total)
|
||||
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
game_data = await self.get_game_data()
|
||||
message = ''
|
||||
if game_data.day is not None:
|
||||
message += f'用户 {self.user.name} 24小时内统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.day.lpm,2)} ( {round(game_data.day.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.day.apm,2)} ( x{round(game_data.day.apm/game_data.day.lpm,2)} )'
|
||||
else:
|
||||
message += f'用户 {self.user.name} 暂无24小时内统计数据'
|
||||
if game_data.total is not None:
|
||||
message += '\n历史统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.total.lpm,2)} ( {round(game_data.total.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.total.apm,2)} ( x{round(game_data.total.apm/game_data.total.lpm,2)} )'
|
||||
else:
|
||||
message += '\n暂无历史统计数据'
|
||||
return message
|
||||
|
||||
@@ -4,7 +4,9 @@ from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, O
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo # type: ignore[import-untyped]
|
||||
|
||||
from ...db import query_bind_info
|
||||
from ...utils.exception import HandleNotFinishedError, NeedCatchError, RequestError
|
||||
@@ -71,7 +73,7 @@ alc = on_alconna(
|
||||
|
||||
async def finish_special_query(matcher: Matcher, proc: Processor) -> NoReturn:
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).finish()
|
||||
except NeedCatchError as e:
|
||||
if isinstance(e, RequestError) and '未找到此用户' in e.message:
|
||||
matcher.skip()
|
||||
@@ -130,14 +132,25 @@ except ImportError:
|
||||
|
||||
|
||||
@alc.assign('bind')
|
||||
async def _(bot: Bot, event: Event, matcher: Matcher, account: User):
|
||||
async def _( # noqa: PLR0913
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
matcher: Matcher,
|
||||
account: User,
|
||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
||||
user_info: UserInfo = EventUserInfo(), # noqa: B008
|
||||
):
|
||||
proc = Processor(
|
||||
event_id=id(event),
|
||||
user=account,
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_bind(platform=get_platform(bot), account=event.get_user_id()))
|
||||
await (
|
||||
await proc.handle_bind(
|
||||
platform=get_platform(bot), account=event.get_user_id(), bot_info=bot_info, nb_user_info=user_info
|
||||
)
|
||||
).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
@@ -161,7 +174,7 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(message + await proc.handle_query())
|
||||
await (UniMessage(message) + await proc.handle_query()).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
@@ -175,7 +188,7 @@ async def _(event: Event, matcher: Matcher, account: User):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).finish()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
from typing import Literal
|
||||
|
||||
GAME_TYPE: Literal['TOS'] = 'TOS'
|
||||
BASE_URL = 'https://teatube.cn:8888/'
|
||||
BASE_URL = {
|
||||
'https://teatube.cn:8888/',
|
||||
'http://cafuuchino1.studio26f.org:19970',
|
||||
'http://cafuuchino2.studio26f.org:19970',
|
||||
'http://cafuuchino3.studio26f.org:19970',
|
||||
'http://cafuuchino4.studio26f.org:19970',
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
from dataclasses import dataclass
|
||||
from re import match
|
||||
from typing import Literal
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import urlencode, urlunparse
|
||||
|
||||
from httpx import TimeoutException
|
||||
from nonebot.compat import type_validate_json
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from pydantic import parse_raw_as
|
||||
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
|
||||
from typing_extensions import override
|
||||
|
||||
from ...db import create_or_update_bind
|
||||
from ...db import BindStatus, create_or_update_bind
|
||||
from ...utils.avatar import get_avatar
|
||||
from ...utils.exception import MessageFormatError, RequestError
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.render import Bind, render
|
||||
from ...utils.request import Request, splice_url
|
||||
from ...utils.screenshot import screenshot
|
||||
from .. import Processor as ProcessorMeta
|
||||
from ..schemas import BaseUser
|
||||
from .constant import BASE_URL, GAME_TYPE
|
||||
@@ -25,6 +33,7 @@ class User(BaseUser):
|
||||
name: str | None = None
|
||||
|
||||
@property
|
||||
@override
|
||||
def unique_identifier(self) -> str:
|
||||
if self.teaid is None:
|
||||
raise ValueError('不完整的User!')
|
||||
@@ -63,33 +72,79 @@ class Processor(ProcessorMeta):
|
||||
raw_response: RawResponse
|
||||
processed_data: ProcessedData
|
||||
|
||||
@override
|
||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||
super().__init__(event_id, user, command_args)
|
||||
self.raw_response = RawResponse(user_profile={})
|
||||
self.processed_data = ProcessedData(user_profile={})
|
||||
|
||||
@property
|
||||
@override
|
||||
def game_platform(self) -> Literal['TOS']:
|
||||
return GAME_TYPE
|
||||
|
||||
async def handle_bind(self, platform: str, account: str) -> str:
|
||||
@override
|
||||
async def handle_bind(
|
||||
self, platform: str, account: str, bot_info: NBUserInfo, nb_user_info: NBUserInfo
|
||||
) -> UniMessage:
|
||||
"""处理绑定消息"""
|
||||
self.command_type = 'bind'
|
||||
await self.get_user()
|
||||
async with get_session() as session:
|
||||
return await create_or_update_bind(
|
||||
bind_status = await create_or_update_bind(
|
||||
session=session,
|
||||
chat_platform=platform,
|
||||
chat_account=account,
|
||||
game_platform=GAME_TYPE,
|
||||
game_account=self.user.unique_identifier,
|
||||
)
|
||||
user_info = await self.get_user_info()
|
||||
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||
async with HostPage(
|
||||
await render(
|
||||
'binding',
|
||||
Bind(
|
||||
platform=self.game_platform,
|
||||
status='unknown',
|
||||
user=Bind.People(
|
||||
avatar=await get_avatar(nb_user_info, 'Data URI', None), name=user_info.data.name
|
||||
),
|
||||
bot=Bind.People(
|
||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||
name=bot_info.user_name,
|
||||
),
|
||||
command='茶服查我',
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
message = UniMessage.image(
|
||||
raw=await screenshot(urlunparse(('http', get_self_netloc(), f'/host/{page_hash}.html', '', '', '')))
|
||||
)
|
||||
return message
|
||||
|
||||
async def handle_query(self) -> str:
|
||||
@override
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
self.command_type = 'query'
|
||||
await self.get_user()
|
||||
return await self.generate_message()
|
||||
user_info = (await self.get_user_info()).data
|
||||
message = f'用户 {user_info.name} ({user_info.teaid}) '
|
||||
if user_info.ranked_games == '0':
|
||||
message += '暂无段位统计数据'
|
||||
else:
|
||||
message += f', 段位分 {round(float(user_info.rating_now),2)}±{round(float(user_info.rd_now),2)} ({round(float(user_info.vol_now),2)}) '
|
||||
game_data = await self.get_game_data()
|
||||
if game_data is None:
|
||||
message += ', 暂无游戏数据'
|
||||
else:
|
||||
message += f', 最近 {game_data.num} 局数据'
|
||||
message += f"\nL'PM: {game_data.lpm} ( {game_data.pps} pps )"
|
||||
message += f'\nAPM: {game_data.apm} ( x{game_data.apl} )'
|
||||
message += f'\nADPM: {game_data.adpm} ( x{game_data.adpl} ) ( {game_data.vs}vs )'
|
||||
message += f'\n40L: {float(user_info.pb_sprint)/1000:.2f}s' if user_info.pb_sprint != '2147483647' else ''
|
||||
message += f'\nMarathon: {user_info.pb_marathon}' if user_info.pb_marathon != '0' else ''
|
||||
message += f'\nChallenge: {user_info.pb_challenge}' if user_info.pb_challenge != '0' else ''
|
||||
return UniMessage(message)
|
||||
|
||||
async def get_user(self) -> None:
|
||||
"""
|
||||
@@ -104,23 +159,31 @@ class Processor(ProcessorMeta):
|
||||
"""获取用户信息"""
|
||||
if self.processed_data.user_info is None:
|
||||
if self.user.teaid is not None:
|
||||
url = splice_url(
|
||||
[
|
||||
BASE_URL,
|
||||
'getTeaIdInfo',
|
||||
f'?{urlencode({"teaId":self.user.teaid})}',
|
||||
]
|
||||
)
|
||||
url = [
|
||||
splice_url(
|
||||
[
|
||||
i,
|
||||
'getTeaIdInfo',
|
||||
f'?{urlencode({"teaId":self.user.teaid})}',
|
||||
]
|
||||
)
|
||||
for i in BASE_URL
|
||||
]
|
||||
else:
|
||||
url = splice_url(
|
||||
[
|
||||
BASE_URL,
|
||||
'getUsernameInfo',
|
||||
f'?{urlencode({"username":self.user.name})}',
|
||||
]
|
||||
)
|
||||
self.raw_response.user_info = await Request.request(url)
|
||||
user_info: UserInfo = parse_raw_as(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
|
||||
url = [
|
||||
splice_url(
|
||||
[
|
||||
i,
|
||||
'getUsernameInfo',
|
||||
f'?{urlencode({"username":self.user.name})}',
|
||||
]
|
||||
)
|
||||
for i in BASE_URL
|
||||
]
|
||||
self.raw_response.user_info = await Request.failover_request(
|
||||
url, failover_code=[502], failover_exc=(TimeoutException,)
|
||||
)
|
||||
user_info: UserInfo = type_validate_json(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
|
||||
if not isinstance(user_info, InfoSuccess):
|
||||
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
|
||||
self.processed_data.user_info = user_info
|
||||
@@ -132,16 +195,23 @@ class Processor(ProcessorMeta):
|
||||
other_parameter = {}
|
||||
params = urlencode(dict(sorted(other_parameter.items())))
|
||||
if self.processed_data.user_profile.get(params) is None:
|
||||
self.raw_response.user_profile[params] = await Request.request(
|
||||
splice_url(
|
||||
[
|
||||
BASE_URL,
|
||||
'getProfile',
|
||||
f'?{urlencode({"id":self.user.teaid or self.user.name,**other_parameter})}',
|
||||
]
|
||||
)
|
||||
self.raw_response.user_profile[params] = await Request.failover_request(
|
||||
[
|
||||
splice_url(
|
||||
[
|
||||
i,
|
||||
'getProfile',
|
||||
f'?{urlencode({"id":self.user.teaid or self.user.name,**other_parameter})}',
|
||||
]
|
||||
)
|
||||
for i in BASE_URL
|
||||
],
|
||||
failover_code=[502],
|
||||
failover_exc=(TimeoutException,),
|
||||
)
|
||||
self.processed_data.user_profile[params] = type_validate_json(
|
||||
UserProfile, self.raw_response.user_profile[params]
|
||||
)
|
||||
self.processed_data.user_profile[params] = UserProfile.parse_raw(self.raw_response.user_profile[params])
|
||||
return self.processed_data.user_profile[params]
|
||||
|
||||
async def get_game_data(self) -> GameData | None:
|
||||
@@ -185,24 +255,3 @@ class Processor(ProcessorMeta):
|
||||
adpl=round((adpm / lpm), 2),
|
||||
vs=round((adpm / 60 * 100), 2),
|
||||
)
|
||||
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
user_info = (await self.get_user_info()).data
|
||||
message = f'用户 {user_info.name} ({user_info.teaid}) '
|
||||
if user_info.ranked_games == '0':
|
||||
message += '暂无段位统计数据'
|
||||
else:
|
||||
message += f', 段位分 {round(float(user_info.rating_now),2)}±{round(float(user_info.rd_now),2)} ({round(float(user_info.vol_now),2)}) '
|
||||
game_data = await self.get_game_data()
|
||||
if game_data is None:
|
||||
message += ', 暂无游戏数据'
|
||||
else:
|
||||
message += f', 最近 {game_data.num} 局数据'
|
||||
message += f"\nL'PM: {game_data.lpm} ( {game_data.pps} pps )"
|
||||
message += f'\nAPM: {game_data.apm} ( x{game_data.apl} )'
|
||||
message += f'\nADPM: {game_data.adpm} ( x{game_data.adpl} ) ( {game_data.vs}vs )'
|
||||
message += f'\n40L: {float(user_info.pb_sprint)/1000:.2f}s' if user_info.pb_sprint != '2147483647' else ''
|
||||
message += f'\nMarathon: {user_info.pb_marathon}' if user_info.pb_marathon != '0' else ''
|
||||
message += f'\nChallenge: {user_info.pb_challenge}' if user_info.pb_challenge != '0' else ''
|
||||
return message
|
||||
|
||||
52
nonebot_plugin_tetris_stats/utils/avatar.py
Normal file
52
nonebot_plugin_tetris_stats/utils/avatar.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from base64 import b64encode
|
||||
from io import BytesIO
|
||||
from typing import Literal, overload
|
||||
|
||||
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
|
||||
from PIL import Image
|
||||
|
||||
|
||||
@overload
|
||||
async def get_avatar(user: UserInfo, scheme: Literal['Data URI'], default: str | None) -> str:
|
||||
"""获取用户头像的指定格式
|
||||
|
||||
Args:
|
||||
user (UserInfo): 要获取的用户
|
||||
scheme (Literal['Data URI']): 格式
|
||||
default (str | None): 获取不到时的默认值
|
||||
|
||||
Raises:
|
||||
TypeError: Can't get avatar: 当获取不到头像并且没有设置默认值时抛出
|
||||
TypeError: Can't get avatar format: 当获取到的头像无法识别格式时抛出
|
||||
|
||||
Returns:
|
||||
str: Data URI 格式的头像
|
||||
"""
|
||||
|
||||
|
||||
@overload
|
||||
async def get_avatar(user: UserInfo, scheme: Literal['bytes'], default: str | None) -> bytes:
|
||||
"""获取用户头像的指定格式
|
||||
|
||||
Args:
|
||||
user (UserInfo): 要获取的用户
|
||||
scheme (Literal['bytes']): 格式
|
||||
default (str | None): 获取不到时的默认值
|
||||
|
||||
Returns:
|
||||
bytes: bytes 格式的头像
|
||||
"""
|
||||
|
||||
|
||||
async def get_avatar(user: UserInfo, scheme: Literal['Data URI', 'bytes'], default: str | None) -> str | bytes:
|
||||
if user.user_avatar is None:
|
||||
if default is None:
|
||||
raise TypeError("Can't get avatar")
|
||||
return default
|
||||
bot_avatar = await user.user_avatar.get_image()
|
||||
if scheme == 'Data URI':
|
||||
avatar_format = Image.open(BytesIO(bot_avatar)).format
|
||||
if avatar_format is None:
|
||||
raise TypeError("Can't get avatar format")
|
||||
return f'data:{Image.MIME[avatar_format]};base64,{b64encode(bot_avatar).decode()}'
|
||||
return bot_avatar
|
||||
@@ -45,7 +45,7 @@ class BrowserManager:
|
||||
logger.error('安装/更新 playwright 浏览器失败')
|
||||
try:
|
||||
await cls._start_browser()
|
||||
except BaseException as e: # noqa: BLE001 不知道会有什么异常, 交给用户解决
|
||||
except BaseException as e: # 不知道会有什么异常, 交给用户解决
|
||||
raise ImportError(
|
||||
'playwright 启动失败, 请尝试在命令行运行 playwright install-deps firefox, 如果仍然启动失败, 请参考上面的报错👆'
|
||||
) from e
|
||||
|
||||
@@ -18,6 +18,10 @@ class NeedCatchError(TetrisStatsError):
|
||||
class RequestError(NeedCatchError):
|
||||
"""请求错误"""
|
||||
|
||||
def __init__(self, message: str = '', *, status_code: int | None = None):
|
||||
super().__init__(message)
|
||||
self.status_code = status_code
|
||||
|
||||
|
||||
class MessageFormatError(NeedCatchError):
|
||||
"""用户发送的消息格式不正确"""
|
||||
|
||||
70
nonebot_plugin_tetris_stats/utils/host.py
Normal file
70
nonebot_plugin_tetris_stats/utils/host.py
Normal file
@@ -0,0 +1,70 @@
|
||||
from hashlib import sha256
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from typing import ClassVar
|
||||
|
||||
from fastapi import FastAPI, status
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from nonebot import get_app, get_driver
|
||||
from nonebot.log import logger
|
||||
from nonebot_plugin_localstore import get_cache_dir # type: ignore[import-untyped]
|
||||
from pydantic import IPvAnyAddress
|
||||
|
||||
from .templates import templates_dir
|
||||
|
||||
app = get_app()
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
global_config = driver.config
|
||||
|
||||
cache_dir = get_cache_dir('nonebot_plugin_tetris_stats')
|
||||
|
||||
if not isinstance(app, FastAPI):
|
||||
raise RuntimeError('本插件需要 FastAPI 驱动器才能运行') # noqa: TRY004
|
||||
|
||||
NOT_FOUND = HTMLResponse('404 Not Found', status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class HostPage:
|
||||
pages: ClassVar[dict[str, str]] = {}
|
||||
|
||||
def __init__(self, page: str) -> None:
|
||||
self.page_hash = sha256(page.encode()).hexdigest()
|
||||
self.pages[self.page_hash] = page
|
||||
|
||||
async def __aenter__(self) -> str:
|
||||
return self.page_hash
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
|
||||
self.pages.pop(self.page_hash, None)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
def _():
|
||||
app.mount(
|
||||
'/host/assets',
|
||||
StaticFiles(directory=templates_dir / 'assets'),
|
||||
name='assets',
|
||||
)
|
||||
logger.success('assets mounted')
|
||||
|
||||
|
||||
@app.get('/host/{page_hash}.html', status_code=status.HTTP_200_OK)
|
||||
async def _(page_hash: str) -> HTMLResponse:
|
||||
if page_hash in HostPage.pages:
|
||||
return HTMLResponse(HostPage.pages[page_hash])
|
||||
return NOT_FOUND
|
||||
|
||||
|
||||
def get_self_netloc() -> str:
|
||||
host: IPv4Address | IPv6Address | IPvAnyAddress = global_config.host
|
||||
if isinstance(host, IPv4Address):
|
||||
if host == IPv4Address('0.0.0.0'): # noqa: S104
|
||||
host = IPv4Address('127.0.0.1')
|
||||
netloc = f'{host}:{global_config.port}'
|
||||
else:
|
||||
if host == IPv6Address('::'):
|
||||
host = IPv6Address('::1')
|
||||
netloc = f'[{host}]:{global_config.port}'
|
||||
return netloc
|
||||
@@ -144,7 +144,7 @@ class TetrisMetricsProWithLPMADPM(TetrisMetricsBasicWithLPM, TetrisMetricsBaseWi
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: Number,
|
||||
lpm: None = None,
|
||||
@@ -157,7 +157,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: None = None,
|
||||
lpm: Number,
|
||||
@@ -170,7 +170,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: None = None,
|
||||
lpm: None = None,
|
||||
@@ -183,7 +183,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: None = None,
|
||||
lpm: None = None,
|
||||
@@ -196,7 +196,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: Number,
|
||||
lpm: None = None,
|
||||
@@ -209,7 +209,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: None = None,
|
||||
lpm: Number,
|
||||
@@ -222,7 +222,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: Number,
|
||||
lpm: None = None,
|
||||
@@ -235,7 +235,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: Number,
|
||||
lpm: None = None,
|
||||
@@ -248,7 +248,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: None = None,
|
||||
lpm: Number,
|
||||
@@ -261,7 +261,7 @@ def get_metrics( # noqa: PLR0913
|
||||
|
||||
|
||||
@overload
|
||||
def get_metrics( # noqa: PLR0913
|
||||
def get_metrics(
|
||||
*,
|
||||
pps: None = None,
|
||||
lpm: Number,
|
||||
|
||||
104
nonebot_plugin_tetris_stats/utils/render.py
Normal file
104
nonebot_plugin_tetris_stats/utils/render.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from datetime import datetime
|
||||
from typing import Annotated, ClassVar, Literal, overload
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from nonebot.compat import PYDANTIC_V2
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..game_data_processor.io_data_processor.typing import Rank
|
||||
from .templates import templates_dir
|
||||
from .typing import Number
|
||||
|
||||
if PYDANTIC_V2:
|
||||
from pydantic import PlainSerializer
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(templates_dir), autoescape=True, trim_blocks=True, lstrip_blocks=True, enable_async=True
|
||||
)
|
||||
|
||||
|
||||
def format_datetime_to_timestamp(dt: datetime) -> int:
|
||||
return int(dt.timestamp() * 1000)
|
||||
|
||||
|
||||
class Bind(BaseModel):
|
||||
class People(BaseModel):
|
||||
avatar: str
|
||||
name: str
|
||||
|
||||
platform: Literal['TETR.IO', 'TOP', 'TOS']
|
||||
status: Literal['error', 'success', 'unknown', 'unlink', 'unverified']
|
||||
user: People
|
||||
bot: People
|
||||
command: str
|
||||
|
||||
|
||||
class TETRIOInfo(BaseModel):
|
||||
class User(BaseModel):
|
||||
avatar: str
|
||||
name: str
|
||||
bio: str | None
|
||||
|
||||
class Ranking(BaseModel):
|
||||
rating: Number
|
||||
rd: Number
|
||||
|
||||
class TetraLeague(BaseModel):
|
||||
rank: Rank
|
||||
tr: Number
|
||||
global_rank: Number
|
||||
pps: Number
|
||||
lpm: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
vs: Number
|
||||
adpm: Number
|
||||
adpl: Number
|
||||
|
||||
class TetraLeagueHistory(BaseModel):
|
||||
class Data(BaseModel):
|
||||
if PYDANTIC_V2:
|
||||
record_at: Annotated[datetime, PlainSerializer(format_datetime_to_timestamp, return_type=int)]
|
||||
else:
|
||||
record_at: datetime # type: ignore[no-redef]
|
||||
tr: Number
|
||||
|
||||
data: list[Data]
|
||||
split_interval: Number
|
||||
min_tr: Number
|
||||
max_tr: Number
|
||||
offset: Number
|
||||
|
||||
class Radar(BaseModel):
|
||||
app: Number
|
||||
dsps: Number
|
||||
dspp: Number
|
||||
ci: Number
|
||||
ge: Number
|
||||
|
||||
user: User
|
||||
ranking: Ranking
|
||||
tetra_league: TetraLeague
|
||||
tetra_league_history: TetraLeagueHistory
|
||||
radar: Radar
|
||||
sprint: str
|
||||
blitz: str
|
||||
|
||||
if not PYDANTIC_V2:
|
||||
|
||||
class Config:
|
||||
json_encoders: ClassVar[dict] = {datetime: format_datetime_to_timestamp}
|
||||
|
||||
|
||||
@overload
|
||||
async def render(render_type: Literal['binding'], data: Bind) -> str: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def render(render_type: Literal['tetrio/info'], data: TETRIOInfo) -> str: ...
|
||||
|
||||
|
||||
async def render(render_type: Literal['binding', 'tetrio/info'], data: Bind | TETRIOInfo) -> str:
|
||||
if PYDANTIC_V2:
|
||||
return await env.get_template('index.html').render_async(path=render_type, data=data.model_dump_json())
|
||||
return await env.get_template('index.html').render_async(path=render_type, data=data.json())
|
||||
@@ -1,9 +1,10 @@
|
||||
from collections.abc import Sequence
|
||||
from http import HTTPStatus
|
||||
from urllib.parse import urljoin, urlparse
|
||||
|
||||
from aiofiles import open
|
||||
from httpx import AsyncClient, HTTPError
|
||||
from nonebot import get_driver
|
||||
from nonebot import get_driver, get_plugin_config
|
||||
from nonebot.log import logger
|
||||
from playwright.async_api import Response
|
||||
from ujson import JSONDecodeError, dumps, loads
|
||||
@@ -13,7 +14,7 @@ from .browser import BrowserManager
|
||||
from .exception import RequestError
|
||||
|
||||
driver = get_driver()
|
||||
config = Config.parse_obj(driver.config)
|
||||
config = get_plugin_config(Config)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
@@ -68,7 +69,11 @@ class Request:
|
||||
raise RequestError('api请求失败')
|
||||
cls._headers = await response.request.all_headers()
|
||||
try:
|
||||
cls._cookies = {i['name']: i['value'] for i in await context.cookies()}
|
||||
cls._cookies = {
|
||||
name: value
|
||||
for i in await context.cookies()
|
||||
if (name := i.get('name')) is not None and (value := i.get('value')) is not None
|
||||
}
|
||||
except KeyError:
|
||||
cls._cookies = None
|
||||
return await response.body()
|
||||
@@ -116,7 +121,8 @@ class Request:
|
||||
response = await session.get(url, headers=cls._headers)
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
raise RequestError(
|
||||
f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}'
|
||||
f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}',
|
||||
status_code=response.status_code,
|
||||
)
|
||||
if is_json:
|
||||
loads(response.content)
|
||||
@@ -127,3 +133,33 @@ class Request:
|
||||
if urlparse(url).netloc.lower().endswith('tetr.io'):
|
||||
return await cls._anti_cloudflare(url)
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
async def failover_request(
|
||||
cls,
|
||||
urls: Sequence[str],
|
||||
*,
|
||||
failover_code: Sequence[int],
|
||||
failover_exc: tuple[type[BaseException], ...],
|
||||
is_json: bool = True,
|
||||
) -> bytes:
|
||||
error_list: list[RequestError] = []
|
||||
for i in urls:
|
||||
logger.debug(f'尝试请求 {i}')
|
||||
try:
|
||||
return await cls.request(i, is_json=is_json)
|
||||
except RequestError as e:
|
||||
if e.status_code in failover_code: # 如果状态码在 failover_code 中, 则继续尝试下一个URL
|
||||
error_list.append(e)
|
||||
continue
|
||||
# 如果状态码不在故障转移列表中, 则查找异常栈, 如果异常栈内有 failover_exc 内的异常类型, 则继续尝试下一个URL
|
||||
tb = e.__traceback__
|
||||
while tb is not None:
|
||||
if isinstance(tb.tb_frame.f_locals.get('exc_value'), failover_exc):
|
||||
error_list.append(e)
|
||||
break
|
||||
tb = tb.tb_next
|
||||
else:
|
||||
raise
|
||||
continue
|
||||
raise RequestError(f'所有地址皆不可用\n{error_list!r}')
|
||||
|
||||
11
nonebot_plugin_tetris_stats/utils/screenshot.py
Normal file
11
nonebot_plugin_tetris_stats/utils/screenshot.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from .browser import BrowserManager
|
||||
|
||||
|
||||
async def screenshot(url: str) -> bytes:
|
||||
browser = await BrowserManager.get_browser()
|
||||
async with (
|
||||
await browser.new_page(no_viewport=True, viewport={'width': 0, 'height': 0}) as page,
|
||||
):
|
||||
await page.goto(url)
|
||||
await page.wait_for_load_state('networkidle')
|
||||
return await page.screenshot(full_page=True, type='png')
|
||||
60
nonebot_plugin_tetris_stats/utils/templates.py
Normal file
60
nonebot_plugin_tetris_stats/utils/templates.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from asyncio.subprocess import PIPE, create_subprocess_exec
|
||||
from shutil import rmtree
|
||||
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
from nonebot_plugin_localstore import get_data_dir # type: ignore[import-untyped]
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
templates_dir = get_data_dir('nonebot_plugin_tetris_stats') / 'templates'
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def init_templates() -> None:
|
||||
try:
|
||||
await create_subprocess_exec('git', '--version', stdout=PIPE)
|
||||
except FileNotFoundError as e:
|
||||
raise RuntimeError(
|
||||
'未找到 git, 请确保 git 已安装并在环境变量中\n安装步骤请参阅: https://git-scm.com/book/zh/v2/%E8%B5%B7%E6%AD%A5-%E5%AE%89%E8%A3%85-Git'
|
||||
) from e
|
||||
if not templates_dir.exists():
|
||||
logger.info('模板仓库不存在, 正在尝试初始化...')
|
||||
proc = await create_subprocess_exec(
|
||||
'git',
|
||||
'clone',
|
||||
'-b',
|
||||
'gh-pages',
|
||||
'https://github.com/A-Minos/tetris-stats-templates',
|
||||
templates_dir,
|
||||
'--depth=1',
|
||||
stdout=PIPE,
|
||||
stderr=PIPE,
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
for i in stderr.decode().splitlines():
|
||||
logger.error(i)
|
||||
raise RuntimeError('初始化模板仓库失败')
|
||||
logger.success('模板仓库初始化成功')
|
||||
return
|
||||
proc = await create_subprocess_exec(
|
||||
'git', 'rev-parse', '--is-inside-work-tree', stdout=PIPE, stderr=PIPE, cwd=templates_dir
|
||||
)
|
||||
stdout, stderr = await proc.communicate()
|
||||
if proc.returncode != 0:
|
||||
for i in stderr.decode().splitlines():
|
||||
logger.error(i)
|
||||
logger.warning('模板仓库状态异常, 尝试重新初始化')
|
||||
rmtree(templates_dir)
|
||||
await init_templates()
|
||||
return
|
||||
logger.info('正在更新模板仓库...')
|
||||
proc = await create_subprocess_exec('git', 'pull', stdout=PIPE, stderr=PIPE, cwd=templates_dir)
|
||||
stdout, stderr = await proc.communicate()
|
||||
logger.info(stdout.decode().strip())
|
||||
if proc.returncode != 0:
|
||||
for i in stderr.decode().splitlines():
|
||||
logger.error(i)
|
||||
raise RuntimeError('更新模板仓库失败')
|
||||
logger.success('模板仓库更新成功')
|
||||
@@ -1,7 +1,7 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any, Literal
|
||||
|
||||
Number = int | float
|
||||
Number = float | int
|
||||
GameType = Literal['IO', 'TOP', 'TOS']
|
||||
CommandType = Literal['bind', 'query']
|
||||
AsyncCallable = Callable[..., Awaitable[Any]]
|
||||
|
||||
763
poetry.lock
generated
763
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiocache"
|
||||
@@ -127,17 +127,17 @@ zookeeper = ["kazoo"]
|
||||
|
||||
[[package]]
|
||||
name = "arclet-alconna"
|
||||
version = "1.8.8"
|
||||
version = "1.8.11"
|
||||
description = "A High-performance, Generality, Humane Command Line Arguments Parser Library."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "arclet_alconna-1.8.8-py3-none-any.whl", hash = "sha256:0b5d1d3d4ae32894bd83d53b99194d446f117431d07ec11957d1e19cfaffb2c3"},
|
||||
{file = "arclet_alconna-1.8.8.tar.gz", hash = "sha256:d3e045a6141af472d1ca88f396eb6e048c04e2b8c61f848096d65c3460ca04e8"},
|
||||
{file = "arclet_alconna-1.8.11-py3-none-any.whl", hash = "sha256:8a0c188400214abe2b72b2e6f4f23ed8d3021270654c15bc46cad62b725b7a44"},
|
||||
{file = "arclet_alconna-1.8.11.tar.gz", hash = "sha256:3fed5e946709d63b9d7ee7cddf32b145e1f931b8ea9d0b8174cac753d965e9c8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
nepattern = ">=0.6.4,<1.0.0"
|
||||
nepattern = ">=0.7.0,<1.0.0"
|
||||
tarina = ">=0.4.4"
|
||||
typing-extensions = ">=4.5.0"
|
||||
|
||||
@@ -146,18 +146,29 @@ full = ["arclet-alconna-tools (>=0.2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "arclet-alconna-tools"
|
||||
version = "0.7.2"
|
||||
version = "0.7.3"
|
||||
description = "Builtin Tools for Alconna"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "arclet_alconna_tools-0.7.2-py3-none-any.whl", hash = "sha256:69739aab07c039537cd72b4ab6d37aff896cd794031a8ff8157e5f85e842ed9b"},
|
||||
{file = "arclet_alconna_tools-0.7.2.tar.gz", hash = "sha256:98a6aad6284efd8af34b10cd35667bd5885f530327b9ac9ea2944bf6325d0d6f"},
|
||||
{file = "arclet_alconna_tools-0.7.3-py3-none-any.whl", hash = "sha256:e7d70025900d348ced62c824a5d7569c140e64714903f8498ed4b8e5f0b2c8fe"},
|
||||
{file = "arclet_alconna_tools-0.7.3.tar.gz", hash = "sha256:f150640182983902292ab1d50ca16a811ffd76bb46eac53fa2cd0479890da337"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
arclet-alconna = ">=1.8.7"
|
||||
nepattern = ">=0.6.5,<1.0.0"
|
||||
arclet-alconna = ">=1.8.10"
|
||||
nepattern = ">=0.7.0,<1.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "5.3.3"
|
||||
description = "Extensible memoizing collections and decorators"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"},
|
||||
{file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
@@ -170,6 +181,70 @@ files = [
|
||||
{file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.16.0"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
|
||||
{file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
|
||||
{file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
|
||||
{file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
|
||||
{file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
|
||||
{file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
|
||||
{file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.7"
|
||||
@@ -206,15 +281,29 @@ files = [
|
||||
{file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "emoji"
|
||||
version = "2.11.1"
|
||||
description = "Emoji for Python"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
|
||||
files = [
|
||||
{file = "emoji-2.11.1-py2.py3-none-any.whl", hash = "sha256:b7ba25299bbf520cc8727848ae66b986da32aee27dc2887eaea2bff07226ce49"},
|
||||
{file = "emoji-2.11.1.tar.gz", hash = "sha256:062ff0b3154b6219143f8b9f4b3e5c64c35bc2b146e6e2349ab5f29e218ce1ee"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["coverage", "coveralls", "pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.0"
|
||||
version = "1.2.1"
|
||||
description = "Backport of PEP 654 (exception groups)"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
|
||||
{file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
|
||||
{file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
|
||||
{file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -222,13 +311,13 @@ test = ["pytest (>=6)"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.110.1"
|
||||
version = "0.110.3"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fastapi-0.110.1-py3-none-any.whl", hash = "sha256:5df913203c482f820d31f48e635e022f8cbfe7350e4830ef05a3163925b1addc"},
|
||||
{file = "fastapi-0.110.1.tar.gz", hash = "sha256:6feac43ec359dfe4f45b2c18ec8c94edb8dc2dfc461d417d9e626590c071baad"},
|
||||
{file = "fastapi-0.110.3-py3-none-any.whl", hash = "sha256:fd7600612f755e4050beb74001310b5a7e1796d149c2ee363124abdfa0289d32"},
|
||||
{file = "fastapi-0.110.3.tar.gz", hash = "sha256:555700b0159379e94fdbfc6bb66a0f1c43f4cf7060f25239af3d84b63a656626"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -237,7 +326,7 @@ starlette = ">=0.37.2,<0.38.0"
|
||||
typing-extensions = ">=4.8.0"
|
||||
|
||||
[package.extras]
|
||||
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
all = ["email_validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.7)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
@@ -425,6 +514,25 @@ files = [
|
||||
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "7.1.0"
|
||||
description = "Read metadata from Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"},
|
||||
{file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
perf = ["ipython"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-resources"
|
||||
version = "6.4.0"
|
||||
@@ -440,6 +548,23 @@ files = [
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["jaraco.test (>=5.4)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)", "zipp (>=3.17)"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.4"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.2"
|
||||
@@ -648,6 +773,30 @@ babel = ["Babel"]
|
||||
lingua = ["lingua"]
|
||||
testing = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
||||
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mdurl = ">=0.1,<1.0"
|
||||
|
||||
[package.extras]
|
||||
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
|
||||
code-style = ["pre-commit (>=3.0,<4.0)"]
|
||||
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
|
||||
linkify = ["linkify-it-py (>=1,<3)"]
|
||||
plugins = ["mdit-py-plugins"]
|
||||
profiling = ["gprof2dot"]
|
||||
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
||||
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "2.1.5"
|
||||
@@ -717,6 +866,17 @@ files = [
|
||||
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
description = "Markdown URL utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgpack"
|
||||
version = "1.0.8"
|
||||
@@ -883,38 +1043,38 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374"},
|
||||
{file = "mypy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612"},
|
||||
{file = "mypy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913"},
|
||||
{file = "mypy-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04"},
|
||||
{file = "mypy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf"},
|
||||
{file = "mypy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9"},
|
||||
{file = "mypy-1.9.0-py3-none-any.whl", hash = "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e"},
|
||||
{file = "mypy-1.9.0.tar.gz", hash = "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:da1cbf08fb3b851ab3b9523a884c232774008267b1f83371ace57f412fe308c2"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:12b6bfc1b1a66095ab413160a6e520e1dc076a28f3e22f7fb25ba3b000b4ef99"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e36fb078cce9904c7989b9693e41cb9711e0600139ce3970c6ef814b6ebc2b2"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2b0695d605ddcd3eb2f736cd8b4e388288c21e7de85001e9f85df9187f2b50f9"},
|
||||
{file = "mypy-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:cd777b780312ddb135bceb9bc8722a73ec95e042f911cc279e2ec3c667076051"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3be66771aa5c97602f382230165b856c231d1277c511c9a8dd058be4784472e1"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8b2cbaca148d0754a54d44121b5825ae71868c7592a53b7292eeb0f3fdae95ee"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ec404a7cbe9fc0e92cb0e67f55ce0c025014e26d33e54d9e506a0f2d07fe5de"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e22e1527dc3d4aa94311d246b59e47f6455b8729f4968765ac1eacf9a4760bc7"},
|
||||
{file = "mypy-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:a87dbfa85971e8d59c9cc1fcf534efe664d8949e4c0b6b44e8ca548e746a8d53"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a781f6ad4bab20eef8b65174a57e5203f4be627b46291f4589879bf4e257b97b"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b808e12113505b97d9023b0b5e0c0705a90571c6feefc6f215c1df9381256e30"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f55583b12156c399dce2df7d16f8a5095291354f1e839c252ec6c0611e86e2e"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cf18f9d0efa1b16478c4c129eabec36148032575391095f73cae2e722fcf9d5"},
|
||||
{file = "mypy-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc6ac273b23c6b82da3bb25f4136c4fd42665f17f2cd850771cb600bdd2ebeda"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9fd50226364cd2737351c79807775136b0abe084433b55b2e29181a4c3c878c0"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f90cff89eea89273727d8783fef5d4a934be2fdca11b47def50cf5d311aff727"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcfc70599efde5c67862a07a1aaf50e55bce629ace26bb19dc17cece5dd31ca4"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:075cbf81f3e134eadaf247de187bd604748171d6b79736fa9b6c9685b4083061"},
|
||||
{file = "mypy-1.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:3f298531bca95ff615b6e9f2fc0333aae27fa48052903a0ac90215021cdcfa4f"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa7ef5244615a2523b56c034becde4e9e3f9b034854c93639adb667ec9ec2976"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3236a4c8f535a0631f85f5fcdffba71c7feeef76a6002fcba7c1a8e57c8be1ec"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a2b5cdbb5dd35aa08ea9114436e0d79aceb2f38e32c21684dcf8e24e1e92821"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92f93b21c0fe73dc00abf91022234c79d793318b8a96faac147cd579c1671746"},
|
||||
{file = "mypy-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:28d0e038361b45f099cc086d9dd99c15ff14d0188f44ac883010e172ce86c38a"},
|
||||
{file = "mypy-1.10.0-py3-none-any.whl", hash = "sha256:f8c083976eb530019175aabadb60921e73b4f45736760826aa1689dda8208aee"},
|
||||
{file = "mypy-1.10.0.tar.gz", hash = "sha256:3d087fcbec056c4ee34974da493a826ce316947485cef3901f511848e687c131"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -941,13 +1101,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "nepattern"
|
||||
version = "0.6.5"
|
||||
version = "0.7.2"
|
||||
description = "a complex pattern, support typing"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "nepattern-0.6.5-py3-none-any.whl", hash = "sha256:6c06dc6b2e06b9b2d98c4dee024c63e10d1301dc90091548b94cf76c7dfbd8fb"},
|
||||
{file = "nepattern-0.6.5.tar.gz", hash = "sha256:dc91ad72cfa907b270ac1e99b6589cddb576d1ae52948b9f9367c86a58ec0ea8"},
|
||||
{file = "nepattern-0.7.2-py3-none-any.whl", hash = "sha256:75406623a4a533628a6fb1996a7cc79d7b535a268e2a8bf4b405197175baecca"},
|
||||
{file = "nepattern-0.7.2.tar.gz", hash = "sha256:7869e7998d870c920611b7a25649fb451c57bf2de450fb5d2e87408ff2917885"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -970,13 +1130,13 @@ nonebot2 = ">=2.2.1,<3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-adapter-kaiheila"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
description = "kaiheila adapter for nonebot2"
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "nonebot_adapter_kaiheila-0.3.3-py3-none-any.whl", hash = "sha256:3ff5427b8fad0d97609f142bd00e5627ce6c8c668871a0e9b5d30dcc4c0558c5"},
|
||||
{file = "nonebot_adapter_kaiheila-0.3.3.tar.gz", hash = "sha256:abf98c85e2f0007f51f2ef7a59608d5a8bf755e2e0eca06d640601fc9dff3018"},
|
||||
{file = "nonebot_adapter_kaiheila-0.3.4-py3-none-any.whl", hash = "sha256:a4cc0e43bd24e015b8312f1753705116274d5b7e9a68be266384dd413ca4f510"},
|
||||
{file = "nonebot_adapter_kaiheila-0.3.4.tar.gz", hash = "sha256:1fea823e5bc2bb5dc8e56a4c10a8f6698dac6e4f77d4526768275fa0925340f2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1002,13 +1162,13 @@ typing-extensions = ">=4.0.0,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-adapter-satori"
|
||||
version = "0.10.4"
|
||||
version = "0.11.5"
|
||||
description = "Satori Protocol Adapter for Nonebot2"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "nonebot_adapter_satori-0.10.4-py3-none-any.whl", hash = "sha256:4e22ea8551ccbdd3d463c3d7c6f8efdd2b23fa4b8b61afbfac70140ebc730e01"},
|
||||
{file = "nonebot_adapter_satori-0.10.4.tar.gz", hash = "sha256:1b42e1cf8b874769921ffdeb46b67c6983af169c238d290c3766a093b1ffdb88"},
|
||||
{file = "nonebot_adapter_satori-0.11.5-py3-none-any.whl", hash = "sha256:8c7a94ac6897a0001878f259880efe4df1c5232fe14a2d03bab742f28b279498"},
|
||||
{file = "nonebot_adapter_satori-0.11.5.tar.gz", hash = "sha256:ab134f0e70302958807b7f91b5c6d0e462c5d39728c93066076699d00c2d067b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1016,20 +1176,21 @@ nonebot2 = ">=2.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-alconna"
|
||||
version = "0.42.4"
|
||||
version = "0.45.3"
|
||||
description = "Alconna Adapter for Nonebot"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "nonebot_plugin_alconna-0.42.4-py3-none-any.whl", hash = "sha256:8dd843f37c1d4a13bf58966a6d592c7f685ec6346954d774b12b53e0864b98b0"},
|
||||
{file = "nonebot_plugin_alconna-0.42.4.tar.gz", hash = "sha256:5fe0ea1374bacbf2eaae6eb4a0e41fd8ebc983bab6f0224a4e64fba5984e219d"},
|
||||
{file = "nonebot_plugin_alconna-0.45.3-py3-none-any.whl", hash = "sha256:1e6ff5e99464ea2acad03df8b9fc48949b7d40a7c8d1976c53a934eafb8d3bf0"},
|
||||
{file = "nonebot_plugin_alconna-0.45.3.tar.gz", hash = "sha256:7667df82fdae02842b0fa28b39d61daf501f1af41d6fecf288fb8bb38a35ff9d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
arclet-alconna = ">=1.8.7"
|
||||
arclet-alconna-tools = ">=0.7.2"
|
||||
nepattern = ">=0.6.5"
|
||||
nonebot2 = ">=2.2.1"
|
||||
arclet-alconna = ">=1.8.11"
|
||||
arclet-alconna-tools = ">=0.7.3"
|
||||
importlib-metadata = ">=4.13.0"
|
||||
nepattern = ">=0.7.2"
|
||||
nonebot2 = ">=2.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-apscheduler"
|
||||
@@ -1065,13 +1226,13 @@ typing-extensions = ">=4.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-orm"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
description = "SQLAlchemy ORM support for nonebot"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "nonebot_plugin_orm-0.7.1-py3-none-any.whl", hash = "sha256:8e663094b0521a4838a4f7e65d53b603bfc5da77bafe9a89c912385918efb569"},
|
||||
{file = "nonebot_plugin_orm-0.7.1.tar.gz", hash = "sha256:e6ae8f93e7e2a0f3de40a4f3dbfd48684654468aea1ea3ca31009fa5aaff42d8"},
|
||||
{file = "nonebot_plugin_orm-0.7.2-py3-none-any.whl", hash = "sha256:a9fce44d7d80a98b7e07125abe26529995fde967e1f9dd390180d70734f9c596"},
|
||||
{file = "nonebot_plugin_orm-0.7.2.tar.gz", hash = "sha256:12f12e8b1be5d5f75bba0a630e5112ad4041b0ef67469dbcd7ac6557faec0953"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1097,15 +1258,33 @@ postgresql = ["sqlalchemy[postgresql-psycopgbinary]"]
|
||||
psycopg = ["sqlalchemy[postgresql-psycopgbinary]"]
|
||||
sqlite = ["sqlalchemy[aiosqlite]"]
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-userinfo"
|
||||
version = "0.2.4"
|
||||
description = "Nonebot2 用户信息获取插件"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.8"
|
||||
files = [
|
||||
{file = "nonebot_plugin_userinfo-0.2.4-py3-none-any.whl", hash = "sha256:f08dac58759b859f8bf1d1c17e96cdcee92b10613631a430405364a40181f9e4"},
|
||||
{file = "nonebot_plugin_userinfo-0.2.4.tar.gz", hash = "sha256:1d49ff00ce38c856be4388fc2a954656f07cc529ce38ef9593e3a0ea40f26b6a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cachetools = ">=5.0.0,<6.0.0"
|
||||
emoji = ">=2.0.0,<3.0.0"
|
||||
httpx = ">=0.20.0,<1.0.0"
|
||||
nonebot2 = ">=2.0.0,<3.0.0"
|
||||
strenum = ">=0.4.8,<0.5.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot2"
|
||||
version = "2.2.1"
|
||||
version = "2.3.0"
|
||||
description = "An asynchronous python bot framework."
|
||||
optional = false
|
||||
python-versions = ">=3.8,<4.0"
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "nonebot2-2.2.1-py3-none-any.whl", hash = "sha256:88f2bb456bf90922925bbe489a9effe3b09300f3aa50bfa75ee50d8a83d7330f"},
|
||||
{file = "nonebot2-2.2.1.tar.gz", hash = "sha256:fe57692300571b00724999238545d8d894523460e6835a11b326a2e1cdf98fc4"},
|
||||
{file = "nonebot2-2.3.0-py3-none-any.whl", hash = "sha256:ecde620a96197189f9f9b719ab8493b5115e5973cba0bd07ed3d737ce7418462"},
|
||||
{file = "nonebot2-2.3.0.tar.gz", hash = "sha256:0e4ebdb0eaab96008619aa4890171ea6e7e7af25eb9d1a4e753e58544c46681e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1269,6 +1448,92 @@ files = [
|
||||
numpy = {version = ">=1.26.0", markers = "python_version < \"3.13\""}
|
||||
types-pytz = ">=2022.1.1"
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.3.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"},
|
||||
{file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"},
|
||||
{file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"},
|
||||
{file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"},
|
||||
{file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"},
|
||||
{file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"},
|
||||
{file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"},
|
||||
{file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"},
|
||||
{file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "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 = "playwright"
|
||||
version = "1.43.0"
|
||||
@@ -1289,20 +1554,31 @@ files = [
|
||||
greenlet = "3.0.3"
|
||||
pyee = "11.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.7.0"
|
||||
version = "2.7.1"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.7.0-py3-none-any.whl", hash = "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352"},
|
||||
{file = "pydantic-2.7.0.tar.gz", hash = "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383"},
|
||||
{file = "pydantic-2.7.1-py3-none-any.whl", hash = "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5"},
|
||||
{file = "pydantic-2.7.1.tar.gz", hash = "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.4.0"
|
||||
pydantic-core = "2.18.1"
|
||||
pydantic-core = "2.18.2"
|
||||
typing-extensions = ">=4.6.1"
|
||||
|
||||
[package.extras]
|
||||
@@ -1310,90 +1586,90 @@ email = ["email-validator (>=2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.18.1"
|
||||
version = "2.18.2"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7"},
|
||||
{file = "pydantic_core-2.18.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6"},
|
||||
{file = "pydantic_core-2.18.1-cp310-none-win32.whl", hash = "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649"},
|
||||
{file = "pydantic_core-2.18.1-cp310-none-win_amd64.whl", hash = "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519"},
|
||||
{file = "pydantic_core-2.18.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win32.whl", hash = "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win_amd64.whl", hash = "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9"},
|
||||
{file = "pydantic_core-2.18.1-cp311-none-win_arm64.whl", hash = "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a"},
|
||||
{file = "pydantic_core-2.18.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win32.whl", hash = "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win_amd64.whl", hash = "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6"},
|
||||
{file = "pydantic_core-2.18.1-cp312-none-win_arm64.whl", hash = "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9"},
|
||||
{file = "pydantic_core-2.18.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622"},
|
||||
{file = "pydantic_core-2.18.1-cp38-none-win32.whl", hash = "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad"},
|
||||
{file = "pydantic_core-2.18.1-cp38-none-win_amd64.whl", hash = "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db"},
|
||||
{file = "pydantic_core-2.18.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6"},
|
||||
{file = "pydantic_core-2.18.1-cp39-none-win32.whl", hash = "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b"},
|
||||
{file = "pydantic_core-2.18.1-cp39-none-win_amd64.whl", hash = "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539"},
|
||||
{file = "pydantic_core-2.18.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59"},
|
||||
{file = "pydantic_core-2.18.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6"},
|
||||
{file = "pydantic_core-2.18.1.tar.gz", hash = "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563"},
|
||||
{file = "pydantic_core-2.18.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38"},
|
||||
{file = "pydantic_core-2.18.2-cp310-none-win32.whl", hash = "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027"},
|
||||
{file = "pydantic_core-2.18.2-cp310-none-win_amd64.whl", hash = "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0"},
|
||||
{file = "pydantic_core-2.18.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win32.whl", hash = "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win_amd64.whl", hash = "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3"},
|
||||
{file = "pydantic_core-2.18.2-cp311-none-win_arm64.whl", hash = "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241"},
|
||||
{file = "pydantic_core-2.18.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win32.whl", hash = "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win_amd64.whl", hash = "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438"},
|
||||
{file = "pydantic_core-2.18.2-cp312-none-win_arm64.whl", hash = "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761"},
|
||||
{file = "pydantic_core-2.18.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788"},
|
||||
{file = "pydantic_core-2.18.2-cp38-none-win32.whl", hash = "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350"},
|
||||
{file = "pydantic_core-2.18.2-cp38-none-win_amd64.whl", hash = "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399"},
|
||||
{file = "pydantic_core-2.18.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b"},
|
||||
{file = "pydantic_core-2.18.2-cp39-none-win32.whl", hash = "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e"},
|
||||
{file = "pydantic_core-2.18.2-cp39-none-win_amd64.whl", hash = "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b"},
|
||||
{file = "pydantic_core-2.18.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae"},
|
||||
{file = "pydantic_core-2.18.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374"},
|
||||
{file = "pydantic_core-2.18.2.tar.gz", hash = "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1416,6 +1692,20 @@ typing-extensions = "*"
|
||||
[package.extras]
|
||||
dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "pytest", "pytest-asyncio", "pytest-trio", "sphinx", "toml", "tox", "trio", "trio", "trio-typing", "twine", "twisted", "validate-pyproject[all]"]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.18.0"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
|
||||
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pygtrie"
|
||||
version = "2.5.0"
|
||||
@@ -1526,30 +1816,48 @@ files = [
|
||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.7.1"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
files = [
|
||||
{file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"},
|
||||
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown-it-py = ">=2.2.0"
|
||||
pygments = ">=2.13.0,<3.0.0"
|
||||
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.3.7"
|
||||
version = "0.4.3"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"},
|
||||
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
|
||||
{file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
|
||||
{file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
|
||||
{file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
|
||||
{file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
|
||||
{file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
|
||||
{file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
|
||||
{file = "ruff-0.4.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b70800c290f14ae6fcbb41bbe201cf62dfca024d124a1f373e76371a007454ce"},
|
||||
{file = "ruff-0.4.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08a0d6a22918ab2552ace96adeaca308833873a4d7d1d587bb1d37bae8728eb3"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba1f14df3c758dd7de5b55fbae7e1c8af238597961e5fb628f3de446c3c40c5"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:819fb06d535cc76dfddbfe8d3068ff602ddeb40e3eacbc90e0d1272bb8d97113"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0bfc9e955e6dc6359eb6f82ea150c4f4e82b660e5b58d9a20a0e42ec3bb6342b"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:510a67d232d2ebe983fddea324dbf9d69b71c4d2dfeb8a862f4a127536dd4cfb"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9ff11cd9a092ee7680a56d21f302bdda14327772cd870d806610a3503d001f"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29efff25bf9ee685c2c8390563a5b5c006a3fee5230d28ea39f4f75f9d0b6f2f"},
|
||||
{file = "ruff-0.4.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18b00e0bcccf0fc8d7186ed21e311dffd19761cb632241a6e4fe4477cc80ef6e"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262f5635e2c74d80b7507fbc2fac28fe0d4fef26373bbc62039526f7722bca1b"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7363691198719c26459e08cc17c6a3dac6f592e9ea3d2fa772f4e561b5fe82a3"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eeb039f8428fcb6725bb63cbae92ad67b0559e68b5d80f840f11914afd8ddf7f"},
|
||||
{file = "ruff-0.4.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:927b11c1e4d0727ce1a729eace61cee88a334623ec424c0b1c8fe3e5f9d3c865"},
|
||||
{file = "ruff-0.4.3-py3-none-win32.whl", hash = "sha256:25cacda2155778beb0d064e0ec5a3944dcca9c12715f7c4634fd9d93ac33fd30"},
|
||||
{file = "ruff-0.4.3-py3-none-win_amd64.whl", hash = "sha256:7a1c3a450bc6539ef00da6c819fb1b76b6b065dec585f91456e7c0d6a0bbc725"},
|
||||
{file = "ruff-0.4.3-py3-none-win_arm64.whl", hash = "sha256:71ca5f8ccf1121b95a59649482470c5601c60a416bf189d553955b0338e34614"},
|
||||
{file = "ruff-0.4.3.tar.gz", hash = "sha256:ff0a3ef2e3c4b6d133fbedcf9586abfbe38d076041f2dc18ffb2c7e0485d5a07"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1679,6 +1987,22 @@ anyio = ">=3.4.0,<5"
|
||||
[package.extras]
|
||||
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7)", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "strenum"
|
||||
version = "0.4.15"
|
||||
description = "An Enum that inherits from str."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"},
|
||||
{file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"]
|
||||
release = ["twine"]
|
||||
test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
|
||||
|
||||
[[package]]
|
||||
name = "tarina"
|
||||
version = "0.4.4"
|
||||
@@ -1804,15 +2128,26 @@ typing-extensions = ">=4.5,<5.0"
|
||||
[package.extras]
|
||||
test = ["beautifulsoup4 (>=4.8,<5.0)", "html5lib (==1.1)", "lxml (>=4.9)", "mypy (==1.9.*)", "pyright (>=1.1.289)", "pytest (>=7.0,<9)", "pytest-mypy-plugins (==1.11.1)", "tox (>=4.0,<5.0)", "typeguard (>=3.0,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "types-pillow"
|
||||
version = "10.2.0.20240423"
|
||||
description = "Typing stubs for Pillow"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-Pillow-10.2.0.20240423.tar.gz", hash = "sha256:696e68b9b6a58548fc307a8669830469237c5b11809ddf978ac77fafa79251cd"},
|
||||
{file = "types_Pillow-10.2.0.20240423-py3-none-any.whl", hash = "sha256:bd12923093b96c91d523efcdb66967a307f1a843bcfaf2d5a529146c10a9ced3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pytz"
|
||||
version = "2024.1.0.20240203"
|
||||
version = "2024.1.0.20240417"
|
||||
description = "Typing stubs for pytz"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-pytz-2024.1.0.20240203.tar.gz", hash = "sha256:c93751ee20dfc6e054a0148f8f5227b9a00b79c90a4d3c9f464711a73179c89e"},
|
||||
{file = "types_pytz-2024.1.0.20240203-py3-none-any.whl", hash = "sha256:9679eef0365db3af91ef7722c199dbb75ee5c1b67e3c4dd7bfbeb1b8a71c21a3"},
|
||||
{file = "types-pytz-2024.1.0.20240417.tar.gz", hash = "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981"},
|
||||
{file = "types_pytz-2024.1.0.20240417-py3-none-any.whl", hash = "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2343,7 +2678,83 @@ files = [
|
||||
idna = ">=2.0"
|
||||
multidict = ">=4.0"
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.18.1"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
|
||||
{file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "zstandard"
|
||||
version = "0.22.0"
|
||||
description = "Zstandard bindings for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "zstandard-0.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:275df437ab03f8c033b8a2c181e51716c32d831082d93ce48002a5227ec93019"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ac9957bc6d2403c4772c890916bf181b2653640da98f32e04b96e4d6fb3252a"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe3390c538f12437b859d815040763abc728955a52ca6ff9c5d4ac707c4ad98e"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1958100b8a1cc3f27fa21071a55cb2ed32e9e5df4c3c6e661c193437f171cba2"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e1856c8313bc688d5df069e106a4bc962eef3d13372020cc6e3ebf5e045202"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1a90ba9a4c9c884bb876a14be2b1d216609385efb180393df40e5172e7ecf356"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3db41c5e49ef73641d5111554e1d1d3af106410a6c1fb52cf68912ba7a343a0d"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-win32.whl", hash = "sha256:d8593f8464fb64d58e8cb0b905b272d40184eac9a18d83cf8c10749c3eafcd7e"},
|
||||
{file = "zstandard-0.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a4b358947a65b94e2501ce3e078bbc929b039ede4679ddb0460829b12f7375"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:589402548251056878d2e7c8859286eb91bd841af117dbe4ab000e6450987e08"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a97079b955b00b732c6f280d5023e0eefe359045e8b83b08cf0333af9ec78f26"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:445b47bc32de69d990ad0f34da0e20f535914623d1e506e74d6bc5c9dc40bb09"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33591d59f4956c9812f8063eff2e2c0065bc02050837f152574069f5f9f17775"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:888196c9c8893a1e8ff5e89b8f894e7f4f0e64a5af4d8f3c410f0319128bb2f8"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:53866a9d8ab363271c9e80c7c2e9441814961d47f88c9bc3b248142c32141d94"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ac59d5d6910b220141c1737b79d4a5aa9e57466e7469a012ed42ce2d3995e88"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-win32.whl", hash = "sha256:2b11ea433db22e720758cba584c9d661077121fcf60ab43351950ded20283440"},
|
||||
{file = "zstandard-0.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:11f0d1aab9516a497137b41e3d3ed4bbf7b2ee2abc79e5c8b010ad286d7464bd"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6c25b8eb733d4e741246151d895dd0308137532737f337411160ff69ca24f93a"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9b2cde1cd1b2a10246dbc143ba49d942d14fb3d2b4bccf4618d475c65464912"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a88b7df61a292603e7cd662d92565d915796b094ffb3d206579aaebac6b85d5f"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466e6ad8caefb589ed281c076deb6f0cd330e8bc13c5035854ffb9c2014b118c"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d67d0d53d2a138f9e29d8acdabe11310c185e36f0a848efa104d4e40b808e4"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:39b2853efc9403927f9065cc48c9980649462acbdf81cd4f0cb773af2fd734bc"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8a1b2effa96a5f019e72874969394edd393e2fbd6414a8208fea363a22803b45"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-win32.whl", hash = "sha256:88c5b4b47a8a138338a07fc94e2ba3b1535f69247670abfe422de4e0b344aae2"},
|
||||
{file = "zstandard-0.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:de20a212ef3d00d609d0b22eb7cc798d5a69035e81839f549b538eff4105d01c"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d75f693bb4e92c335e0645e8845e553cd09dc91616412d1d4650da835b5449df"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:36a47636c3de227cd765e25a21dc5dace00539b82ddd99ee36abae38178eff9e"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68953dc84b244b053c0d5f137a21ae8287ecf51b20872eccf8eaac0302d3e3b0"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2612e9bb4977381184bb2463150336d0f7e014d6bb5d4a370f9a372d21916f69"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23d2b3c2b8e7e5a6cb7922f7c27d73a9a615f0a5ab5d0e03dd533c477de23004"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d43501f5f31e22baf822720d82b5547f8a08f5386a883b32584a185675c8fbf"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a493d470183ee620a3df1e6e55b3e4de8143c0ba1b16f3ded83208ea8ddfd91d"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-win32.whl", hash = "sha256:7034d381789f45576ec3f1fa0e15d741828146439228dc3f7c59856c5bcd3292"},
|
||||
{file = "zstandard-0.22.0-cp38-cp38-win_amd64.whl", hash = "sha256:d8fff0f0c1d8bc5d866762ae95bd99d53282337af1be9dc0d88506b340e74b73"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2fdd53b806786bd6112d97c1f1e7841e5e4daa06810ab4b284026a1a0e484c0b"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73a1d6bd01961e9fd447162e137ed949c01bdb830dfca487c4a14e9742dccc93"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9501f36fac6b875c124243a379267d879262480bf85b1dbda61f5ad4d01b75a3"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48f260e4c7294ef275744210a4010f116048e0c95857befb7462e033f09442fe"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959665072bd60f45c5b6b5d711f15bdefc9849dd5da9fb6c873e35f5d34d8cfb"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d22fdef58976457c65e2796e6730a3ea4a254f3ba83777ecfc8592ff8d77d303"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a7ccf5825fd71d4542c8ab28d4d482aace885f5ebe4b40faaa290eed8e095a4c"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-win32.whl", hash = "sha256:f058a77ef0ece4e210bb0450e68408d4223f728b109764676e1a13537d056bb0"},
|
||||
{file = "zstandard-0.22.0-cp39-cp39-win_amd64.whl", hash = "sha256:e9e9d4e2e336c529d4c435baad846a181e39a982f823f7e4495ec0b0ec8538d2"},
|
||||
{file = "zstandard-0.22.0.tar.gz", hash = "sha256:8226a33c542bcb54cd6bd0a366067b610b41713b64c9abec1bc4533d69f51e70"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
cffi = ["cffi (>=1.11)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "c3fe3de570a684d28ff18e45c1161dc081f5b92d530098ee1170146e436f4494"
|
||||
content-hash = "0bdc912fa16ac0774edb3c5343152ecbec81f8a9764bb7e41e32dbf8259127ad"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = 'nonebot-plugin-tetris-stats'
|
||||
version = '1.0.0.a15'
|
||||
version = '1.1.1'
|
||||
description = '一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件'
|
||||
authors = ['scdhh <wallfjjd@gmail.com>']
|
||||
readme = 'README.md'
|
||||
@@ -10,7 +10,7 @@ license = 'AGPL-3.0'
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = '^3.10'
|
||||
nonebot2 = '^2.2.0'
|
||||
nonebot2 = { extras = ["fastapi"], version = "^2.3.0" }
|
||||
lxml = '^5.1.0'
|
||||
pandas = '>=1.4.3,<3.0.0'
|
||||
playwright = '^1.41.2'
|
||||
@@ -22,6 +22,11 @@ httpx = "^0.27.0"
|
||||
nonebot-plugin-alconna = ">=0.40"
|
||||
nonebot-plugin-apscheduler = "^0.4.0"
|
||||
aiocache = "^0.12.2"
|
||||
zstandard = "^0.22.0"
|
||||
jinja2 = "^3.1.3"
|
||||
nonebot-plugin-userinfo = "^0.2.4"
|
||||
pillow = "^10.3.0"
|
||||
rich = "^13.7.1"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = '>=1.9'
|
||||
@@ -29,13 +34,13 @@ types-ujson = '^5.9.0'
|
||||
pandas-stubs = '>=1.5.2,<3.0.0'
|
||||
ruff = '>=0.3.0'
|
||||
types-aiofiles = "^23.2.0.20240106"
|
||||
nonebot2 = { extras = ["fastapi"], version = "^2.2.0" }
|
||||
types-lxml = "^2024.2.9"
|
||||
nonebot-plugin-orm = { extras = ["default"], version = ">=0.3,<0.8" }
|
||||
nonebot-adapter-onebot = "^2.4.1"
|
||||
nonebot-adapter-satori = "^0.10.0"
|
||||
nonebot-adapter-kaiheila = "^0.3.1"
|
||||
nonebot-adapter-satori = "^0.11.4"
|
||||
nonebot-adapter-kaiheila = "^0.3.4"
|
||||
nonebot-adapter-discord = "^0.1.3"
|
||||
types-pillow = "^10.2.0.20240423"
|
||||
|
||||
[tool.poetry.group.debug.dependencies]
|
||||
objprint = '^0.2.2'
|
||||
@@ -46,6 +51,10 @@ requires = ['poetry-core>=1.0.0']
|
||||
build-backend = 'poetry.core.masonry.api'
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
'F', # pyflakes
|
||||
'E', # pycodestyle errors
|
||||
@@ -86,14 +95,12 @@ ignore = [
|
||||
'ANN202', # 向 NoneBot 注册的函数
|
||||
'TRY003',
|
||||
]
|
||||
line-length = 120
|
||||
target-version = "py310"
|
||||
flake8-quotes = { inline-quotes = 'single', multiline-quotes = 'double' }
|
||||
|
||||
[tool.ruff.flake8-annotations]
|
||||
[tool.ruff.lint.flake8-annotations]
|
||||
mypy-init-return = true
|
||||
|
||||
[tool.ruff.flake8-builtins]
|
||||
[tool.ruff.lint.flake8-builtins]
|
||||
builtins-ignorelist = ["id"]
|
||||
|
||||
[tool.ruff.format]
|
||||
|
||||
Reference in New Issue
Block a user