mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20dcc2bc3d | |||
| 606dddbca2 | |||
| f509b03cd0 | |||
| 6293d088db | |||
| 97e2abed78 | |||
|
|
5ea3fcb234 | ||
|
|
ca33ba1310 | ||
| 3629a2ff4a | |||
| a2108c9776 | |||
| 7133cd9384 | |||
|
|
406bc7674e | ||
|
|
259b38fda5 | ||
|
|
414345ae5c | ||
|
|
341cbd86cd | ||
|
|
bf7804738e | ||
|
|
553f373671 |
@@ -7,7 +7,7 @@ ci:
|
||||
autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks'
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.5.7
|
||||
rev: v0.6.1
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
from nonebot_plugin_localstore import get_cache_dir, get_data_dir
|
||||
from nonebot import get_plugin_config
|
||||
from nonebot_plugin_localstore import get_plugin_cache_dir, get_plugin_data_dir
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
CACHE_PATH = get_cache_dir('nonebot_plugin_tetris_stats')
|
||||
DATA_PATH = get_data_dir('nonebot_plugin_tetris_stats')
|
||||
CACHE_PATH = get_plugin_cache_dir()
|
||||
DATA_PATH = get_plugin_data_dir()
|
||||
|
||||
|
||||
class Proxy(BaseModel):
|
||||
main: str | None = None
|
||||
github: str | None = None
|
||||
tetrio: str | None = None
|
||||
tos: str | None = None
|
||||
top: str | None = None
|
||||
|
||||
|
||||
class ScopedConfig(BaseModel):
|
||||
request_timeout: float = 30.0
|
||||
screenshot_quality: float = 2
|
||||
proxy: Proxy = Field(default_factory=Proxy)
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
tetris: ScopedConfig = Field(default_factory=ScopedConfig)
|
||||
|
||||
|
||||
config = get_plugin_config(Config)
|
||||
|
||||
@@ -19,7 +19,6 @@ from sqlalchemy import desc, select
|
||||
from sqlalchemy.dialects import sqlite
|
||||
from sqlalchemy.ext.automap import automap_base
|
||||
from sqlalchemy.orm import Session
|
||||
from ujson import dumps, loads
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
@@ -31,6 +30,8 @@ depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def migrate_old_data() -> None:
|
||||
from json import dumps, loads
|
||||
|
||||
Base = automap_base() # noqa: N806
|
||||
Base.prepare(autoload_with=op.get_bind())
|
||||
OldHistoricalData = Base.classes.nonebot_plugin_tetris_stats_historicaldata # noqa: N806
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
"""add TETRIOLeagueStats
|
||||
|
||||
迁移 ID: 5a1b93948494
|
||||
父迁移: cfeab6961dce
|
||||
创建时间: 2024-08-24 00:22:41.359500
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
|
||||
revision: str = '5a1b93948494'
|
||||
down_revision: str | Sequence[str] | None = 'cfeab6961dce'
|
||||
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! ###
|
||||
op.create_table(
|
||||
'nonebot_plugin_tetris_stats_tetrioleaguestats',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_tetrioleaguestats')),
|
||||
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||
)
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestats', schema=None) as batch_op:
|
||||
batch_op.create_index(
|
||||
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestats_update_time'), ['update_time'], unique=False
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'nonebot_plugin_tetris_stats_tetrioleaguehistorical',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('request_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('data', sa.JSON(), nullable=False),
|
||||
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||
sa.Column('stats_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
['stats_id'],
|
||||
['nonebot_plugin_tetris_stats_tetrioleaguestats.id'],
|
||||
name=op.f(
|
||||
'fk_nonebot_plugin_tetris_stats_tetrioleaguehistorical_stats_id_nonebot_plugin_tetris_stats_tetrioleaguestats'
|
||||
),
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_tetrioleaguehistorical')),
|
||||
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||
)
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguehistorical', schema=None) as batch_op:
|
||||
batch_op.create_index(
|
||||
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_request_id'), ['request_id'], unique=False
|
||||
)
|
||||
batch_op.create_index(
|
||||
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_update_time'),
|
||||
['update_time'],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
'nonebot_plugin_tetris_stats_tetrioleaguestatsfield',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('rank', sa.String(length=2), nullable=False),
|
||||
sa.Column('tr_line', sa.Float(), nullable=False),
|
||||
sa.Column('player_count', sa.Integer(), nullable=False),
|
||||
sa.Column('low_pps', sa.JSON(), nullable=False),
|
||||
sa.Column('low_apm', sa.JSON(), nullable=False),
|
||||
sa.Column('low_vs', sa.JSON(), nullable=False),
|
||||
sa.Column('avg_pps', sa.Float(), nullable=False),
|
||||
sa.Column('avg_apm', sa.Float(), nullable=False),
|
||||
sa.Column('avg_vs', sa.Float(), nullable=False),
|
||||
sa.Column('high_pps', sa.JSON(), nullable=False),
|
||||
sa.Column('high_apm', sa.JSON(), nullable=False),
|
||||
sa.Column('high_vs', sa.JSON(), nullable=False),
|
||||
sa.Column('stats_id', sa.Integer(), nullable=False),
|
||||
sa.ForeignKeyConstraint(
|
||||
['stats_id'],
|
||||
['nonebot_plugin_tetris_stats_tetrioleaguestats.id'],
|
||||
name=op.f(
|
||||
'fk_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_stats_id_nonebot_plugin_tetris_stats_tetrioleaguestats'
|
||||
),
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_tetrioleaguestatsfield')),
|
||||
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||
)
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestatsfield', schema=None) as batch_op:
|
||||
batch_op.create_index(
|
||||
batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_rank'), ['rank'], 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_tetrioleaguestatsfield', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_rank'))
|
||||
|
||||
op.drop_table('nonebot_plugin_tetris_stats_tetrioleaguestatsfield')
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguehistorical', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_update_time'))
|
||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguehistorical_request_id'))
|
||||
|
||||
op.drop_table('nonebot_plugin_tetris_stats_tetrioleaguehistorical')
|
||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestats', schema=None) as batch_op:
|
||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestats_update_time'))
|
||||
|
||||
op.drop_table('nonebot_plugin_tetris_stats_tetrioleaguestats')
|
||||
# ### end Alembic commands ###
|
||||
@@ -13,7 +13,6 @@ from typing import TYPE_CHECKING
|
||||
from alembic import op
|
||||
from sqlalchemy.ext.automap import automap_base
|
||||
from sqlalchemy.orm import Session
|
||||
from ujson import dumps, loads
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
@@ -27,6 +26,7 @@ depends_on: str | Sequence[str] | None = None
|
||||
def upgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
from json import dumps, loads
|
||||
|
||||
Base = automap_base() # noqa: N806
|
||||
connection = op.get_bind()
|
||||
@@ -50,6 +50,7 @@ def upgrade(name: str = '') -> None:
|
||||
def downgrade(name: str = '') -> None:
|
||||
if name:
|
||||
return
|
||||
from json import dumps, loads
|
||||
|
||||
Base = automap_base() # noqa: N806
|
||||
connection = op.get_bind()
|
||||
|
||||
@@ -23,7 +23,7 @@ command = Subcommand(
|
||||
)
|
||||
|
||||
|
||||
from . import bind, config, query, record # noqa: E402
|
||||
from . import bind, config, list, query, rank, record # noqa: E402
|
||||
|
||||
main_command.add(command)
|
||||
|
||||
@@ -31,6 +31,8 @@ __all__ = [
|
||||
'alc',
|
||||
'bind',
|
||||
'config',
|
||||
'list',
|
||||
'query',
|
||||
'rank',
|
||||
'record',
|
||||
]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from .player import Player
|
||||
from .schemas.user import User
|
||||
from .schemas.user_info import UserInfoSuccess
|
||||
from .tetra_league import full_export as tetra_league_full_export
|
||||
|
||||
__all__ = ['Player', 'User', 'UserInfoSuccess', 'tetra_league_full_export']
|
||||
__all__ = ['Player', 'User', 'UserInfoSuccess']
|
||||
|
||||
@@ -6,25 +6,29 @@ from weakref import WeakValueDictionary
|
||||
from aiocache import Cache as ACache # type: ignore[import-untyped]
|
||||
from nonebot.compat import type_validate_json
|
||||
from nonebot.log import logger
|
||||
from yarl import URL
|
||||
|
||||
from ....config.config import config
|
||||
from ....utils.request import Request
|
||||
from .schemas.base import FailedModel, SuccessModel
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
request = Request(config.tetris.proxy.tetrio or config.tetris.proxy.main)
|
||||
|
||||
|
||||
class Cache:
|
||||
cache = ACache(ACache.MEMORY)
|
||||
task: ClassVar[WeakValueDictionary[str, Lock]] = WeakValueDictionary()
|
||||
task: ClassVar[WeakValueDictionary[URL, Lock]] = WeakValueDictionary()
|
||||
|
||||
@classmethod
|
||||
async def get(cls, url: str) -> bytes:
|
||||
async def get(cls, url: URL, extra_headers: dict | None = None) -> bytes:
|
||||
lock = cls.task.setdefault(url, Lock())
|
||||
async with lock:
|
||||
if (cached_data := await cls.cache.get(url)) is not None:
|
||||
logger.debug(f'{url}: Cache hit!')
|
||||
return cached_data
|
||||
response_data = await Request.request(url)
|
||||
response_data = await request.request(url, extra_headers, enable_anti_cloudflare=True)
|
||||
parsed_data: SuccessModel | FailedModel = type_validate_json(SuccessModel | FailedModel, response_data) # type: ignore[arg-type]
|
||||
if isinstance(parsed_data, SuccessModel):
|
||||
await cls.cache.add(
|
||||
|
||||
89
nonebot_plugin_tetris_stats/games/tetrio/api/leaderboards.py
Normal file
89
nonebot_plugin_tetris_stats/games/tetrio/api/leaderboards.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from typing import Literal, overload
|
||||
from uuid import UUID
|
||||
|
||||
from nonebot.compat import type_validate_json
|
||||
from yarl import URL
|
||||
|
||||
from ....utils.exception import RequestError
|
||||
from ..constant import BASE_URL
|
||||
from .cache import Cache
|
||||
from .schemas.base import FailedModel
|
||||
from .schemas.leaderboards import Parameter
|
||||
from .schemas.leaderboards.by import By, BySuccessModel
|
||||
from .schemas.leaderboards.solo import Solo, SoloSuccessModel
|
||||
from .schemas.leaderboards.zenith import Zenith, ZenithSuccessModel
|
||||
|
||||
|
||||
async def by(
|
||||
by_type: Literal['league', 'xp', 'ar'], parameter: Parameter, x_session_id: UUID | None = None
|
||||
) -> BySuccessModel:
|
||||
model: By = type_validate_json(
|
||||
By, # type: ignore[arg-type]
|
||||
await get(
|
||||
BASE_URL / f'users/by/{by_type}',
|
||||
parameter,
|
||||
{'X-Session-ID': str(x_session_id)} if x_session_id is not None else None,
|
||||
),
|
||||
)
|
||||
if isinstance(model, FailedModel):
|
||||
msg = f'排行榜信息请求错误:\n{model.error}'
|
||||
raise RequestError(msg)
|
||||
return model
|
||||
|
||||
|
||||
@overload
|
||||
async def records(
|
||||
records_type: Literal['40l', 'blitz'],
|
||||
scope: str = '_global',
|
||||
revolution_id: str | None = None,
|
||||
*,
|
||||
parameter: Parameter,
|
||||
) -> SoloSuccessModel: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def records(
|
||||
records_type: Literal['zenith', 'zenithex'],
|
||||
scope: str = '_global',
|
||||
revolution_id: str | None = None,
|
||||
*,
|
||||
parameter: Parameter,
|
||||
) -> ZenithSuccessModel: ...
|
||||
|
||||
|
||||
async def records(
|
||||
records_type: Literal['40l', 'blitz', 'zenith', 'zenithex'],
|
||||
scope: str = '_global',
|
||||
revolution_id: str | None = None,
|
||||
*,
|
||||
parameter: Parameter,
|
||||
) -> SoloSuccessModel | ZenithSuccessModel:
|
||||
model: Solo | Zenith
|
||||
match records_type:
|
||||
case '40l' | 'blitz':
|
||||
model = type_validate_json(
|
||||
Solo, # type: ignore[arg-type]
|
||||
await get(
|
||||
BASE_URL / 'records' / f'{records_type}{scope}{revolution_id if revolution_id is not None else ""}',
|
||||
parameter,
|
||||
),
|
||||
)
|
||||
case 'zenith' | 'zenithex':
|
||||
model = type_validate_json(
|
||||
Zenith, # type: ignore[arg-type]
|
||||
await get(
|
||||
BASE_URL / 'records' / f'{records_type}{scope}{revolution_id if revolution_id is not None else ""}',
|
||||
parameter,
|
||||
),
|
||||
)
|
||||
case _:
|
||||
msg = f'records_type: {records_type} is not supported'
|
||||
raise ValueError(msg)
|
||||
if isinstance(model, FailedModel):
|
||||
msg = f'排行榜信息请求错误:\n{model.error}' # type: ignore[attr-defined]
|
||||
raise RequestError(msg)
|
||||
return model
|
||||
|
||||
|
||||
async def get(url: URL, parameter: Parameter, extra_headers: dict | None = None) -> bytes:
|
||||
return await Cache.get(url % parameter.to_params(), extra_headers)
|
||||
@@ -7,7 +7,6 @@ from nonebot.compat import type_validate_json
|
||||
|
||||
from ....db import anti_duplicate_add
|
||||
from ....utils.exception import RequestError
|
||||
from ....utils.request import splice_url
|
||||
from ..constant import BASE_URL, USER_ID, USER_NAME
|
||||
from .cache import Cache
|
||||
from .models import TETRIOHistoricalData
|
||||
@@ -88,12 +87,7 @@ class Player:
|
||||
|
||||
@property
|
||||
def _request_user_parameter(self) -> str:
|
||||
if self.user_id is not None:
|
||||
return self.user_id
|
||||
if self.user_name is not None:
|
||||
return self.user_name.lower()
|
||||
msg = 'Invalid user'
|
||||
raise ValueError(msg)
|
||||
return self.user_id or cast(str, self.user_name).lower()
|
||||
|
||||
@property
|
||||
async def user(self) -> User:
|
||||
@@ -117,7 +111,7 @@ class Player:
|
||||
async def get_info(self) -> UserInfoSuccess:
|
||||
"""Get User Info"""
|
||||
if self._user_info is None:
|
||||
raw_user_info = await Cache.get(splice_url([BASE_URL, 'users/', f'{self._request_user_parameter}']))
|
||||
raw_user_info = await Cache.get(BASE_URL / 'users' / self._request_user_parameter)
|
||||
user_info: UserInfo = type_validate_json(UserInfo, raw_user_info) # type: ignore[arg-type]
|
||||
if isinstance(user_info, FailedModel):
|
||||
msg = f'用户信息请求错误:\n{user_info.error}'
|
||||
@@ -147,7 +141,7 @@ class Player:
|
||||
async def get_summaries(self, summaries_type: Summaries) -> SummariesModel:
|
||||
if summaries_type not in self._summaries:
|
||||
raw_summaries = await Cache.get(
|
||||
splice_url([BASE_URL, 'users/', f'{self._request_user_parameter}/', 'summaries/', summaries_type])
|
||||
BASE_URL / 'users' / self._request_user_parameter / 'summaries' / summaries_type
|
||||
)
|
||||
summaries: SummariesModel | FailedModel = type_validate_json(
|
||||
self.__SUMMARIES_MAPPING[summaries_type] | FailedModel, # type: ignore[arg-type]
|
||||
@@ -217,16 +211,7 @@ class Player:
|
||||
async def get_records(self, mode_type: RecordModeType, records_type: RecordType) -> RecordsSoloSuccessModel:
|
||||
if (record_key := RecordKey(mode_type, records_type)) not in self._records:
|
||||
raw_records = await Cache.get(
|
||||
splice_url(
|
||||
[
|
||||
BASE_URL,
|
||||
'users/',
|
||||
f'{self._request_user_parameter}/',
|
||||
'records/',
|
||||
f'{mode_type}/',
|
||||
records_type,
|
||||
]
|
||||
)
|
||||
BASE_URL / 'users' / self._request_user_parameter / 'records' / mode_type / records_type,
|
||||
)
|
||||
records: RecordsSoloSuccessModel | FailedModel = type_validate_json(SoloRecord, raw_records) # type: ignore[arg-type]
|
||||
if isinstance(records, FailedModel):
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...typing import Prisecter
|
||||
|
||||
|
||||
class AggregateStats(BaseModel):
|
||||
@@ -39,11 +41,29 @@ class Garbage(BaseModel):
|
||||
cleared: int
|
||||
|
||||
|
||||
class P(BaseModel): # what is P
|
||||
class P(BaseModel):
|
||||
pri: float
|
||||
sec: float
|
||||
ter: float
|
||||
|
||||
def to_prisecter(self) -> Prisecter:
|
||||
return Prisecter(f'{self.pri}:{self.sec}:{self.ter}')
|
||||
|
||||
|
||||
class ArCounts(BaseModel):
|
||||
bronze: int | None = Field(default=None, alias='1')
|
||||
silver: int | None = Field(default=None, alias='2')
|
||||
gold: int | None = Field(default=None, alias='3')
|
||||
platinum: int | None = Field(default=None, alias='4')
|
||||
diamond: int | None = Field(default=None, alias='5')
|
||||
issued: int | None = Field(default=None, alias='100')
|
||||
top3: int | None = Field(default=None, alias='t3')
|
||||
top5: int | None = Field(default=None, alias='t5')
|
||||
top10: int | None = Field(default=None, alias='t10')
|
||||
top25: int | None = Field(default=None, alias='t25')
|
||||
top50: int | None = Field(default=None, alias='t50')
|
||||
top100: int | None = Field(default=None, alias='t100')
|
||||
|
||||
|
||||
class Cache(BaseModel):
|
||||
status: str
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...typing import Prisecter
|
||||
|
||||
|
||||
class Parameter(BaseModel):
|
||||
after: Prisecter | None = None
|
||||
before: Prisecter | None = None
|
||||
limit: int = Field(default=25, ge=1, le=100)
|
||||
country: str | None = None
|
||||
|
||||
def to_params(self) -> dict[str, Any]:
|
||||
return self.model_dump(exclude_defaults=True)
|
||||
@@ -1,27 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..base import SuccessModel
|
||||
from .base import Entry as BaseEntry
|
||||
|
||||
|
||||
class ArCounts(BaseModel):
|
||||
bronze: int | None = Field(None, alias='1')
|
||||
silver: int | None = Field(None, alias='2')
|
||||
gold: int | None = Field(None, alias='3')
|
||||
platinum: int | None = Field(None, alias='4')
|
||||
diamond: int | None = Field(None, alias='5')
|
||||
issued: int | None = Field(None, alias='100')
|
||||
top10: int | None = Field(None, alias='t10')
|
||||
|
||||
|
||||
class Entry(BaseEntry):
|
||||
ar: int
|
||||
ar_counts: ArCounts
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
entries: list[Entry]
|
||||
|
||||
|
||||
class ArSuccessModel(SuccessModel):
|
||||
data: Data
|
||||
@@ -1,30 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...typing import Rank
|
||||
from ..base import P
|
||||
|
||||
|
||||
class League(BaseModel):
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
rating: int
|
||||
rank: Rank
|
||||
decaying: bool
|
||||
|
||||
|
||||
class Entry(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
role: str
|
||||
xp: float
|
||||
league: League
|
||||
supporter: bool | None = None
|
||||
verified: bool
|
||||
country: str | None = None
|
||||
ts: datetime
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
gametime: float
|
||||
p: P
|
||||
@@ -0,0 +1,50 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...typing import Rank, ValidRank
|
||||
from ..base import ArCounts, FailedModel, P, SuccessModel
|
||||
|
||||
|
||||
class League(BaseModel):
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
tr: float
|
||||
gxe: float
|
||||
rank: Rank
|
||||
bestrank: ValidRank
|
||||
glicko: float
|
||||
rd: float
|
||||
apm: float
|
||||
pps: float
|
||||
vs: float
|
||||
decaying: bool
|
||||
|
||||
|
||||
class Entry(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop']
|
||||
ts: datetime | None = None
|
||||
xp: float
|
||||
country: str | None = None
|
||||
supporter: bool | None = None
|
||||
league: League
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
gametime: float
|
||||
ar: int
|
||||
ar_counts: ArCounts
|
||||
p: P
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
entries: list[Entry]
|
||||
|
||||
|
||||
class BySuccessModel(SuccessModel):
|
||||
data: Data
|
||||
|
||||
|
||||
By = BySuccessModel | FailedModel
|
||||
@@ -1,6 +1,6 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..base import SuccessModel
|
||||
from ..base import FailedModel, SuccessModel
|
||||
from ..summaries.solo import Record
|
||||
|
||||
|
||||
@@ -10,3 +10,6 @@ class Data(BaseModel):
|
||||
|
||||
class SoloSuccessModel(SuccessModel):
|
||||
data: Data
|
||||
|
||||
|
||||
Solo = SoloSuccessModel | FailedModel
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..base import SuccessModel
|
||||
from .base import Entry
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
entries: list[Entry]
|
||||
|
||||
|
||||
class XpSuccessModel(SuccessModel):
|
||||
data: Data
|
||||
@@ -1,6 +1,6 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..base import SuccessModel
|
||||
from ..base import FailedModel, SuccessModel
|
||||
from ..summaries.zenith import Record
|
||||
|
||||
|
||||
@@ -10,3 +10,6 @@ class Data(BaseModel):
|
||||
|
||||
class ZenithSuccessModel(SuccessModel):
|
||||
data: Data
|
||||
|
||||
|
||||
Zenith = ZenithSuccessModel | FailedModel
|
||||
|
||||
@@ -7,5 +7,4 @@ class User(BaseModel):
|
||||
avatar_revision: int | None
|
||||
banner_revision: int | None
|
||||
country: str | None
|
||||
verified: int | None = None
|
||||
supporter: int
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..typing import Rank
|
||||
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 | None = None
|
||||
verified: bool
|
||||
country: str | None = None
|
||||
|
||||
|
||||
class _League(BaseModel):
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
rating: float
|
||||
rank: Rank
|
||||
bestrank: Rank
|
||||
decaying: bool
|
||||
|
||||
|
||||
class ValidLeague(_League):
|
||||
glicko: float
|
||||
rd: float
|
||||
apm: float
|
||||
pps: float
|
||||
vs: float
|
||||
|
||||
|
||||
class ValidUser(_User):
|
||||
league: ValidLeague
|
||||
|
||||
|
||||
class InvalidLeague(_League):
|
||||
glicko: float | None = None
|
||||
rd: float | None = None
|
||||
apm: float | None = None
|
||||
pps: float | None = None
|
||||
vs: float | None = None
|
||||
|
||||
|
||||
class InvalidUser(_User):
|
||||
league: InvalidLeague
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
users: list[ValidUser | InvalidUser]
|
||||
|
||||
|
||||
class TetraLeagueSuccess(BaseSuccessModel):
|
||||
data: Data
|
||||
|
||||
|
||||
TetraLeague = TetraLeagueSuccess | FailedModel
|
||||
@@ -3,7 +3,7 @@ from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .base import FailedModel
|
||||
from .base import ArCounts, FailedModel
|
||||
from .base import SuccessModel as BaseSuccessModel
|
||||
|
||||
|
||||
@@ -14,13 +14,19 @@ class Badge(BaseModel):
|
||||
ts: datetime | Literal[False] | None = None
|
||||
|
||||
|
||||
class Discord(BaseModel):
|
||||
class Connection(BaseModel):
|
||||
id: str
|
||||
username: str
|
||||
display_username: str
|
||||
|
||||
|
||||
class Connections(BaseModel):
|
||||
discord: Discord | None = None
|
||||
discord: Connection | None = None
|
||||
twitch: Connection | None = None
|
||||
twitter: Connection | None = None
|
||||
reddit: Connection | None = None
|
||||
youtube: Connection | None = None
|
||||
steam: Connection | None = None
|
||||
|
||||
|
||||
class Distinguishment(BaseModel):
|
||||
@@ -28,9 +34,9 @@ class Distinguishment(BaseModel):
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
id: str = Field(default=..., alias='_id')
|
||||
username: str
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'banned']
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'hidden', 'banned']
|
||||
ts: datetime | None = None
|
||||
botmaster: str | None = None
|
||||
badges: list[Badge]
|
||||
@@ -42,7 +48,6 @@ class Data(BaseModel):
|
||||
badstanding: bool | None = None
|
||||
supporter: bool | None = None # osk说是必有, 但实际上不是 fkosk
|
||||
supporter_tier: int
|
||||
verified: bool | None = None
|
||||
avatar_revision: int | None = None
|
||||
"""This user's avatar ID. Get their avatar at
|
||||
|
||||
@@ -57,6 +62,9 @@ class Data(BaseModel):
|
||||
connections: Connections
|
||||
friend_count: int | None = None
|
||||
distinguishment: Distinguishment | None = None
|
||||
achievements: list[int]
|
||||
ar: int
|
||||
ar_counts: ArCounts
|
||||
|
||||
|
||||
class UserInfoSuccess(BaseSuccessModel):
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
from typing import Literal, NamedTuple, TypedDict, overload
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from nonebot.compat import type_validate_json
|
||||
|
||||
from ....utils.exception import RequestError
|
||||
from ....utils.request import splice_url
|
||||
from ..constant import BASE_URL
|
||||
from .cache import Cache
|
||||
from .schemas.base import FailedModel
|
||||
from .schemas.tetra_league import TetraLeague, TetraLeagueSuccess
|
||||
|
||||
|
||||
class Parameter(TypedDict, total=False):
|
||||
after: float
|
||||
before: float
|
||||
limit: int
|
||||
country: str
|
||||
|
||||
|
||||
async def leaderboard(parameter: Parameter | None = None) -> TetraLeagueSuccess:
|
||||
league: TetraLeague = type_validate_json(
|
||||
TetraLeague, # type: ignore[arg-type]
|
||||
(await Cache.get(splice_url([BASE_URL, 'users/lists/league', f'?{urlencode(parameter or {})}']))),
|
||||
)
|
||||
if isinstance(league, FailedModel):
|
||||
msg = f'排行榜数据请求错误:\n{league.error}'
|
||||
raise RequestError(msg)
|
||||
return league
|
||||
|
||||
|
||||
class FullExport(NamedTuple):
|
||||
model: TetraLeagueSuccess
|
||||
original: bytes
|
||||
|
||||
|
||||
@overload
|
||||
async def full_export(*, with_original: Literal[False]) -> TetraLeagueSuccess: ...
|
||||
|
||||
|
||||
@overload
|
||||
async def full_export(*, with_original: Literal[True]) -> FullExport: ...
|
||||
|
||||
|
||||
async def full_export(*, with_original: bool) -> TetraLeagueSuccess | FullExport:
|
||||
full: TetraLeague = type_validate_json(
|
||||
TetraLeague, # type: ignore[arg-type]
|
||||
(data := await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))),
|
||||
)
|
||||
if isinstance(full, FailedModel):
|
||||
msg = f'排行榜数据请求错误:\n{full.error}'
|
||||
raise RequestError(msg)
|
||||
if with_original:
|
||||
return FullExport(full, data)
|
||||
return full
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Literal
|
||||
from typing import Literal, NewType
|
||||
|
||||
S1ValidRank = Literal[
|
||||
'x+',
|
||||
@@ -43,3 +43,5 @@ Records = Literal[
|
||||
'blitz_recent',
|
||||
'blitz_progression',
|
||||
]
|
||||
|
||||
Prisecter = NewType('Prisecter', str)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from hashlib import md5
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from arclet.alconna import Arg, ArgFlag
|
||||
from nonebot_plugin_alconna import Args, Subcommand
|
||||
@@ -9,6 +8,7 @@ from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from nonebot_plugin_user import User
|
||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo
|
||||
from yarl import URL
|
||||
|
||||
from ...db import BindStatus, create_or_update_bind, trigger
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
@@ -67,7 +67,10 @@ async def _(nb_user: User, account: Player, event_session: EventSession, bot_inf
|
||||
platform='TETR.IO',
|
||||
status='unknown',
|
||||
user=People(
|
||||
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
|
||||
avatar=str(
|
||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}')
|
||||
% {'revision': avatar_revision}
|
||||
)
|
||||
if (avatar_revision := (await account.avatar_revision)) is not None and avatar_revision != 0
|
||||
else Avatar(type='identicon', hash=md5(user.ID.encode()).hexdigest()), # noqa: S324
|
||||
name=user.name.upper(),
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
from re import compile
|
||||
from typing import Literal
|
||||
|
||||
from yarl import URL
|
||||
|
||||
from .api.typing import ValidRank
|
||||
|
||||
GAME_TYPE: Literal['IO'] = 'IO'
|
||||
|
||||
BASE_URL = 'https://ch.tetr.io/api/'
|
||||
BASE_URL = URL('https://ch.tetr.io/api/')
|
||||
|
||||
RANK_PERCENTILE: dict[ValidRank, float] = {
|
||||
'x+': 0.2,
|
||||
'x': 1,
|
||||
'u': 5,
|
||||
'ss': 11,
|
||||
|
||||
90
nonebot_plugin_tetris_stats/games/tetrio/list.py
Normal file
90
nonebot_plugin_tetris_stats/games/tetrio/list.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from nonebot_plugin_alconna import Args, Option, Subcommand
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
|
||||
from ...db import trigger
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.metrics import get_metrics
|
||||
from ...utils.render import render
|
||||
from ...utils.render.schemas.tetrio.user.list_v2 import List, TetraLeague, User
|
||||
from ...utils.screenshot import screenshot
|
||||
from .. import alc
|
||||
from . import command
|
||||
from .api.leaderboards import by
|
||||
from .api.schemas.base import P
|
||||
from .api.schemas.leaderboards import Parameter
|
||||
from .constant import GAME_TYPE
|
||||
|
||||
command.add(
|
||||
Subcommand(
|
||||
'list',
|
||||
Option('--max-tr', Args['max_tr', float], help_text='TR的上限'),
|
||||
Option('--min-tr', Args['min_tr', float], help_text='TR的下限'),
|
||||
Option('--limit', Args['limit', int], help_text='查询数量'),
|
||||
Option('--country', Args['country', str], help_text='国家代码'),
|
||||
help_text='查询 TETR.IO 段位排行榜',
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@alc.assign('TETRIO.list')
|
||||
async def _(
|
||||
event_session: EventSession,
|
||||
max_tr: float | None = None,
|
||||
min_tr: float | None = None,
|
||||
limit: int | None = None,
|
||||
country: str | None = None,
|
||||
):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='list',
|
||||
command_args=[
|
||||
f'{key} {value}'
|
||||
for key, value in zip(
|
||||
('--max-tr', '--min-tr', '--limit', '--country'), (max_tr, min_tr, limit, country), strict=True
|
||||
)
|
||||
if value is not None
|
||||
],
|
||||
):
|
||||
parameter = Parameter(
|
||||
# ?: 似乎是只需要 pri 至少 league 榜的返回值只有 pri
|
||||
after=P(pri=max_tr, sec=0, ter=0).to_prisecter() if max_tr is not None else None,
|
||||
before=P(pri=min_tr, sec=0, ter=0).to_prisecter() if min_tr is not None else None,
|
||||
limit=limit or 25,
|
||||
country=country,
|
||||
)
|
||||
league = await by('league', parameter)
|
||||
async with HostPage(
|
||||
await render(
|
||||
'v2/tetrio/user/list',
|
||||
List(
|
||||
show_index=True,
|
||||
users=[
|
||||
User(
|
||||
id=i.id,
|
||||
name=i.username.upper(),
|
||||
avatar=f'https://tetr.io/user-content/avatars/{i.id}.jpg',
|
||||
country=i.country,
|
||||
tetra_league=TetraLeague(
|
||||
rank=i.league.rank,
|
||||
tr=round(i.league.tr, 2),
|
||||
glicko=round(i.league.glicko, 2),
|
||||
rd=round(i.league.rd, 2),
|
||||
decaying=i.league.decaying,
|
||||
pps=(metrics := get_metrics(pps=i.league.pps, apm=i.league.apm, vs=i.league.vs)).pps,
|
||||
apm=metrics.apm,
|
||||
apl=metrics.apl,
|
||||
vs=metrics.vs,
|
||||
adpl=metrics.adpl,
|
||||
),
|
||||
xp=i.xp,
|
||||
join_at=None,
|
||||
)
|
||||
for i in league.data.entries
|
||||
],
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
await UniMessage.image(raw=await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')).finish()
|
||||
@@ -1,10 +1,53 @@
|
||||
from nonebot_plugin_orm import Model
|
||||
from sqlalchemy import String
|
||||
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from nonebot_plugin_orm import Model
|
||||
from sqlalchemy import DateTime, ForeignKey, String
|
||||
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column, relationship
|
||||
|
||||
from ...db.models import PydanticType
|
||||
from .api.schemas.leaderboards.by import BySuccessModel, Entry
|
||||
from .api.typing import ValidRank
|
||||
from .typing import Template
|
||||
|
||||
|
||||
class TETRIOUserConfig(MappedAsDataclass, Model):
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
query_template: Mapped[Template] = mapped_column(String(2))
|
||||
|
||||
|
||||
class TETRIOLeagueStats(MappedAsDataclass, Model):
|
||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||
raw: Mapped[list['TETRIOLeagueHistorical']] = relationship(back_populates='stats', lazy='noload')
|
||||
fields: Mapped[list['TETRIOLeagueStatsField']] = relationship(back_populates='stats')
|
||||
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)
|
||||
|
||||
|
||||
class TETRIOLeagueHistorical(MappedAsDataclass, Model):
|
||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||
request_id: Mapped[UUID] = mapped_column(index=True)
|
||||
data: Mapped[BySuccessModel] = mapped_column(PydanticType([], {BySuccessModel}))
|
||||
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)
|
||||
stats_id: Mapped[int] = mapped_column(ForeignKey('nonebot_plugin_tetris_stats_tetrioleaguestats.id'), init=False)
|
||||
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='raw')
|
||||
|
||||
|
||||
entry_type = PydanticType([], {Entry})
|
||||
|
||||
|
||||
class TETRIOLeagueStatsField(MappedAsDataclass, Model):
|
||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||
rank: Mapped[ValidRank] = mapped_column(String(2), index=True)
|
||||
tr_line: Mapped[float]
|
||||
player_count: Mapped[int]
|
||||
low_pps: Mapped[Entry] = mapped_column(entry_type)
|
||||
low_apm: Mapped[Entry] = mapped_column(entry_type)
|
||||
low_vs: Mapped[Entry] = mapped_column(entry_type)
|
||||
avg_pps: Mapped[float]
|
||||
avg_apm: Mapped[float]
|
||||
avg_vs: Mapped[float]
|
||||
high_pps: Mapped[Entry] = mapped_column(entry_type)
|
||||
high_apm: Mapped[Entry] = mapped_column(entry_type)
|
||||
high_vs: Mapped[Entry] = mapped_column(entry_type)
|
||||
stats_id: Mapped[int] = mapped_column(ForeignKey('nonebot_plugin_tetris_stats_tetrioleaguestats.id'), init=False)
|
||||
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='fields')
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
from asyncio import gather
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from hashlib import md5
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
from urllib.parse import urlencode
|
||||
from typing import TypeVar
|
||||
|
||||
from arclet.alconna import Arg, ArgFlag
|
||||
from nonebot import get_driver
|
||||
@@ -16,6 +15,7 @@ from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[im
|
||||
from nonebot_plugin_user import User as NBUser
|
||||
from nonebot_plugin_user import get_user
|
||||
from sqlalchemy import select
|
||||
from yarl import URL
|
||||
|
||||
from ...db import query_bind_info, trigger
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
@@ -39,16 +39,11 @@ from .. import add_block_handlers, alc
|
||||
from ..constant import CANT_VERIFY_MESSAGE
|
||||
from . import command, get_player
|
||||
from .api import Player
|
||||
from .api.schemas.summaries.league import LeagueSuccessModel, NeverPlayedData, NeverRatedData
|
||||
from .api.schemas.summaries.league import NeverPlayedData, NeverRatedData
|
||||
from .constant import GAME_TYPE
|
||||
from .models import TETRIOUserConfig
|
||||
from .typing import Template
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .api.schemas.summaries import SoloSuccessModel, ZenSuccessModel
|
||||
from .api.schemas.user import User
|
||||
from .api.schemas.user_info import UserInfoSuccess
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
driver = get_driver()
|
||||
@@ -154,26 +149,13 @@ def handling_special_value(value: N) -> N | None:
|
||||
|
||||
|
||||
async def make_query_image_v2(player: Player) -> bytes:
|
||||
user: User
|
||||
user_info: UserInfoSuccess
|
||||
league: LeagueSuccessModel
|
||||
sprint: SoloSuccessModel
|
||||
blitz: SoloSuccessModel
|
||||
zen: ZenSuccessModel
|
||||
avatar_revision: int | None
|
||||
banner_revision: int | None
|
||||
# TODO)) 有没有什么办法能让这类型推导成功)
|
||||
user, user_info, league, sprint, blitz, zen, avatar_revision, banner_revision = await gather( # type: ignore[assignment]
|
||||
player.user,
|
||||
player.get_info(),
|
||||
player.league,
|
||||
player.sprint,
|
||||
player.blitz,
|
||||
player.zen,
|
||||
player.avatar_revision,
|
||||
player.banner_revision,
|
||||
(
|
||||
(user, user_info, league, sprint, blitz, zen),
|
||||
(avatar_revision, banner_revision),
|
||||
) = await gather(
|
||||
gather(player.user, player.get_info(), player.league, player.sprint, player.blitz, player.zen),
|
||||
gather(player.avatar_revision, player.banner_revision),
|
||||
)
|
||||
|
||||
if sprint.data.record is not None:
|
||||
duration = timedelta(milliseconds=sprint.data.record.results.stats.finaltime).total_seconds()
|
||||
sprint_value = f'{duration:.3f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.3f}s' # noqa: PLR2004
|
||||
@@ -199,10 +181,14 @@ async def make_query_image_v2(player: Player) -> bytes:
|
||||
id=user.ID,
|
||||
name=user.name.upper(),
|
||||
bio=user_info.data.bio,
|
||||
banner=f'http://{netloc}/host/resource/tetrio/banners/{user.ID}?{urlencode({"revision": banner_revision})}'
|
||||
banner=str(
|
||||
URL(f'http://{netloc}/host/resource/tetrio/banners/{user.ID}') % {'revision': banner_revision}
|
||||
)
|
||||
if banner_revision is not None and banner_revision != 0
|
||||
else None,
|
||||
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
|
||||
avatar=str(
|
||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
||||
)
|
||||
if avatar_revision is not None and avatar_revision != 0
|
||||
else Avatar(
|
||||
type='identicon',
|
||||
@@ -223,7 +209,6 @@ async def make_query_image_v2(player: Player) -> bytes:
|
||||
friend_count=user_info.data.friend_count,
|
||||
supporter_tier=user_info.data.supporter_tier,
|
||||
bad_standing=user_info.data.badstanding or False,
|
||||
verified=user_info.data.verified or False,
|
||||
playtime=play_time,
|
||||
join_at=user_info.data.ts,
|
||||
),
|
||||
|
||||
154
nonebot_plugin_tetris_stats/games/tetrio/rank/__init__.py
Normal file
154
nonebot_plugin_tetris_stats/games/tetrio/rank/__init__.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Sequence
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from math import floor
|
||||
from statistics import mean
|
||||
from typing import TYPE_CHECKING
|
||||
from uuid import uuid4
|
||||
|
||||
from nonebot import get_driver
|
||||
from nonebot_plugin_alconna import Subcommand
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_orm import get_session
|
||||
from sqlalchemy import select
|
||||
|
||||
from ....utils.exception import RequestError
|
||||
from ....utils.limit import limit
|
||||
from ....utils.retry import retry
|
||||
from .. import alc
|
||||
from .. import command as base_command
|
||||
from ..api.leaderboards import by
|
||||
from ..api.schemas.base import P
|
||||
from ..api.schemas.leaderboards import Parameter
|
||||
from ..api.schemas.leaderboards.by import Entry
|
||||
from ..constant import RANK_PERCENTILE
|
||||
from ..models import TETRIOLeagueHistorical, TETRIOLeagueStats, TETRIOLeagueStatsField
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..api.schemas.leaderboards.by import BySuccessModel
|
||||
from ..api.typing import Rank
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
command = Subcommand('rank', help_text='查询 TETR.IO 段位信息')
|
||||
|
||||
|
||||
def wrapper(slot: int | str, content: str | None) -> str | None:
|
||||
if slot == 'rank' and not content:
|
||||
return '--all'
|
||||
if content is not None:
|
||||
return f'--detail {content.lower()}'
|
||||
return content
|
||||
|
||||
|
||||
alc.shortcut(
|
||||
r'(?i:io)(?i:段位|段|rank)\s*(?P<rank>[a-zA-Z+-]{0,2})',
|
||||
command='tstats TETR.IO rank {rank}',
|
||||
humanized='iorank',
|
||||
fuzzy=False,
|
||||
wrapper=wrapper,
|
||||
)
|
||||
|
||||
|
||||
def _pps(user: Entry) -> float:
|
||||
return user.league.pps
|
||||
|
||||
|
||||
def _apm(user: Entry) -> float:
|
||||
return user.league.apm
|
||||
|
||||
|
||||
def _vs(user: Entry) -> float:
|
||||
return user.league.vs
|
||||
|
||||
|
||||
def _min(users: Sequence[Entry], field: Callable[[Entry], float]) -> Entry:
|
||||
return min(users, key=field)
|
||||
|
||||
|
||||
def _max(users: Sequence[Entry], field: Callable[[Entry], float]) -> Entry:
|
||||
return max(users, key=field)
|
||||
|
||||
|
||||
def find_special_player(
|
||||
users: Sequence[Entry],
|
||||
field: Callable[[Entry], float],
|
||||
sort: Callable[[Sequence[Entry], Callable[[Entry], float]], Entry],
|
||||
) -> Entry:
|
||||
return sort(users, field)
|
||||
|
||||
|
||||
@scheduler.scheduled_job('cron', hour='0,6,12,18', minute=0)
|
||||
async def get_tetra_league_data() -> None:
|
||||
x_session_id = uuid4()
|
||||
limit_by = retry(max_attempts=10, exception_type=RequestError)(limit(timedelta(seconds=1))(by))
|
||||
prisecter = P(pri=9007199254740991, sec=9007199254740991, ter=9007199254740991) # * from ch.tetr.io
|
||||
results: list[BySuccessModel] = []
|
||||
while True:
|
||||
model = await limit_by('league', Parameter(after=prisecter.to_prisecter(), limit=100), x_session_id)
|
||||
prisecter = model.data.entries[-1].p
|
||||
results.append(model)
|
||||
if len(model.data.entries) < 100: # 分页值 # noqa: PLR2004
|
||||
break
|
||||
|
||||
players: list[Entry] = []
|
||||
for result in results:
|
||||
players.extend(result.data.entries)
|
||||
players.sort(key=lambda x: x.league.tr, reverse=True)
|
||||
|
||||
rank_player_mapping: defaultdict[Rank, list[Entry]] = defaultdict(list)
|
||||
for player in players:
|
||||
rank_player_mapping[player.league.rank].append(player)
|
||||
|
||||
stats = TETRIOLeagueStats(raw=[], fields=[], update_time=datetime.now(UTC))
|
||||
fields: list[TETRIOLeagueStatsField] = []
|
||||
for rank, percentile in RANK_PERCENTILE.items():
|
||||
offset = floor((percentile / 100) * len(players)) - 1
|
||||
tr_line = players[offset].league.tr
|
||||
rank_players = rank_player_mapping[rank]
|
||||
fields.append(
|
||||
TETRIOLeagueStatsField(
|
||||
rank=rank,
|
||||
tr_line=tr_line,
|
||||
player_count=len(rank_players),
|
||||
low_pps=find_special_player(rank_players, _pps, _min),
|
||||
low_apm=find_special_player(rank_players, _apm, _min),
|
||||
low_vs=find_special_player(rank_players, _vs, _min),
|
||||
avg_pps=mean(_pps(i) for i in rank_players),
|
||||
avg_apm=mean(_apm(i) for i in rank_players),
|
||||
avg_vs=mean(_vs(i) for i in rank_players),
|
||||
high_pps=find_special_player(rank_players, _pps, _max),
|
||||
high_apm=find_special_player(rank_players, _apm, _max),
|
||||
high_vs=find_special_player(rank_players, _vs, _max),
|
||||
stats=stats,
|
||||
)
|
||||
)
|
||||
historicals = [
|
||||
TETRIOLeagueHistorical(request_id=x_session_id, data=model, update_time=model.cache.cached_at, stats=stats)
|
||||
for model in results
|
||||
]
|
||||
stats.raw = historicals
|
||||
stats.fields = fields
|
||||
async with get_session() as session:
|
||||
session.add(stats)
|
||||
await session.commit()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _() -> None:
|
||||
async with get_session() as session:
|
||||
latest_time = await session.scalar(
|
||||
select(TETRIOLeagueStats.update_time).order_by(TETRIOLeagueStats.id.desc()).limit(1)
|
||||
)
|
||||
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
|
||||
await get_tetra_league_data()
|
||||
|
||||
|
||||
from . import all, detail # noqa: E402
|
||||
|
||||
base_command.add(command)
|
||||
|
||||
__all__ = ['all', 'detail']
|
||||
115
nonebot_plugin_tetris_stats/games/tetrio/rank/all.py
Normal file
115
nonebot_plugin_tetris_stats/games/tetrio/rank/all.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from arclet.alconna import Arg
|
||||
from nonebot_plugin_alconna import Option, Subcommand, UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from ....db import trigger
|
||||
from ....utils.host import HostPage, get_self_netloc
|
||||
from ....utils.metrics import get_metrics
|
||||
from ....utils.render import render
|
||||
from ....utils.render.schemas.tetrio.rank.v1 import Data as DataV1
|
||||
from ....utils.render.schemas.tetrio.rank.v1 import ItemData as ItemDataV1
|
||||
from ....utils.render.schemas.tetrio.rank.v2 import AverageData as AverageDataV2
|
||||
from ....utils.render.schemas.tetrio.rank.v2 import Data as DataV2
|
||||
from ....utils.render.schemas.tetrio.rank.v2 import ItemData as ItemDataV2
|
||||
from ....utils.screenshot import screenshot
|
||||
from .. import alc
|
||||
from ..constant import GAME_TYPE
|
||||
from ..models import TETRIOLeagueStats
|
||||
from ..typing import Template
|
||||
from . import command
|
||||
|
||||
command.add(
|
||||
Subcommand(
|
||||
'--all', Option('--template', Arg('template', Template), alias=['-T'], help_text='要使用的查询模板'), dest='all'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@alc.assign('TETRIO.rank.all')
|
||||
async def _(event_session: EventSession, template: Template = 'v1'):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='rank',
|
||||
command_args=['--all'],
|
||||
):
|
||||
async with get_session() as session:
|
||||
latest_data = (
|
||||
await session.scalars(
|
||||
select(TETRIOLeagueStats)
|
||||
.order_by(TETRIOLeagueStats.id.desc())
|
||||
.limit(1)
|
||||
.options(selectinload(TETRIOLeagueStats.fields))
|
||||
)
|
||||
).one()
|
||||
compare_data = (
|
||||
await session.scalars(
|
||||
select(TETRIOLeagueStats)
|
||||
.order_by(
|
||||
func.abs(
|
||||
func.julianday(TETRIOLeagueStats.update_time)
|
||||
- func.julianday(latest_data.update_time - timedelta(hours=24))
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.options(selectinload(TETRIOLeagueStats.fields))
|
||||
)
|
||||
).one()
|
||||
match template:
|
||||
case 'v1':
|
||||
await UniMessage.image(raw=await make_image_v1(latest_data, compare_data)).finish()
|
||||
case 'v2':
|
||||
await UniMessage.image(raw=await make_image_v2(latest_data, compare_data)).finish()
|
||||
|
||||
|
||||
async def make_image_v1(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
|
||||
async with HostPage(
|
||||
await render(
|
||||
'v1/tetrio/rank',
|
||||
DataV1(
|
||||
items={
|
||||
i[0].rank: ItemDataV1(
|
||||
trending=round(i[0].tr_line - i[1].tr_line, 2),
|
||||
require_tr=round(i[0].tr_line, 2),
|
||||
players=i[0].player_count,
|
||||
)
|
||||
for i in zip(latest_data.fields, compare_data.fields, strict=True)
|
||||
},
|
||||
updated_at=latest_data.update_time,
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
||||
|
||||
|
||||
async def make_image_v2(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
|
||||
async with HostPage(
|
||||
await render(
|
||||
'v2/tetrio/rank',
|
||||
DataV2(
|
||||
items={
|
||||
i[0].rank: ItemDataV2(
|
||||
require_tr=round(i[0].tr_line, 2),
|
||||
trending=round(i[0].tr_line - i[1].tr_line, 2),
|
||||
average_data=AverageDataV2(
|
||||
pps=(metrics := get_metrics(pps=i[0].avg_pps, apm=i[0].avg_apm, vs=i[0].avg_vs)).pps,
|
||||
apm=metrics.apm,
|
||||
apl=metrics.apl,
|
||||
vs=metrics.vs,
|
||||
adpl=metrics.adpl,
|
||||
),
|
||||
players=i[0].player_count,
|
||||
)
|
||||
for i in zip(latest_data.fields, compare_data.fields, strict=True)
|
||||
},
|
||||
updated_at=latest_data.update_time,
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
||||
128
nonebot_plugin_tetris_stats/games/tetrio/rank/detail.py
Normal file
128
nonebot_plugin_tetris_stats/games/tetrio/rank/detail.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from datetime import timedelta, timezone
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from arclet.alconna import Arg
|
||||
from nonebot import get_driver
|
||||
from nonebot_plugin_alconna import Option, UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from ....db import trigger
|
||||
from ....utils.host import HostPage, get_self_netloc
|
||||
from ....utils.metrics import get_metrics
|
||||
from ....utils.render import render
|
||||
from ....utils.render.schemas.tetrio.rank.detail import Data, SpecialData
|
||||
from ....utils.screenshot import screenshot
|
||||
from .. import alc
|
||||
from ..api.typing import ValidRank
|
||||
from ..constant import GAME_TYPE
|
||||
from ..models import TETRIOLeagueStats
|
||||
from . import command
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
command.add(Option('--detail', Arg('rank', ValidRank), alias=['-D']))
|
||||
|
||||
|
||||
@alc.assign('TETRIO.rank')
|
||||
async def _(rank: ValidRank, event_session: EventSession):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='rank',
|
||||
command_args=[f'--detail {rank}'],
|
||||
):
|
||||
async with get_session() as session:
|
||||
latest_data = (
|
||||
await session.scalars(
|
||||
select(TETRIOLeagueStats)
|
||||
.order_by(TETRIOLeagueStats.id.desc())
|
||||
.limit(1)
|
||||
.options(selectinload(TETRIOLeagueStats.fields))
|
||||
)
|
||||
).one()
|
||||
compare_data = (
|
||||
await session.scalars(
|
||||
select(TETRIOLeagueStats)
|
||||
.order_by(
|
||||
func.abs(
|
||||
func.julianday(TETRIOLeagueStats.update_time)
|
||||
- func.julianday(latest_data.update_time - timedelta(hours=24))
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.options(selectinload(TETRIOLeagueStats.fields))
|
||||
)
|
||||
).one()
|
||||
await UniMessage.image(
|
||||
raw=await make_image(
|
||||
rank,
|
||||
latest_data,
|
||||
compare_data,
|
||||
)
|
||||
).finish()
|
||||
|
||||
|
||||
async def make_image(rank: ValidRank, latest: TETRIOLeagueStats, compare: TETRIOLeagueStats) -> bytes:
|
||||
latest_data = next(filter(lambda x: x.rank == rank, latest.fields))
|
||||
compare_data = next(filter(lambda x: x.rank == rank, compare.fields))
|
||||
avg = get_metrics(pps=latest_data.avg_pps, apm=latest_data.avg_apm, vs=latest_data.avg_vs)
|
||||
low_pps = get_metrics(
|
||||
pps=latest_data.low_pps.league.pps, apm=latest_data.low_pps.league.apm, vs=latest_data.low_pps.league.vs
|
||||
)
|
||||
low_apm = get_metrics(
|
||||
pps=latest_data.low_apm.league.pps, apm=latest_data.low_apm.league.apm, vs=latest_data.low_apm.league.vs
|
||||
)
|
||||
low_vs = get_metrics(
|
||||
pps=latest_data.low_vs.league.pps, apm=latest_data.low_vs.league.apm, vs=latest_data.low_vs.league.vs
|
||||
)
|
||||
max_pps = get_metrics(
|
||||
pps=latest_data.high_pps.league.pps, apm=latest_data.high_pps.league.apm, vs=latest_data.high_pps.league.vs
|
||||
)
|
||||
max_apm = get_metrics(
|
||||
pps=latest_data.high_apm.league.pps, apm=latest_data.high_apm.league.apm, vs=latest_data.high_apm.league.vs
|
||||
)
|
||||
max_vs = get_metrics(
|
||||
pps=latest_data.high_vs.league.pps, apm=latest_data.high_vs.league.apm, vs=latest_data.high_vs.league.vs
|
||||
)
|
||||
async with HostPage(
|
||||
await render(
|
||||
'v2/tetrio/rank/detail',
|
||||
Data(
|
||||
name=latest_data.rank,
|
||||
trending=round(latest_data.tr_line - compare_data.tr_line, 2),
|
||||
require_tr=round(latest_data.tr_line, 2),
|
||||
players=latest_data.player_count,
|
||||
minimum_data=SpecialData(
|
||||
apm=low_apm.apm,
|
||||
pps=low_pps.pps,
|
||||
lpm=low_pps.lpm,
|
||||
vs=low_vs.vs,
|
||||
adpm=low_vs.adpm,
|
||||
apm_holder=latest_data.low_apm.username.upper(),
|
||||
pps_holder=latest_data.low_pps.username.upper(),
|
||||
vs_holder=latest_data.low_vs.username.upper(),
|
||||
),
|
||||
average_data=SpecialData(
|
||||
apm=avg.apm, pps=avg.pps, lpm=avg.lpm, vs=avg.vs, adpm=avg.adpm, apl=avg.apl, adpl=avg.adpl
|
||||
),
|
||||
maximum_data=SpecialData(
|
||||
apm=max_apm.apm,
|
||||
pps=max_pps.pps,
|
||||
lpm=max_pps.lpm,
|
||||
vs=max_vs.vs,
|
||||
adpm=max_vs.adpm,
|
||||
apm_holder=latest_data.high_apm.username.upper(),
|
||||
pps_holder=latest_data.high_pps.username.upper(),
|
||||
vs_holder=latest_data.high_vs.username.upper(),
|
||||
),
|
||||
updated_at=latest.update_time.replace(tzinfo=UTC).astimezone(ZoneInfo('Asia/Shanghai')),
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
||||
@@ -1,7 +1,6 @@
|
||||
from asyncio import gather
|
||||
from datetime import timedelta
|
||||
from hashlib import md5
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.matcher import Matcher
|
||||
@@ -11,6 +10,7 @@ from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from nonebot_plugin_user import get_user
|
||||
from yarl import URL
|
||||
|
||||
from ....db import query_bind_info, trigger
|
||||
from ....utils.exception import RecordNotFoundError
|
||||
@@ -94,7 +94,9 @@ async def make_blitz_image(player: Player) -> bytes:
|
||||
user=User(
|
||||
id=user.ID,
|
||||
name=user.name.upper(),
|
||||
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
|
||||
avatar=str(
|
||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
||||
)
|
||||
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
|
||||
else Avatar(
|
||||
type='identicon',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from asyncio import gather
|
||||
from datetime import timedelta
|
||||
from hashlib import md5
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.matcher import Matcher
|
||||
@@ -11,6 +10,7 @@ from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from nonebot_plugin_user import get_user
|
||||
from yarl import URL
|
||||
|
||||
from ....db import query_bind_info, trigger
|
||||
from ....utils.exception import RecordNotFoundError
|
||||
@@ -95,7 +95,9 @@ async def make_sprint_image(player: Player) -> bytes:
|
||||
user=User(
|
||||
id=user.ID,
|
||||
name=user.name.upper(),
|
||||
avatar=f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}?{urlencode({"revision": avatar_revision})}'
|
||||
avatar=str(
|
||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
||||
)
|
||||
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
|
||||
else Avatar(
|
||||
type='identicon',
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
from contextlib import suppress
|
||||
from datetime import datetime, timezone
|
||||
from io import StringIO
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from lxml import etree
|
||||
from pandas import read_html
|
||||
|
||||
from ....config.config import config
|
||||
from ....db import anti_duplicate_add
|
||||
from ....utils.request import Request, splice_url
|
||||
from ....utils.request import Request
|
||||
from ..constant import BASE_URL, USER_NAME
|
||||
from .models import TOPHistoricalData
|
||||
from .schemas.user import User
|
||||
@@ -15,6 +15,8 @@ from .schemas.user_profile import Data, UserProfile
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
request = Request(config.tetris.proxy.top or config.tetris.proxy.main)
|
||||
|
||||
|
||||
class Player:
|
||||
def __init__(self, *, user_name: str, trust: bool = False) -> None:
|
||||
@@ -35,8 +37,7 @@ class Player:
|
||||
async def get_profile(self) -> UserProfile:
|
||||
"""获取用户信息"""
|
||||
if self._user_profile is None:
|
||||
url = splice_url([BASE_URL, 'profile.php', f'?{urlencode({"user":self.user_name})}'])
|
||||
raw_user_profile = await Request.request(url, is_json=False)
|
||||
raw_user_profile = await request.request(BASE_URL / 'profile.php' % {'user': self.user_name}, is_json=False)
|
||||
self._user_profile = self._parse_profile(raw_user_profile)
|
||||
await anti_duplicate_add(
|
||||
TOPHistoricalData(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from re import compile
|
||||
from typing import Literal
|
||||
|
||||
from yarl import URL
|
||||
|
||||
GAME_TYPE: Literal['TOP'] = 'TOP'
|
||||
|
||||
BASE_URL = 'http://tetrisonline.pl/top/'
|
||||
BASE_URL = URL('http://tetrisonline.pl/top/')
|
||||
|
||||
USER_NAME = compile(r'^[a-zA-Z0-9_]{1,16}$')
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import overload
|
||||
from urllib.parse import urlencode
|
||||
from typing import cast, overload
|
||||
|
||||
from httpx import TimeoutException
|
||||
from nonebot.compat import type_validate_json
|
||||
from yarl import URL
|
||||
|
||||
from ....config.config import config
|
||||
from ....db import anti_duplicate_add
|
||||
from ....utils.exception import RequestError
|
||||
from ....utils.request import Request, splice_url
|
||||
from ....utils.request import Request
|
||||
from ..constant import BASE_URL, USER_NAME
|
||||
from .models import TOSHistoricalData
|
||||
from .schemas.user import User
|
||||
@@ -16,6 +17,8 @@ from .schemas.user_profile import UserProfile
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
request = Request(config.tetris.proxy.tos or config.tetris.proxy.main)
|
||||
|
||||
|
||||
class Player:
|
||||
@overload
|
||||
@@ -56,29 +59,14 @@ class Player:
|
||||
async def get_info(self) -> UserInfoSuccess:
|
||||
"""获取用户信息"""
|
||||
if self._user_info is None:
|
||||
if self.teaid is not None:
|
||||
url = [
|
||||
splice_url(
|
||||
[
|
||||
i,
|
||||
'getTeaIdInfo',
|
||||
f'?{urlencode({"teaId":self.teaid})}',
|
||||
]
|
||||
)
|
||||
for i in BASE_URL
|
||||
]
|
||||
else:
|
||||
url = [
|
||||
splice_url(
|
||||
[
|
||||
i,
|
||||
'getUsernameInfo',
|
||||
f'?{urlencode({"username":self.user_name})}',
|
||||
]
|
||||
)
|
||||
for i in BASE_URL
|
||||
]
|
||||
raw_user_info = await Request.failover_request(url, failover_code=[502], failover_exc=(TimeoutException,))
|
||||
path = str(
|
||||
URL('getTeaIdInfo') % {'teaId': self.teaid}
|
||||
if self.teaid is not None
|
||||
else URL('getUsernameInfo') % {'username': cast(str, self.user_name)}
|
||||
)
|
||||
raw_user_info = await request.failover_request(
|
||||
[i / path for i in BASE_URL], failover_code=[502], failover_exc=(TimeoutException,)
|
||||
)
|
||||
user_info: UserInfo = type_validate_json(UserInfo, raw_user_info) # type: ignore[arg-type]
|
||||
if not isinstance(user_info, UserInfoSuccess):
|
||||
msg = f'用户信息请求错误:\n{user_info.error}'
|
||||
@@ -98,17 +86,11 @@ class Player:
|
||||
"""获取用户数据"""
|
||||
if other_parameter is None:
|
||||
other_parameter = {}
|
||||
params = urlencode(dict(sorted(other_parameter.items())))
|
||||
params = (URL('') % dict(sorted(other_parameter.items()))).human_repr()
|
||||
if self._user_profile.get(params) is None:
|
||||
raw_user_profile = await Request.failover_request(
|
||||
raw_user_profile = await request.failover_request(
|
||||
[
|
||||
splice_url(
|
||||
[
|
||||
i,
|
||||
'getProfile',
|
||||
f'?{urlencode({"id":self.teaid or self.user_name,**other_parameter})}',
|
||||
]
|
||||
)
|
||||
i / 'getProfile' % {'id': self.teaid or cast(str, self.user_name), **other_parameter}
|
||||
for i in BASE_URL
|
||||
],
|
||||
failover_code=[502],
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
from re import compile
|
||||
from typing import Literal
|
||||
|
||||
from yarl import URL
|
||||
|
||||
GAME_TYPE: Literal['TOS'] = 'TOS'
|
||||
|
||||
BASE_URL = {
|
||||
'https://teatube.cn:8888/',
|
||||
'http://cafuuchino1.studio26f.org:19970',
|
||||
URL('https://teatube.cn:8888/'),
|
||||
URL('http://cafuuchino1.studio26f.org:19970'),
|
||||
}
|
||||
|
||||
USER_NAME = compile(
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
from functools import cache
|
||||
from hashlib import sha256
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from pathlib import Path as FilePath
|
||||
from typing import TYPE_CHECKING, ClassVar, Literal
|
||||
|
||||
from fastapi import FastAPI, Path, status
|
||||
from aiofiles import open
|
||||
from fastapi import BackgroundTasks, FastAPI, Path, status
|
||||
from fastapi.responses import FileResponse, HTMLResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from nonebot import get_app, get_driver
|
||||
from nonebot.log import logger
|
||||
from yarl import URL
|
||||
|
||||
from ..config.config import CACHE_PATH
|
||||
from ..games.tetrio.api.cache import request
|
||||
from .image import img_to_png
|
||||
from .request import Request
|
||||
from .templates import TEMPLATES_DIR
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -22,6 +26,7 @@ driver = get_driver()
|
||||
|
||||
global_config = driver.config
|
||||
|
||||
BASE_URL = URL('https://tetr.io/user-content/')
|
||||
|
||||
if not isinstance(app, FastAPI):
|
||||
msg = '本插件需要 FastAPI 驱动器才能运行'
|
||||
@@ -63,20 +68,30 @@ def _(page_hash: str) -> HTMLResponse:
|
||||
|
||||
@app.get('/host/resource/tetrio/{resource_type}/{user_id}', status_code=status.HTTP_200_OK)
|
||||
async def _(
|
||||
resource_type: Literal['avatars', 'banners'], revision: int, user_id: str = Path(regex=r'^[a-f0-9]{24}$')
|
||||
resource_type: Literal['avatars', 'banners'],
|
||||
revision: int,
|
||||
background_tasks: BackgroundTasks,
|
||||
user_id: str = Path(regex=r'^[a-f0-9]{24}$'),
|
||||
) -> Response:
|
||||
if not (path := CACHE_PATH / 'tetrio' / resource_type / f'{user_id}_{revision}.png').exists():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(
|
||||
img_to_png(
|
||||
await Request.request(
|
||||
f'https://tetr.io/user-content/{resource_type}/{user_id}.jpg?rv={revision}', is_json=False
|
||||
)
|
||||
image = img_to_png(
|
||||
await request.request(
|
||||
BASE_URL / resource_type / f'{user_id}.jpg' % {'rv': revision},
|
||||
is_json=False,
|
||||
)
|
||||
)
|
||||
background_tasks.add_task(write_cache, path=path, data=image)
|
||||
return Response(content=image, media_type='image/png')
|
||||
return FileResponse(path)
|
||||
|
||||
|
||||
async def write_cache(path: FilePath, data: bytes) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with open(path, 'wb') as file:
|
||||
await file.write(data)
|
||||
|
||||
|
||||
@cache
|
||||
def get_self_netloc() -> str:
|
||||
host: IPv4Address | IPv6Address | IPvAnyAddress = global_config.host
|
||||
if isinstance(host, IPv4Address):
|
||||
|
||||
33
nonebot_plugin_tetris_stats/utils/limit.py
Normal file
33
nonebot_plugin_tetris_stats/utils/limit.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from asyncio import Lock, sleep
|
||||
from collections.abc import Callable, Coroutine
|
||||
from datetime import timedelta
|
||||
from functools import wraps
|
||||
from time import time
|
||||
from typing import Any, ParamSpec, TypeVar
|
||||
|
||||
from nonebot.log import logger
|
||||
|
||||
P = ParamSpec('P')
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
def limit(limit: timedelta) -> Callable[[Callable[P, Coroutine[Any, Any, T]]], Callable[P, Coroutine[Any, Any, T]]]:
|
||||
limit_seconds = limit.total_seconds()
|
||||
|
||||
def decorator(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, Coroutine[Any, Any, T]]:
|
||||
last_call = 0.0
|
||||
lock = Lock()
|
||||
|
||||
@wraps(func)
|
||||
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
||||
nonlocal last_call
|
||||
async with lock:
|
||||
if (diff := (time() - last_call)) < limit_seconds:
|
||||
logger.debug(f'request limit {(limit_time:=limit_seconds-diff)}s')
|
||||
await sleep(limit_time)
|
||||
last_call = time()
|
||||
return await func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
@@ -3,7 +3,7 @@ from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ......games.tetrio.api.typing import Rank, ValidRank
|
||||
from ......games.tetrio.api.typing import Rank
|
||||
from .....typing import Number
|
||||
from ...base import Avatar
|
||||
from .base import TetraLeagueHistoryData
|
||||
@@ -21,7 +21,7 @@ class User(BaseModel):
|
||||
name: str
|
||||
country: str | None
|
||||
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'banned']
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'hidden', 'banned']
|
||||
|
||||
avatar: str | Avatar
|
||||
banner: str | None
|
||||
@@ -31,7 +31,6 @@ class User(BaseModel):
|
||||
friend_count: int | None
|
||||
supporter_tier: int
|
||||
|
||||
verified: bool
|
||||
bad_standing: bool
|
||||
|
||||
badges: list[Badge]
|
||||
|
||||
@@ -26,7 +26,6 @@ class User(BaseModel):
|
||||
name: str
|
||||
avatar: str | Avatar
|
||||
country: str | None
|
||||
verified: bool
|
||||
tetra_league: TetraLeague
|
||||
xp: Number
|
||||
join_at: datetime | None
|
||||
|
||||
@@ -1,54 +1,79 @@
|
||||
from collections.abc import Sequence
|
||||
from http import HTTPStatus
|
||||
from urllib.parse import urljoin, urlparse
|
||||
from typing import Any
|
||||
|
||||
from aiofiles import open
|
||||
from httpx import AsyncClient, HTTPError
|
||||
from nonebot import get_driver, get_plugin_config
|
||||
from msgspec import DecodeError, Struct, json
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
from playwright.async_api import Response
|
||||
from ujson import JSONDecodeError, dumps, loads
|
||||
from yarl import URL
|
||||
|
||||
from ..config.config import CACHE_PATH, Config
|
||||
from ..config.config import CACHE_PATH, config
|
||||
from .browser import BrowserManager
|
||||
from .exception import RequestError
|
||||
|
||||
driver = get_driver()
|
||||
config = get_plugin_config(Config)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
await Request.init_cache()
|
||||
await Request.read_cache()
|
||||
class CloudflareCache(Struct):
|
||||
headers: dict[str, Any] | None = None
|
||||
cookies: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@driver.on_shutdown
|
||||
async def _():
|
||||
await Request.write_cache()
|
||||
encoder = json.Encoder()
|
||||
decoder = json.Decoder()
|
||||
|
||||
|
||||
def splice_url(url_list: list[str]) -> str:
|
||||
url = ''
|
||||
if len(url_list):
|
||||
url = url_list.pop(0)
|
||||
for i in url_list:
|
||||
url = urljoin(url, i)
|
||||
return url
|
||||
class AntiCloudflare:
|
||||
cache_decoder = json.Decoder(type=CloudflareCache)
|
||||
|
||||
def __init__(self, domain_suffix: str) -> None:
|
||||
self.domain_suffix = domain_suffix
|
||||
self.cache_path = CACHE_PATH / f'{self.domain_suffix}_cloudflare_cache.json'
|
||||
self._headers: dict | None = None
|
||||
self._cookies: dict | None = None
|
||||
self.read_cache()
|
||||
|
||||
class Request:
|
||||
"""网络请求相关类"""
|
||||
def read_cache(self) -> None:
|
||||
"""读取缓存文件"""
|
||||
try:
|
||||
cache: CloudflareCache = self.cache_decoder.decode(self.cache_path.read_text(encoding='UTF-8'))
|
||||
self._headers = cache.headers
|
||||
self._cookies = cache.cookies
|
||||
except (OSError, DecodeError):
|
||||
self.cache_path.unlink()
|
||||
self.write_cache()
|
||||
|
||||
_CACHE_FILE = CACHE_PATH / 'cloudflare_cache.json'
|
||||
_headers: dict | None = None
|
||||
_cookies: dict | None = None
|
||||
def write_cache(self) -> None:
|
||||
"""写入缓存文件"""
|
||||
self.cache_path.write_bytes(json.encode(CloudflareCache(headers=self.headers, cookies=self.cookies)))
|
||||
|
||||
@classmethod
|
||||
async def _anti_cloudflare(cls, url: str) -> bytes:
|
||||
@property
|
||||
def headers(self) -> dict | None:
|
||||
return self._headers
|
||||
|
||||
@headers.setter
|
||||
def headers(self, value: dict | None) -> None:
|
||||
self._headers = value
|
||||
self.write_cache()
|
||||
|
||||
@property
|
||||
def cookies(self) -> dict | None:
|
||||
return self._cookies
|
||||
|
||||
@cookies.setter
|
||||
def cookies(self, value: dict | None) -> None:
|
||||
self._cookies = value
|
||||
self.write_cache()
|
||||
|
||||
async def __call__(self, url: str, proxy: str | None = None) -> bytes:
|
||||
"""用firefox硬穿五秒盾"""
|
||||
browser = await BrowserManager.get_browser()
|
||||
async with await browser.new_context() as context, await context.new_page() as page:
|
||||
async with (
|
||||
await browser.new_context(proxy={'server': proxy} if proxy is not None else None) as context,
|
||||
await context.new_page() as page,
|
||||
):
|
||||
response = await page.goto(url)
|
||||
attempts = 0
|
||||
while attempts < 60: # noqa: PLR2004
|
||||
@@ -61,84 +86,70 @@ class Request:
|
||||
logger.warning('疑似触发了 Cloudflare 的验证码')
|
||||
break
|
||||
try:
|
||||
loads(text)
|
||||
except JSONDecodeError:
|
||||
decoder.decode(text)
|
||||
except DecodeError:
|
||||
await page.wait_for_timeout(1000)
|
||||
else:
|
||||
if not isinstance(response, Response):
|
||||
msg = 'api请求失败'
|
||||
raise RequestError(msg)
|
||||
cls._headers = await response.request.all_headers()
|
||||
self.headers = await response.request.all_headers()
|
||||
try:
|
||||
cls._cookies = {
|
||||
self.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
|
||||
self.cookies = None
|
||||
return await response.body()
|
||||
msg = '绕过五秒盾失败'
|
||||
raise RequestError(msg)
|
||||
|
||||
@classmethod
|
||||
async def init_cache(cls) -> None:
|
||||
"""初始化缓存文件"""
|
||||
if not cls._CACHE_FILE.exists():
|
||||
async with open(file=cls._CACHE_FILE, mode='w', encoding='UTF-8') as file:
|
||||
await file.write(dumps({'headers': cls._headers, 'cookies': cls._cookies}))
|
||||
|
||||
@classmethod
|
||||
async def read_cache(cls) -> None:
|
||||
"""读取缓存文件"""
|
||||
try:
|
||||
async with open(file=cls._CACHE_FILE, mode='r', encoding='UTF-8') as file:
|
||||
json = loads(await file.read())
|
||||
except FileNotFoundError:
|
||||
await cls.init_cache()
|
||||
except (PermissionError, JSONDecodeError):
|
||||
cls._CACHE_FILE.unlink()
|
||||
await cls.init_cache()
|
||||
else:
|
||||
cls._headers = json['headers']
|
||||
cls._cookies = json['cookies']
|
||||
class Request:
|
||||
"""网络请求相关类"""
|
||||
|
||||
@classmethod
|
||||
async def write_cache(cls) -> None:
|
||||
"""写入缓存文件"""
|
||||
try:
|
||||
async with open(file=cls._CACHE_FILE, mode='r+', encoding='UTF-8') as file:
|
||||
await file.write(dumps({'headers': cls._headers, 'cookies': cls._cookies}))
|
||||
except FileNotFoundError:
|
||||
await cls.init_cache()
|
||||
except (PermissionError, JSONDecodeError):
|
||||
cls._CACHE_FILE.unlink()
|
||||
await cls.init_cache()
|
||||
def __init__(self, proxy: str | None) -> None:
|
||||
self.proxy = proxy
|
||||
self.anti_cloudflares: dict[str, AntiCloudflare] = {}
|
||||
|
||||
@classmethod
|
||||
async def request(cls, url: str, *, is_json: bool = True) -> bytes:
|
||||
async def request(
|
||||
self,
|
||||
url: URL,
|
||||
extra_headers: dict | None = None,
|
||||
*,
|
||||
is_json: bool = True,
|
||||
enable_anti_cloudflare: bool = False,
|
||||
) -> bytes:
|
||||
"""请求api"""
|
||||
if (anti_cloudflare := self.anti_cloudflares.get(url.host or '')) is not None:
|
||||
cookies = anti_cloudflare.cookies
|
||||
headers = anti_cloudflare.headers
|
||||
else:
|
||||
cookies = None
|
||||
headers = None
|
||||
headers = headers if extra_headers is None else extra_headers if headers is None else headers | extra_headers
|
||||
try:
|
||||
async with AsyncClient(cookies=cls._cookies, timeout=config.tetris.request_timeout) as session:
|
||||
response = await session.get(url, headers=cls._headers)
|
||||
async with AsyncClient(cookies=cookies, timeout=config.tetris.request_timeout, proxy=self.proxy) as session:
|
||||
response = await session.get(str(url), headers=headers)
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
msg = f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}'
|
||||
raise RequestError(msg, status_code=response.status_code)
|
||||
if is_json:
|
||||
loads(response.content)
|
||||
decoder.decode(response.content)
|
||||
return response.content
|
||||
except HTTPError as e:
|
||||
msg = f'请求错误 \n{e!r}'
|
||||
raise RequestError(msg) from e
|
||||
except JSONDecodeError:
|
||||
if urlparse(url).netloc.lower().endswith('tetr.io'):
|
||||
return await cls._anti_cloudflare(url)
|
||||
except DecodeError: # 由于捕获的是 DecodeError 所以一定是 is_json = True
|
||||
if enable_anti_cloudflare and url.host is not None:
|
||||
return await self.anti_cloudflares.setdefault(url.host, AntiCloudflare(url.host))(str(url), self.proxy)
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
async def failover_request(
|
||||
cls,
|
||||
urls: Sequence[str],
|
||||
self,
|
||||
urls: Sequence[URL],
|
||||
*,
|
||||
failover_code: Sequence[int],
|
||||
failover_exc: tuple[type[BaseException], ...],
|
||||
@@ -148,7 +159,7 @@ class Request:
|
||||
for i in urls:
|
||||
logger.debug(f'尝试请求 {i}')
|
||||
try:
|
||||
return await cls.request(i, is_json=is_json)
|
||||
return await self.request(i, is_json=is_json)
|
||||
except RequestError as e:
|
||||
if e.status_code in failover_code: # 如果状态码在 failover_code 中, 则继续尝试下一个URL
|
||||
error_list.append(e)
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
from nonebot import get_plugin_config
|
||||
from playwright.async_api import TimeoutError, ViewportSize
|
||||
|
||||
from ..config.config import Config
|
||||
from ..config.config import config
|
||||
from .browser import BrowserManager
|
||||
from .retry import retry
|
||||
from .time_it import time_it
|
||||
|
||||
config = get_plugin_config(Config)
|
||||
|
||||
|
||||
@retry(exception_type=TimeoutError, reply='截图失败, 重试中')
|
||||
@time_it
|
||||
|
||||
@@ -13,7 +13,7 @@ from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_alconna import Alconna, Args, Option, on_alconna
|
||||
from rich.progress import Progress
|
||||
|
||||
from ..config.config import CACHE_PATH, DATA_PATH
|
||||
from ..config.config import CACHE_PATH, DATA_PATH, config
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
@@ -24,7 +24,7 @@ alc = on_alconna(Alconna('更新模板', Option('--revision', Args['revision', s
|
||||
|
||||
async def download_templates(tag: str) -> Path:
|
||||
logger.info(f'开始下载模板 {tag}')
|
||||
async with AsyncClient() as client:
|
||||
async with AsyncClient(proxy=config.tetris.proxy.github or config.tetris.proxy.main) as client:
|
||||
if tag == 'latest':
|
||||
logger.info('目标为 latest, 正在获取最新版本号')
|
||||
tag = (
|
||||
@@ -105,7 +105,7 @@ async def init_templates(tag: str) -> bool:
|
||||
|
||||
|
||||
async def check_tag(tag: str) -> bool:
|
||||
async with AsyncClient() as client:
|
||||
async with AsyncClient(proxy=config.tetris.proxy.github or config.tetris.proxy.main) as client:
|
||||
return (
|
||||
await client.get(f'https://github.com/A-Minos/tetris-stats-templates/releases/tag/{tag}')
|
||||
).status_code != HTTPStatus.NOT_FOUND
|
||||
|
||||
611
poetry.lock
generated
611
poetry.lock
generated
@@ -595,6 +595,69 @@ files = [
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "contourpy"
|
||||
version = "1.2.1"
|
||||
description = "Python library for calculating contours of 2D quadrilateral grids"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"},
|
||||
{file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"},
|
||||
{file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"},
|
||||
{file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"},
|
||||
{file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"},
|
||||
{file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"},
|
||||
{file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"},
|
||||
{file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"},
|
||||
{file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
numpy = ">=1.20"
|
||||
|
||||
[package.extras]
|
||||
bokeh = ["bokeh", "selenium"]
|
||||
docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"]
|
||||
mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"]
|
||||
test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
|
||||
test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"]
|
||||
|
||||
[[package]]
|
||||
name = "cssselect"
|
||||
version = "1.2.0"
|
||||
@@ -606,6 +669,21 @@ files = [
|
||||
{file = "cssselect-1.2.0.tar.gz", hash = "sha256:666b19839cfaddb9ce9d36bfe4c969132c647b92fc9088c4e23f786b30f1b3dc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cycler"
|
||||
version = "0.12.1"
|
||||
description = "Composable style cycles"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
|
||||
{file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
|
||||
tests = ["pytest", "pytest-cov", "pytest-xdist"]
|
||||
|
||||
[[package]]
|
||||
name = "emoji"
|
||||
version = "2.12.1"
|
||||
@@ -693,6 +771,71 @@ Werkzeug = ">=3.0.0"
|
||||
async = ["asgiref (>=3.2)"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
name = "fonttools"
|
||||
version = "4.53.1"
|
||||
description = "Tools to manipulate font files"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"},
|
||||
{file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"},
|
||||
{file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"},
|
||||
{file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"},
|
||||
{file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"},
|
||||
{file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"},
|
||||
{file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"},
|
||||
{file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
|
||||
graphite = ["lz4 (>=1.7.4.2)"]
|
||||
interpolatable = ["munkres", "pycairo", "scipy"]
|
||||
lxml = ["lxml (>=4.0)"]
|
||||
pathops = ["skia-pathops (>=0.5.0)"]
|
||||
plot = ["matplotlib"]
|
||||
repacker = ["uharfbuzz (>=0.23.0)"]
|
||||
symfont = ["sympy"]
|
||||
type1 = ["xattr"]
|
||||
ufo = ["fs (>=2.2.0,<3)"]
|
||||
unicode = ["unicodedata2 (>=15.1.0)"]
|
||||
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "frozenlist"
|
||||
version = "1.4.1"
|
||||
@@ -1092,6 +1235,119 @@ MarkupSafe = ">=2.0"
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "kiwisolver"
|
||||
version = "1.4.5"
|
||||
description = "A fast implementation of the Cassowary constraint solver"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"},
|
||||
{file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"},
|
||||
{file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"},
|
||||
{file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"},
|
||||
{file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"},
|
||||
{file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"},
|
||||
{file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"},
|
||||
{file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"},
|
||||
{file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"},
|
||||
{file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"},
|
||||
{file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.2"
|
||||
@@ -1469,6 +1725,69 @@ files = [
|
||||
{file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.9.2"
|
||||
description = "Python plotting package"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "matplotlib-3.9.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9d78bbc0cbc891ad55b4f39a48c22182e9bdaea7fc0e5dbd364f49f729ca1bbb"},
|
||||
{file = "matplotlib-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c375cc72229614632c87355366bdf2570c2dac01ac66b8ad048d2dabadf2d0d4"},
|
||||
{file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d94ff717eb2bd0b58fe66380bd8b14ac35f48a98e7c6765117fe67fb7684e64"},
|
||||
{file = "matplotlib-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab68d50c06938ef28681073327795c5db99bb4666214d2d5f880ed11aeaded66"},
|
||||
{file = "matplotlib-3.9.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:65aacf95b62272d568044531e41de26285d54aec8cb859031f511f84bd8b495a"},
|
||||
{file = "matplotlib-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:3fd595f34aa8a55b7fc8bf9ebea8aa665a84c82d275190a61118d33fbc82ccae"},
|
||||
{file = "matplotlib-3.9.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8dd059447824eec055e829258ab092b56bb0579fc3164fa09c64f3acd478772"},
|
||||
{file = "matplotlib-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c797dac8bb9c7a3fd3382b16fe8f215b4cf0f22adccea36f1545a6d7be310b41"},
|
||||
{file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d719465db13267bcef19ea8954a971db03b9f48b4647e3860e4bc8e6ed86610f"},
|
||||
{file = "matplotlib-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8912ef7c2362f7193b5819d17dae8629b34a95c58603d781329712ada83f9447"},
|
||||
{file = "matplotlib-3.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7741f26a58a240f43bee74965c4882b6c93df3e7eb3de160126d8c8f53a6ae6e"},
|
||||
{file = "matplotlib-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:ae82a14dab96fbfad7965403c643cafe6515e386de723e498cf3eeb1e0b70cc7"},
|
||||
{file = "matplotlib-3.9.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ac43031375a65c3196bee99f6001e7fa5bdfb00ddf43379d3c0609bdca042df9"},
|
||||
{file = "matplotlib-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:be0fc24a5e4531ae4d8e858a1a548c1fe33b176bb13eff7f9d0d38ce5112a27d"},
|
||||
{file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf81de2926c2db243c9b2cbc3917619a0fc85796c6ba4e58f541df814bbf83c7"},
|
||||
{file = "matplotlib-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6ee45bc4245533111ced13f1f2cace1e7f89d1c793390392a80c139d6cf0e6c"},
|
||||
{file = "matplotlib-3.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:306c8dfc73239f0e72ac50e5a9cf19cc4e8e331dd0c54f5e69ca8758550f1e1e"},
|
||||
{file = "matplotlib-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:5413401594cfaff0052f9d8b1aafc6d305b4bd7c4331dccd18f561ff7e1d3bd3"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:18128cc08f0d3cfff10b76baa2f296fc28c4607368a8402de61bb3f2eb33c7d9"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4876d7d40219e8ae8bb70f9263bcbe5714415acfdf781086601211335e24f8aa"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d9f07a80deab4bb0b82858a9e9ad53d1382fd122be8cde11080f4e7dfedb38b"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7c0410f181a531ec4e93bbc27692f2c71a15c2da16766f5ba9761e7ae518413"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:909645cce2dc28b735674ce0931a4ac94e12f5b13f6bb0b5a5e65e7cea2c192b"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:f32c7410c7f246838a77d6d1eff0c0f87f3cb0e7c4247aebea71a6d5a68cab49"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:37e51dd1c2db16ede9cfd7b5cabdfc818b2c6397c83f8b10e0e797501c963a03"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b82c5045cebcecd8496a4d694d43f9cc84aeeb49fe2133e036b207abe73f4d30"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f053c40f94bc51bc03832a41b4f153d83f2062d88c72b5e79997072594e97e51"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbe196377a8248972f5cede786d4c5508ed5f5ca4a1e09b44bda889958b33f8c"},
|
||||
{file = "matplotlib-3.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5816b1e1fe8c192cbc013f8f3e3368ac56fbecf02fb41b8f8559303f24c5015e"},
|
||||
{file = "matplotlib-3.9.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:cef2a73d06601437be399908cf13aee74e86932a5ccc6ccdf173408ebc5f6bb2"},
|
||||
{file = "matplotlib-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0830e188029c14e891fadd99702fd90d317df294c3298aad682739c5533721a"},
|
||||
{file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ba9c1299c920964e8d3857ba27173b4dbb51ca4bab47ffc2c2ba0eb5e2cbc5"},
|
||||
{file = "matplotlib-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd93b91ab47a3616b4d3c42b52f8363b88ca021e340804c6ab2536344fad9ca"},
|
||||
{file = "matplotlib-3.9.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6d1ce5ed2aefcdce11904fc5bbea7d9c21fff3d5f543841edf3dea84451a09ea"},
|
||||
{file = "matplotlib-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:b2696efdc08648536efd4e1601b5fd491fd47f4db97a5fbfd175549a7365c1b2"},
|
||||
{file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d52a3b618cb1cbb769ce2ee1dcdb333c3ab6e823944e9a2d36e37253815f9556"},
|
||||
{file = "matplotlib-3.9.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:039082812cacd6c6bec8e17a9c1e6baca230d4116d522e81e1f63a74d01d2e21"},
|
||||
{file = "matplotlib-3.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6758baae2ed64f2331d4fd19be38b7b4eae3ecec210049a26b6a4f3ae1c85dcc"},
|
||||
{file = "matplotlib-3.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:050598c2b29e0b9832cde72bcf97627bf00262adbc4a54e2b856426bb2ef0697"},
|
||||
{file = "matplotlib-3.9.2.tar.gz", hash = "sha256:96ab43906269ca64a6366934106fa01534454a69e471b7bf3d79083981aaab92"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
contourpy = ">=1.0.1"
|
||||
cycler = ">=0.10"
|
||||
fonttools = ">=4.22.0"
|
||||
kiwisolver = ">=1.3.1"
|
||||
numpy = ">=1.23"
|
||||
packaging = ">=20.0"
|
||||
pillow = ">=8"
|
||||
pyparsing = ">=2.3.1"
|
||||
python-dateutil = ">=2.7"
|
||||
|
||||
[package.extras]
|
||||
dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
@@ -1559,6 +1878,58 @@ files = [
|
||||
{file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "msgspec"
|
||||
version = "0.18.6"
|
||||
description = "A fast serialization and validation library, with builtin support for JSON, MessagePack, YAML, and TOML."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "msgspec-0.18.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:77f30b0234eceeff0f651119b9821ce80949b4d667ad38f3bfed0d0ebf9d6d8f"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a76b60e501b3932782a9da039bd1cd552b7d8dec54ce38332b87136c64852dd"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06acbd6edf175bee0e36295d6b0302c6de3aaf61246b46f9549ca0041a9d7177"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40a4df891676d9c28a67c2cc39947c33de516335680d1316a89e8f7218660410"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a6896f4cd5b4b7d688018805520769a8446df911eb93b421c6c68155cdf9dd5a"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ac4dd63fd5309dd42a8c8c36c1563531069152be7819518be0a9d03be9788e4"},
|
||||
{file = "msgspec-0.18.6-cp310-cp310-win_amd64.whl", hash = "sha256:fda4c357145cf0b760000c4ad597e19b53adf01382b711f281720a10a0fe72b7"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e77e56ffe2701e83a96e35770c6adb655ffc074d530018d1b584a8e635b4f36f"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5351afb216b743df4b6b147691523697ff3a2fc5f3d54f771e91219f5c23aaa"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3232fabacef86fe8323cecbe99abbc5c02f7698e3f5f2e248e3480b66a3596b"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b524df6ea9998bbc99ea6ee4d0276a101bcc1aa8d14887bb823914d9f60d07"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:37f67c1d81272131895bb20d388dd8d341390acd0e192a55ab02d4d6468b434c"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d0feb7a03d971c1c0353de1a8fe30bb6579c2dc5ccf29b5f7c7ab01172010492"},
|
||||
{file = "msgspec-0.18.6-cp311-cp311-win_amd64.whl", hash = "sha256:41cf758d3f40428c235c0f27bc6f322d43063bc32da7b9643e3f805c21ed57b4"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d86f5071fe33e19500920333c11e2267a31942d18fed4d9de5bc2fbab267d28c"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce13981bfa06f5eb126a3a5a38b1976bddb49a36e4f46d8e6edecf33ccf11df1"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97dec6932ad5e3ee1e3c14718638ba333befc45e0661caa57033cd4cc489466"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad237100393f637b297926cae1868b0d500f764ccd2f0623a380e2bcfb2809ca"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db1d8626748fa5d29bbd15da58b2d73af25b10aa98abf85aab8028119188ed57"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d70cb3d00d9f4de14d0b31d38dfe60c88ae16f3182988246a9861259c6722af6"},
|
||||
{file = "msgspec-0.18.6-cp312-cp312-win_amd64.whl", hash = "sha256:1003c20bfe9c6114cc16ea5db9c5466e49fae3d7f5e2e59cb70693190ad34da0"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f7d9faed6dfff654a9ca7d9b0068456517f63dbc3aa704a527f493b9200b210a"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9da21f804c1a1471f26d32b5d9bc0480450ea77fbb8d9db431463ab64aaac2cf"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46eb2f6b22b0e61c137e65795b97dc515860bf6ec761d8fb65fdb62aa094ba61"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8355b55c80ac3e04885d72db515817d9fbb0def3bab936bba104e99ad22cf46"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9080eb12b8f59e177bd1eb5c21e24dd2ba2fa88a1dbc9a98e05ad7779b54c681"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc001cf39becf8d2dcd3f413a4797c55009b3a3cdbf78a8bf5a7ca8fdb76032c"},
|
||||
{file = "msgspec-0.18.6-cp38-cp38-win_amd64.whl", hash = "sha256:fac5834e14ac4da1fca373753e0c4ec9c8069d1fe5f534fa5208453b6065d5be"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:974d3520fcc6b824a6dedbdf2b411df31a73e6e7414301abac62e6b8d03791b4"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd62e5818731a66aaa8e9b0a1e5543dc979a46278da01e85c3c9a1a4f047ef7e"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7481355a1adcf1f08dedd9311193c674ffb8bf7b79314b4314752b89a2cf7f1c"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6aa85198f8f154cf35d6f979998f6dadd3dc46a8a8c714632f53f5d65b315c07"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e24539b25c85c8f0597274f11061c102ad6b0c56af053373ba4629772b407be"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c61ee4d3be03ea9cd089f7c8e36158786cd06e51fbb62529276452bbf2d52ece"},
|
||||
{file = "msgspec-0.18.6-cp39-cp39-win_amd64.whl", hash = "sha256:b5c390b0b0b7da879520d4ae26044d74aeee5144f83087eb7842ba59c02bc090"},
|
||||
{file = "msgspec-0.18.6.tar.gz", hash = "sha256:a59fc3b4fcdb972d09138cb516dbde600c99d07c38fd9372a6ef500d2d031b4e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["attrs", "coverage", "furo", "gcovr", "ipython", "msgpack", "mypy", "pre-commit", "pyright", "pytest", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "tomli", "tomli-w"]
|
||||
doc = ["furo", "ipython", "sphinx", "sphinx-copybutton", "sphinx-design"]
|
||||
test = ["attrs", "msgpack", "mypy", "pyright", "pytest", "pyyaml", "tomli", "tomli-w"]
|
||||
toml = ["tomli", "tomli-w"]
|
||||
yaml = ["pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "6.0.5"
|
||||
@@ -1779,13 +2150,13 @@ typing-extensions = ">=4.0.0,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-adapter-qq"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
description = "QQ adapter for nonebot2"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "nonebot_adapter_qq-1.5.0-py3-none-any.whl", hash = "sha256:27e6bcbc733d41102c085c844008aa5f2651a5cb1b9f17a3972bb5548ee0b695"},
|
||||
{file = "nonebot_adapter_qq-1.5.0.tar.gz", hash = "sha256:1f46389389f99b19d1447c6032a34d6a7c0d1876468b63152e2ec68c3a042e29"},
|
||||
{file = "nonebot_adapter_qq-1.5.1-py3-none-any.whl", hash = "sha256:d98a264087e2e92024673cbbefc963804b4a85b680599d9bebc5d3c606c8cd22"},
|
||||
{file = "nonebot_adapter_qq-1.5.1.tar.gz", hash = "sha256:02cd9c6204fa8a711569fd59fd518826fb484a3ad5bcb45868a754091005a6ea"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1927,13 +2298,13 @@ nonebot2 = {version = ">=2.2.0,<3.0.0", extras = ["fastapi"]}
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-user"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
description = "适用于 Nonebot2 的用户插件"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "nonebot_plugin_user-0.4.1-py3-none-any.whl", hash = "sha256:1d4daba6659774b65ebbc9b679d50c5505a8dcdd76a71a122b09452e0aaa2d11"},
|
||||
{file = "nonebot_plugin_user-0.4.1.tar.gz", hash = "sha256:80ea561a46c22d0e087edefb9a2bce8cfc4395426fbb54506d85382538eb68de"},
|
||||
{file = "nonebot_plugin_user-0.4.2-py3-none-any.whl", hash = "sha256:c7312109614cb40e1887e534deb1e81dd213b2304dd34cba738b35580971cc9f"},
|
||||
{file = "nonebot_plugin_user-0.4.2.tar.gz", hash = "sha256:422062dfa97d8fbd8dbd11ab46b240e86d90fcca3d991886596c3332e4211694"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1977,13 +2348,13 @@ nonebot2 = ">=2.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot2"
|
||||
version = "2.3.2"
|
||||
version = "2.3.3"
|
||||
description = "An asynchronous python bot framework."
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "nonebot2-2.3.2-py3-none-any.whl", hash = "sha256:c51aa3c1f23d8062ce6d13c8423dcb9a8bf0c44f21687916095f825da79a9a55"},
|
||||
{file = "nonebot2-2.3.2.tar.gz", hash = "sha256:af52e27e03e7fe147f2b642151eec81f264d058efe53b974eb08b5d90177cd14"},
|
||||
{file = "nonebot2-2.3.3-py3-none-any.whl", hash = "sha256:5bc8d073091347f29c4a1a2f927c24a8941e5d286c77139376259318b9bbfc68"},
|
||||
{file = "nonebot2-2.3.3.tar.gz", hash = "sha256:4fa7707de5d708c27cc49493bc78a07fee2ba01f5516835a2ea5fbebb49b9dfa"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2074,6 +2445,17 @@ files = [
|
||||
{file = "objprint-0.2.3.tar.gz", hash = "sha256:73d0ad5a7c3151fce634c8892e5c2a050ccae3b1a353bf1316f08b7854da863b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.1"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.2.2"
|
||||
@@ -2561,6 +2943,84 @@ files = [
|
||||
{file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.1.2"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
files = [
|
||||
{file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"},
|
||||
{file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6"
|
||||
version = "6.7.1"
|
||||
description = "Python bindings for the Qt cross platform application toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyQt6-6.7.1-1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc"},
|
||||
{file = "PyQt6-6.7.1-1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-win_amd64.whl", hash = "sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c"},
|
||||
{file = "PyQt6-6.7.1.tar.gz", hash = "sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PyQt6-Qt6 = ">=6.7.0,<6.8.0"
|
||||
PyQt6-sip = ">=13.8,<14"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-qt6"
|
||||
version = "6.7.2"
|
||||
description = "The subset of a Qt installation needed by PyQt6."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:05f2c7d195d316d9e678a92ecac0252a24ed175bd2444cc6077441807d756580"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-win_amd64.whl", hash = "sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-sip"
|
||||
version = "13.8.0"
|
||||
description = "The sip module support for PyQt6"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cedd554c643e54c4c2e12b5874781a87441a1b405acf3650a4a2e1df42aae231"},
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f57275b5af774529f9838adcfb58869ba3ebdaf805daea113bb0697a96a3f3cb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:835ed22eab977f75fd77e60d4ff308a1fa794b1d0c04849311f36d2a080cdf3b"},
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8b22a6850917c68ce83fc152a8b606ecb2efaaeed35be53110468885d6cdd9d"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b203b6fbae4a8f2d27f35b7df46200057033d9ecd9134bcf30e3eab66d43572c"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:beaddc1ec96b342f4e239702f91802706a80cb403166c2da318cec4ad8b790cb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5c086b7c9c7996ea9b7522646cc24eebbf3591ec9dd38f65c0a3fdb0dbeaac7"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd168667addf01f8a4b0fa7755323e43e4cd12ca4bade558c61f713a5d48ba1a"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:33d9b399fc9c9dc99496266842b0fb2735d924604774e97cf9b555667cc0fc59"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:056af69d1d8d28d5968066ec5da908afd82fc0be07b67cf2b84b9f02228416ce"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:08dd81037a2864982ece2bf9891f3bf4558e247034e112993ea1a3fe239458cb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbb249b82c53180f1420571ece5dc24fea1188ba435923edd055599dffe7abfb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7f84c472afdc7d316ff683f63129350d645ef82d9b3fd75a609b08472d1f7291"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1bf29e95f10a8a00819dac804ca7e5eba5fc1769adcd74c837c11477bf81954"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9ea9223c94906efd68148f12ae45b51a21d67e86704225ddc92bce9c54e4d93c"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:2559afa68825d08de09d71c42f3b6ad839dcc30f91e7c6d0785e07830d5541a5"},
|
||||
{file = "PyQt6_sip-13.8.0.tar.gz", hash = "sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
@@ -2706,29 +3166,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.6.0"
|
||||
version = "0.6.2"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "ruff-0.6.0-py3-none-linux_armv6l.whl", hash = "sha256:92dcce923e5df265781e5fc76f9a1edad52201a7aafe56e586b90988d5239013"},
|
||||
{file = "ruff-0.6.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:31b90ff9dc79ed476c04e957ba7e2b95c3fceb76148f2079d0d68a908d2cfae7"},
|
||||
{file = "ruff-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d834a9ec9f8287dd6c3297058b3a265ed6b59233db22593379ee38ebc4b9768"},
|
||||
{file = "ruff-0.6.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2089267692696aba342179471831a085043f218706e642564812145df8b8d0d"},
|
||||
{file = "ruff-0.6.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa62b423ee4bbd8765f2c1dbe8f6aac203e0583993a91453dc0a449d465c84da"},
|
||||
{file = "ruff-0.6.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7344e1a964b16b1137ea361d6516ce4ee61a0403fa94252a1913ecc1311adcae"},
|
||||
{file = "ruff-0.6.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:487f3a35c3f33bf82be212ce15dc6278ea854e35573a3f809442f73bec8b2760"},
|
||||
{file = "ruff-0.6.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75db409984077a793cf344d499165298a6f65449e905747ac65983b12e3e64b1"},
|
||||
{file = "ruff-0.6.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84908bd603533ecf1db456d8fc2665d1f4335d722e84bc871d3bbd2d1116c272"},
|
||||
{file = "ruff-0.6.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f1749a0aef3ec41ed91a0e2127a6ae97d2e2853af16dbd4f3c00d7a3af726c5"},
|
||||
{file = "ruff-0.6.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:016fea751e2bcfbbd2f8cb19b97b37b3fd33148e4df45b526e87096f4e17354f"},
|
||||
{file = "ruff-0.6.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6ae80f141b53b2e36e230017e64f5ea2def18fac14334ffceaae1b780d70c4f7"},
|
||||
{file = "ruff-0.6.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eaaaf33ea4b3f63fd264d6a6f4a73fa224bbfda4b438ffea59a5340f4afa2bb5"},
|
||||
{file = "ruff-0.6.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7667ddd1fc688150a7ca4137140867584c63309695a30016880caf20831503a0"},
|
||||
{file = "ruff-0.6.0-py3-none-win32.whl", hash = "sha256:ae48365aae60d40865a412356f8c6f2c0be1c928591168111eaf07eaefa6bea3"},
|
||||
{file = "ruff-0.6.0-py3-none-win_amd64.whl", hash = "sha256:774032b507c96f0c803c8237ce7d2ef3934df208a09c40fa809c2931f957fe5e"},
|
||||
{file = "ruff-0.6.0-py3-none-win_arm64.whl", hash = "sha256:a5366e8c3ae6b2dc32821749b532606c42e609a99b0ae1472cf601da931a048c"},
|
||||
{file = "ruff-0.6.0.tar.gz", hash = "sha256:272a81830f68f9bd19d49eaf7fa01a5545c5a2e86f32a9935bb0e4bb9a1db5b8"},
|
||||
{file = "ruff-0.6.2-py3-none-linux_armv6l.whl", hash = "sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c"},
|
||||
{file = "ruff-0.6.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570"},
|
||||
{file = "ruff-0.6.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1"},
|
||||
{file = "ruff-0.6.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c"},
|
||||
{file = "ruff-0.6.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56"},
|
||||
{file = "ruff-0.6.2-py3-none-win32.whl", hash = "sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da"},
|
||||
{file = "ruff-0.6.2-py3-none-win_amd64.whl", hash = "sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2"},
|
||||
{file = "ruff-0.6.2-py3-none-win_arm64.whl", hash = "sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9"},
|
||||
{file = "ruff-0.6.2.tar.gz", hash = "sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3018,13 +3478,13 @@ test = ["beautifulsoup4 (>=4.8,<5.0)", "html5lib (==1.1)", "lxml (>=4.9)", "mypy
|
||||
|
||||
[[package]]
|
||||
name = "types-pillow"
|
||||
version = "10.2.0.20240520"
|
||||
version = "10.2.0.20240822"
|
||||
description = "Typing stubs for Pillow"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-Pillow-10.2.0.20240520.tar.gz", hash = "sha256:130b979195465fa1e1676d8e81c9c7c30319e8e95b12fae945e8f0d525213107"},
|
||||
{file = "types_Pillow-10.2.0.20240520-py3-none-any.whl", hash = "sha256:33c36494b380e2a269bb742181bea5d9b00820367822dbd3760f07210a1da23d"},
|
||||
{file = "types-Pillow-10.2.0.20240822.tar.gz", hash = "sha256:559fb52a2ef991c326e4a0d20accb3bb63a7ba8d40eb493e0ecb0310ba52f0d3"},
|
||||
{file = "types_Pillow-10.2.0.20240822-py3-none-any.whl", hash = "sha256:d9dab025aba07aeb12fd50a6799d4eac52a9603488eca09d7662543983f16c5d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3088,93 +3548,6 @@ tzdata = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
[package.extras]
|
||||
devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
|
||||
|
||||
[[package]]
|
||||
name = "ujson"
|
||||
version = "5.10.0"
|
||||
description = "Ultra fast JSON encoder and decoder for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"},
|
||||
{file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"},
|
||||
{file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"},
|
||||
{file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"},
|
||||
{file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"},
|
||||
{file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"},
|
||||
{file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"},
|
||||
{file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"},
|
||||
{file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"},
|
||||
{file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"},
|
||||
{file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.30.5"
|
||||
@@ -3740,4 +4113,4 @@ cffi = ["cffi (>=1.11)"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "b153df349812da94c345ea3ea5587c26e1adf12efa9f98706658f20e01d119d4"
|
||||
content-hash = "32ebea708e97ef8e09d178e1bd1f2c3cecb76e9e01862416e7b977345e4dcd24"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = 'nonebot-plugin-tetris-stats'
|
||||
version = '1.4.16'
|
||||
version = '1.5.0'
|
||||
description = '一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件'
|
||||
authors = ['scdhh <wallfjjd@gmail.com>']
|
||||
readme = 'README.md'
|
||||
@@ -25,11 +25,12 @@ async-lru = '^2.0.4'
|
||||
httpx = '^0.27.0'
|
||||
jinja2 = '^3.1.3'
|
||||
lxml = '^5.1.0'
|
||||
msgspec = "^0.18.6"
|
||||
pandas = '>=1.4.3,<3.0.0'
|
||||
pillow = '^10.3.0'
|
||||
playwright = '^1.41.2'
|
||||
rich = '^13.7.1'
|
||||
ujson = '^5.9.0'
|
||||
yarl = "^1.9.4"
|
||||
zstandard = '>=0.22,<0.24'
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
@@ -49,8 +50,10 @@ nonebot-adapter-satori = '>=0.11.4,<0.13.0'
|
||||
nonebot-plugin-orm = { extras = ['default'], version = '>=0.3,<0.8' }
|
||||
|
||||
[tool.poetry.group.debug.dependencies]
|
||||
matplotlib = "^3.9.2"
|
||||
memory-profiler = '^0.61.0'
|
||||
objprint = '^0.2.2'
|
||||
pyqt6 = "^6.7.1"
|
||||
viztracer = '^0.16.2'
|
||||
|
||||
[build-system]
|
||||
|
||||
Reference in New Issue
Block a user