From 606dddbca21f1b39fc09fc99c430ceafbfc4d8b6 Mon Sep 17 00:00:00 2001 From: shoucandanghehe Date: Sun, 25 Aug 2024 23:16:33 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E9=80=82=E9=85=8D=E6=96=B0?= =?UTF-8?q?=E8=B5=9B=E5=AD=A3=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../games/tetrio/__init__.py | 3 +- .../games/tetrio/api/leaderboards.py | 3 +- .../games/tetrio/api/schemas/base/__init__.py | 24 ++++- .../api/schemas/leaderboards/__init__.py | 17 ++-- .../tetrio/api/schemas/leaderboards/by.py | 17 +--- .../tetrio/api/schemas/summaries/base.py | 1 - .../games/tetrio/api/schemas/user_info.py | 20 +++-- .../games/tetrio/api/typing.py | 4 +- .../games/tetrio/list.py | 90 +++++++++++++++++++ .../games/tetrio/query.py | 1 - .../games/tetrio/rank/__init__.py | 4 +- .../render/schemas/tetrio/user/info_v2.py | 3 +- .../render/schemas/tetrio/user/list_v2.py | 1 - 13 files changed, 146 insertions(+), 42 deletions(-) create mode 100644 nonebot_plugin_tetris_stats/games/tetrio/list.py diff --git a/nonebot_plugin_tetris_stats/games/tetrio/__init__.py b/nonebot_plugin_tetris_stats/games/tetrio/__init__.py index 57791a5..079b81e 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/__init__.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/__init__.py @@ -23,7 +23,7 @@ command = Subcommand( ) -from . import bind, config, query, rank, record # noqa: E402 +from . import bind, config, list, query, rank, record # noqa: E402 main_command.add(command) @@ -31,6 +31,7 @@ __all__ = [ 'alc', 'bind', 'config', + 'list', 'query', 'rank', 'record', diff --git a/nonebot_plugin_tetris_stats/games/tetrio/api/leaderboards.py b/nonebot_plugin_tetris_stats/games/tetrio/api/leaderboards.py index 268689f..fbf2eb6 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/api/leaderboards.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/api/leaderboards.py @@ -1,7 +1,6 @@ from typing import Literal, overload from uuid import UUID -from msgspec import to_builtins from nonebot.compat import type_validate_json from yarl import URL @@ -87,4 +86,4 @@ async def records( async def get(url: URL, parameter: Parameter, extra_headers: dict | None = None) -> bytes: - return await Cache.get(url % to_builtins(parameter), extra_headers) + return await Cache.get(url % parameter.to_params(), extra_headers) diff --git a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/base/__init__.py b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/base/__init__.py index 4c4b970..5ee0583 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/base/__init__.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/base/__init__.py @@ -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 diff --git a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/__init__.py b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/__init__.py index 34072be..a4a6020 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/__init__.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/__init__.py @@ -1,10 +1,15 @@ -from typing import Annotated +from typing import Any -from msgspec import Meta, Struct +from pydantic import BaseModel, Field + +from ...typing import Prisecter -class Parameter(Struct, omit_defaults=True): - after: str | None = None - before: str | None = None - limit: Annotated[int, Meta(ge=1, le=100)] = 25 +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) diff --git a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/by.py b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/by.py index 5e9b354..f6550e2 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/by.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/leaderboards/by.py @@ -4,22 +4,7 @@ from typing import Literal from pydantic import BaseModel, Field from ...typing import Rank, ValidRank -from ..base import FailedModel, P, SuccessModel - - -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') +from ..base import ArCounts, FailedModel, P, SuccessModel class League(BaseModel): diff --git a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/summaries/base.py b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/summaries/base.py index 1548cfe..0f2a9c2 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/summaries/base.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/summaries/base.py @@ -7,5 +7,4 @@ class User(BaseModel): avatar_revision: int | None banner_revision: int | None country: str | None - verified: int | None = None supporter: int diff --git a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/user_info.py b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/user_info.py index d6b7764..1172d9e 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/user_info.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/api/schemas/user_info.py @@ -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): diff --git a/nonebot_plugin_tetris_stats/games/tetrio/api/typing.py b/nonebot_plugin_tetris_stats/games/tetrio/api/typing.py index bb53b4e..ae4f4fc 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/api/typing.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/api/typing.py @@ -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) diff --git a/nonebot_plugin_tetris_stats/games/tetrio/list.py b/nonebot_plugin_tetris_stats/games/tetrio/list.py new file mode 100644 index 0000000..3617eda --- /dev/null +++ b/nonebot_plugin_tetris_stats/games/tetrio/list.py @@ -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() diff --git a/nonebot_plugin_tetris_stats/games/tetrio/query.py b/nonebot_plugin_tetris_stats/games/tetrio/query.py index ac852ef..f06c78f 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/query.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/query.py @@ -209,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, ), diff --git a/nonebot_plugin_tetris_stats/games/tetrio/rank/__init__.py b/nonebot_plugin_tetris_stats/games/tetrio/rank/__init__.py index 50e58f6..18f885e 100644 --- a/nonebot_plugin_tetris_stats/games/tetrio/rank/__init__.py +++ b/nonebot_plugin_tetris_stats/games/tetrio/rank/__init__.py @@ -88,9 +88,7 @@ async def get_tetra_league_data() -> None: prisecter = P(pri=9007199254740991, sec=9007199254740991, ter=9007199254740991) # * from ch.tetr.io results: list[BySuccessModel] = [] while True: - model = await limit_by( - 'league', Parameter(after=f'{prisecter.pri}:{prisecter.sec}:{prisecter.ter}', limit=100), x_session_id - ) + 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 diff --git a/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/info_v2.py b/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/info_v2.py index dbe38c6..f369b9c 100644 --- a/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/info_v2.py +++ b/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/info_v2.py @@ -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] diff --git a/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/list_v2.py b/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/list_v2.py index 10d7c9e..2077b4d 100644 --- a/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/list_v2.py +++ b/nonebot_plugin_tetris_stats/utils/render/schemas/tetrio/user/list_v2.py @@ -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