mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7133cd9384 | |||
|
|
406bc7674e | ||
|
|
259b38fda5 | ||
|
|
414345ae5c | ||
|
|
341cbd86cd | ||
|
|
bf7804738e | ||
|
|
553f373671 | ||
| e53e164a52 | |||
| 2cd7d89c3e | |||
| b8b6d5f6c8 | |||
| 7a44c0dca5 | |||
| 4155d8eb42 | |||
| 4cc942d226 |
@@ -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,3 +1,4 @@
|
||||
from nonebot import get_plugin_config
|
||||
from nonebot_plugin_localstore import get_cache_dir, get_data_dir
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -5,10 +6,22 @@ CACHE_PATH = get_cache_dir('nonebot_plugin_tetris_stats')
|
||||
DATA_PATH = get_data_dir('nonebot_plugin_tetris_stats')
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) -> 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)
|
||||
parsed_data: SuccessModel | FailedModel = type_validate_json(SuccessModel | FailedModel, response_data) # type: ignore[arg-type]
|
||||
if isinstance(parsed_data, SuccessModel):
|
||||
await cls.cache.add(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -21,7 +21,7 @@ class Stats(BaseModel):
|
||||
level_lines: int
|
||||
level_lines_needed: int
|
||||
inputs: int
|
||||
holds: int
|
||||
holds: int = 0
|
||||
time: Time | None = None # ?: 不知道是之后都没有了还是还会有
|
||||
score: int
|
||||
zenlevel: int
|
||||
|
||||
@@ -26,7 +26,7 @@ class PastInner(BaseModel):
|
||||
|
||||
|
||||
class Past(BaseModel):
|
||||
first: PastInner = Field(..., alias='1')
|
||||
first: PastInner | None = Field(default=None, alias='1')
|
||||
|
||||
|
||||
class BaseData(BaseModel):
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
from typing import Literal, NamedTuple, TypedDict, overload
|
||||
from urllib.parse import urlencode
|
||||
from typing import Literal, NamedTuple, overload
|
||||
|
||||
from msgspec import Struct, to_builtins
|
||||
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
|
||||
class Parameter(Struct, omit_defaults=True):
|
||||
after: float | None = None
|
||||
before: float | None = None
|
||||
limit: int | None = None
|
||||
country: str | None = None
|
||||
|
||||
|
||||
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 {})}']))),
|
||||
(await Cache.get(BASE_URL / 'users/lists/league' % to_builtins(parameter))),
|
||||
)
|
||||
if isinstance(league, FailedModel):
|
||||
msg = f'排行榜数据请求错误:\n{league.error}'
|
||||
@@ -45,8 +44,9 @@ 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']))),
|
||||
(data := await Cache.get(BASE_URL / 'users/lists/league/all')),
|
||||
)
|
||||
|
||||
if isinstance(full, FailedModel):
|
||||
msg = f'排行榜数据请求错误:\n{full.error}'
|
||||
raise RequestError(msg)
|
||||
|
||||
@@ -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,11 +1,13 @@
|
||||
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': 1,
|
||||
|
||||
@@ -2,7 +2,6 @@ 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 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
|
||||
@@ -199,10 +199,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',
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,68 @@ 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,
|
||||
*,
|
||||
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
|
||||
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 +157,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
|
||||
@@ -27,4 +24,5 @@ async def screenshot(url: str) -> bytes:
|
||||
};
|
||||
""")
|
||||
await page.set_viewport_size(size)
|
||||
return await page.locator('id=content').screenshot(timeout=5000, type='png')
|
||||
await page.wait_for_load_state('networkidle')
|
||||
return await page.locator('id=content').screenshot(animations='disabled', timeout=5000, type='png')
|
||||
|
||||
@@ -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
|
||||
|
||||
197
poetry.lock
generated
197
poetry.lock
generated
@@ -1559,6 +1559,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 +1831,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 +1979,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 +2029,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]
|
||||
@@ -2706,29 +2758,29 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
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.1-py3-none-linux_armv6l.whl", hash = "sha256:b4bb7de6a24169dc023f992718a9417380301b0c2da0fe85919f47264fb8add9"},
|
||||
{file = "ruff-0.6.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:45efaae53b360c81043e311cdec8a7696420b3d3e8935202c2846e7a97d4edae"},
|
||||
{file = "ruff-0.6.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bc60c7d71b732c8fa73cf995efc0c836a2fd8b9810e115be8babb24ae87e0850"},
|
||||
{file = "ruff-0.6.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c7477c3b9da822e2db0b4e0b59e61b8a23e87886e727b327e7dcaf06213c5cf"},
|
||||
{file = "ruff-0.6.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a0af7ab3f86e3dc9f157a928e08e26c4b40707d0612b01cd577cc84b8905cc9"},
|
||||
{file = "ruff-0.6.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392688dbb50fecf1bf7126731c90c11a9df1c3a4cdc3f481b53e851da5634fa5"},
|
||||
{file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5278d3e095ccc8c30430bcc9bc550f778790acc211865520f3041910a28d0024"},
|
||||
{file = "ruff-0.6.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe6d5f65d6f276ee7a0fc50a0cecaccb362d30ef98a110f99cac1c7872df2f18"},
|
||||
{file = "ruff-0.6.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2e0dd11e2ae553ee5c92a81731d88a9883af8db7408db47fc81887c1f8b672e"},
|
||||
{file = "ruff-0.6.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d812615525a34ecfc07fd93f906ef5b93656be01dfae9a819e31caa6cfe758a1"},
|
||||
{file = "ruff-0.6.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:faaa4060f4064c3b7aaaa27328080c932fa142786f8142aff095b42b6a2eb631"},
|
||||
{file = "ruff-0.6.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99d7ae0df47c62729d58765c593ea54c2546d5de213f2af2a19442d50a10cec9"},
|
||||
{file = "ruff-0.6.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9eb18dfd7b613eec000e3738b3f0e4398bf0153cb80bfa3e351b3c1c2f6d7b15"},
|
||||
{file = "ruff-0.6.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c62bc04c6723a81e25e71715aa59489f15034d69bf641df88cb38bdc32fd1dbb"},
|
||||
{file = "ruff-0.6.1-py3-none-win32.whl", hash = "sha256:9fb4c4e8b83f19c9477a8745e56d2eeef07a7ff50b68a6998f7d9e2e3887bdc4"},
|
||||
{file = "ruff-0.6.1-py3-none-win_amd64.whl", hash = "sha256:c2ebfc8f51ef4aca05dad4552bbcf6fe8d1f75b2f6af546cc47cc1c1ca916b5b"},
|
||||
{file = "ruff-0.6.1-py3-none-win_arm64.whl", hash = "sha256:3bc81074971b0ffad1bd0c52284b22411f02a11a012082a76ac6da153536e014"},
|
||||
{file = "ruff-0.6.1.tar.gz", hash = "sha256:af3ffd8c6563acb8848d33cd19a69b9bfe943667f0419ca083f8ebe4224a3436"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3088,93 +3140,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 +3705,4 @@ cffi = ["cffi (>=1.11)"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "b153df349812da94c345ea3ea5587c26e1adf12efa9f98706658f20e01d119d4"
|
||||
content-hash = "3126e56a799e58845cb01c95413f34978cd49c250962f6eba9346302471ff67a"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = 'nonebot-plugin-tetris-stats'
|
||||
version = '1.4.13'
|
||||
version = '1.4.17'
|
||||
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]
|
||||
|
||||
Reference in New Issue
Block a user