mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9806050e33 | |||
| 461327749f | |||
|
|
208582d313 | ||
|
|
90f655259d | ||
|
|
2ef400ca28 | ||
|
|
616a64bd6a | ||
|
|
bb8943d4c3 | ||
| 5e45db8cf5 | |||
|
|
2020deadac | ||
|
|
ce7bce6e20 | ||
|
|
d4b690f682 | ||
| c4bde71546 | |||
|
|
f56f993c69 | ||
|
|
cfcda6f597 | ||
|
|
96f5d4559d | ||
|
|
23f412b4f4 | ||
|
|
25b0d2bcdc | ||
|
|
a116f9901c | ||
|
|
82befd631e |
2
.github/workflows/Release.yml
vendored
2
.github/workflows/Release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v3
|
- uses: astral-sh/setup-uv@v5
|
||||||
name: Setup UV
|
name: Setup UV
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
|
|||||||
4
.github/workflows/Test.yml
vendored
4
.github/workflows/Test.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup uv
|
- name: Setup uv
|
||||||
uses: astral-sh/setup-uv@v3
|
uses: astral-sh/setup-uv@v5
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
cache-suffix: ${{ env.PYTHON_VERSION }}_${{ env.OS }}
|
cache-suffix: ${{ env.PYTHON_VERSION }}_${{ env.OS }}
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
run: uv run pytest --cov=nonebot_plugin_tetris_stats --cov-report xml
|
run: uv run pytest --cov=nonebot_plugin_tetris_stats --cov-report xml
|
||||||
|
|
||||||
- name: Upload coverage to Codecov
|
- name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
env_vars: OS,PYTHON_VERSION
|
env_vars: OS,PYTHON_VERSION
|
||||||
|
|||||||
2
.github/workflows/TypeCheck.yml
vendored
2
.github/workflows/TypeCheck.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: astral-sh/setup-uv@v3
|
- uses: astral-sh/setup-uv@v5
|
||||||
name: Setup UV
|
name: Setup UV
|
||||||
with:
|
with:
|
||||||
enable-cache: true
|
enable-cache: true
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ ci:
|
|||||||
autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks'
|
autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks'
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.7.3
|
rev: v0.11.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ require_plugins = {
|
|||||||
'nonebot_plugin_session',
|
'nonebot_plugin_session',
|
||||||
'nonebot_plugin_user',
|
'nonebot_plugin_user',
|
||||||
'nonebot_plugin_userinfo',
|
'nonebot_plugin_userinfo',
|
||||||
|
'nonebot_plugin_waiter',
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in require_plugins:
|
for i in require_plugins:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from nonebot_plugin_orm import AsyncSession, get_session
|
|||||||
from nonebot_plugin_user import User
|
from nonebot_plugin_user import User
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from ..utils.typing import AllCommandType, BaseCommandType, GameType, TETRIOCommandType
|
from ..utils.typedefs import AllCommandType, BaseCommandType, GameType, TETRIOCommandType
|
||||||
from .models import Bind, TriggerHistoricalData
|
from .models import Bind, TriggerHistoricalData
|
||||||
|
|
||||||
UTC = timezone.utc
|
UTC = timezone.utc
|
||||||
@@ -63,6 +63,23 @@ async def create_or_update_bind(
|
|||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
async def remove_bind(
|
||||||
|
session: AsyncSession,
|
||||||
|
user: User,
|
||||||
|
game_platform: GameType,
|
||||||
|
) -> bool:
|
||||||
|
bind = await query_bind_info(
|
||||||
|
session=session,
|
||||||
|
user=user,
|
||||||
|
game_platform=game_platform,
|
||||||
|
)
|
||||||
|
if bind is not None:
|
||||||
|
await session.delete(bind)
|
||||||
|
await session.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
T = TypeVar('T', 'TETRIOHistoricalData', 'TOPHistoricalData', 'TOSHistoricalData')
|
T = TypeVar('T', 'TETRIOHistoricalData', 'TOPHistoricalData', 'TOSHistoricalData')
|
||||||
|
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from sqlalchemy import JSON, DateTime, Dialect, String, TypeDecorator
|
|||||||
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
|
|
||||||
from ..utils.typing import AllCommandType, GameType
|
from ..utils.typedefs import AllCommandType, GameType
|
||||||
|
|
||||||
|
|
||||||
class PydanticType(TypeDecorator):
|
class PydanticType(TypeDecorator):
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ async def _(matcher: Matcher, account: MessageFormatError):
|
|||||||
|
|
||||||
@alc.handle()
|
@alc.handle()
|
||||||
async def _(matcher: Matcher, matches: AlcMatches):
|
async def _(matcher: Matcher, matches: AlcMatches):
|
||||||
if matches.head_matched and matches.options != {} or matches.main_args == {}:
|
if (matches.head_matched and matches.options != {}) or matches.main_args == {}:
|
||||||
await matcher.finish(
|
await matcher.finish(
|
||||||
(f'{matches.error_info!r}\n' if matches.error_info is not None else '')
|
(f'{matches.error_info!r}\n' if matches.error_info is not None else '')
|
||||||
+ f'输入"{matches.header_result} --help"查看帮助'
|
+ f'输入"{matches.header_result} --help"查看帮助'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Generic, TypeVar
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ..utils.typing import GameType
|
from ..utils.typedefs import GameType
|
||||||
|
|
||||||
T = TypeVar('T', bound=GameType)
|
T = TypeVar('T', bound=GameType)
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ command = Subcommand(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from . import bind, config, list, query, rank, record # noqa: E402
|
from . import bind, config, list, query, rank, record, unbind # noqa: A004, E402
|
||||||
|
|
||||||
main_command.add(command)
|
main_command.add(command)
|
||||||
|
|
||||||
@@ -35,4 +35,5 @@ __all__ = [
|
|||||||
'query',
|
'query',
|
||||||
'rank',
|
'rank',
|
||||||
'record',
|
'record',
|
||||||
|
'unbind',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from typing import Literal, overload
|
from typing import Literal, overload
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
from nonebot import __version__ as __nonebot_version__
|
||||||
from nonebot.compat import type_validate_json
|
from nonebot.compat import type_validate_json
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from ....utils.exception import RequestError
|
from ....utils.exception import RequestError
|
||||||
|
from ....version import __version__
|
||||||
from ..constant import BASE_URL
|
from ..constant import BASE_URL
|
||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .schemas.base import FailedModel
|
from .schemas.base import FailedModel
|
||||||
@@ -22,7 +24,12 @@ async def by(
|
|||||||
await get(
|
await get(
|
||||||
BASE_URL / f'users/by/{by_type}',
|
BASE_URL / f'users/by/{by_type}',
|
||||||
parameter,
|
parameter,
|
||||||
{'X-Session-ID': str(x_session_id)} if x_session_id is not None else None,
|
{
|
||||||
|
'X-Session-ID': str(x_session_id),
|
||||||
|
'User-Agent': f'nonebot-plugin-tetris-stats/{__version__} (Windows NT 10.0; Win64; x64) NoneBot2/{__nonebot_version__}',
|
||||||
|
}
|
||||||
|
if x_session_id is not None
|
||||||
|
else None,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if isinstance(model, FailedModel):
|
if isinstance(model, FailedModel):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
|||||||
|
|
||||||
from ....db.models import PydanticType
|
from ....db.models import PydanticType
|
||||||
from .schemas.base import SuccessModel
|
from .schemas.base import SuccessModel
|
||||||
from .typing import Records, Summaries
|
from .typedefs import Records, Summaries
|
||||||
|
|
||||||
|
|
||||||
class TETRIOHistoricalData(MappedAsDataclass, Model):
|
class TETRIOHistoricalData(MappedAsDataclass, Model):
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ from .schemas.summaries.base import User as SummariesUser
|
|||||||
from .schemas.summaries.league import LeagueSuccessModel
|
from .schemas.summaries.league import LeagueSuccessModel
|
||||||
from .schemas.user import User
|
from .schemas.user import User
|
||||||
from .schemas.user_info import UserInfo, UserInfoSuccess
|
from .schemas.user_info import UserInfo, UserInfoSuccess
|
||||||
from .typing import Records, Summaries
|
from .typedefs import Records, Summaries
|
||||||
|
|
||||||
|
|
||||||
class RecordModeType(str, Enum):
|
class RecordModeType(str, Enum):
|
||||||
@@ -46,7 +46,7 @@ class RecordKey(NamedTuple):
|
|||||||
record_type: RecordType
|
record_type: RecordType
|
||||||
|
|
||||||
def to_records(self) -> Records:
|
def to_records(self) -> Records:
|
||||||
return cast(Records, f'{self.mode_type.value}_{self.record_type.value}')
|
return cast('Records', f'{self.mode_type.value}_{self.record_type.value}')
|
||||||
|
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
@@ -89,7 +89,7 @@ class Player:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _request_user_parameter(self) -> str:
|
def _request_user_parameter(self) -> str:
|
||||||
return self.user_id or cast(str, self.user_name).lower()
|
return self.user_id or cast('str', self.user_name).lower()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
async def user(self) -> User:
|
async def user(self) -> User:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Literal
|
|||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ...typing import Prisecter
|
from ...typedefs import Prisecter
|
||||||
|
|
||||||
|
|
||||||
class AggregateStats(BaseModel):
|
class AggregateStats(BaseModel):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Any
|
|||||||
from nonebot.compat import PYDANTIC_V2
|
from nonebot.compat import PYDANTIC_V2
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ...typing import Prisecter
|
from ...typedefs import Prisecter
|
||||||
|
|
||||||
|
|
||||||
class Parameter(BaseModel):
|
class Parameter(BaseModel):
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ from typing import Literal
|
|||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ...typing import Rank, ValidRank
|
from ...typedefs import Rank, ValidRank
|
||||||
from ..base import ArCounts, FailedModel, P, SuccessModel
|
from ..base import ArCounts, FailedModel, P, SuccessModel
|
||||||
|
|
||||||
|
|
||||||
class League(BaseModel):
|
class BaseLeague(BaseModel):
|
||||||
gamesplayed: int
|
gamesplayed: int
|
||||||
gameswon: int
|
gameswon: int
|
||||||
tr: float
|
tr: float
|
||||||
@@ -16,13 +16,22 @@ class League(BaseModel):
|
|||||||
bestrank: ValidRank
|
bestrank: ValidRank
|
||||||
glicko: float
|
glicko: float
|
||||||
rd: float
|
rd: float
|
||||||
apm: float
|
|
||||||
pps: float
|
|
||||||
vs: float
|
|
||||||
decaying: bool
|
decaying: bool
|
||||||
|
|
||||||
|
|
||||||
class Entry(BaseModel):
|
class InvalidLeague(BaseLeague):
|
||||||
|
pps: float | None
|
||||||
|
apm: None
|
||||||
|
vs: None
|
||||||
|
|
||||||
|
|
||||||
|
class League(BaseLeague):
|
||||||
|
pps: float
|
||||||
|
apm: float
|
||||||
|
vs: float
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEntry(BaseModel):
|
||||||
id: str = Field(..., alias='_id')
|
id: str = Field(..., alias='_id')
|
||||||
username: str
|
username: str
|
||||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop']
|
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop']
|
||||||
@@ -30,7 +39,6 @@ class Entry(BaseModel):
|
|||||||
xp: float
|
xp: float
|
||||||
country: str | None = None
|
country: str | None = None
|
||||||
supporter: bool | None = None
|
supporter: bool | None = None
|
||||||
league: League
|
|
||||||
gamesplayed: int
|
gamesplayed: int
|
||||||
gameswon: int
|
gameswon: int
|
||||||
gametime: float
|
gametime: float
|
||||||
@@ -39,8 +47,16 @@ class Entry(BaseModel):
|
|||||||
p: P
|
p: P
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidEntry(BaseEntry):
|
||||||
|
league: InvalidLeague
|
||||||
|
|
||||||
|
|
||||||
|
class Entry(BaseEntry):
|
||||||
|
league: League
|
||||||
|
|
||||||
|
|
||||||
class Data(BaseModel):
|
class Data(BaseModel):
|
||||||
entries: list[Entry]
|
entries: list[Entry | InvalidEntry]
|
||||||
|
|
||||||
|
|
||||||
class BySuccessModel(SuccessModel):
|
class BySuccessModel(SuccessModel):
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ __all__ = [
|
|||||||
'SoloSuccessModel',
|
'SoloSuccessModel',
|
||||||
'SummariesModel',
|
'SummariesModel',
|
||||||
'Zen',
|
'Zen',
|
||||||
|
'ZenSuccessModel',
|
||||||
'Zenith',
|
'Zenith',
|
||||||
'ZenithEx',
|
'ZenithEx',
|
||||||
'ZenithSuccessModel',
|
'ZenithSuccessModel',
|
||||||
'ZenSuccessModel',
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Literal
|
|||||||
from nonebot.compat import PYDANTIC_V2
|
from nonebot.compat import PYDANTIC_V2
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ...typing import Rank, S1Rank, S1ValidRank
|
from ...typedefs import Rank, S1Rank, S1ValidRank
|
||||||
from ..base import SuccessModel
|
from ..base import SuccessModel
|
||||||
|
|
||||||
if PYDANTIC_V2:
|
if PYDANTIC_V2:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from ...db import trigger
|
|||||||
from . import alc, command
|
from . import alc, command
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
from .models import TETRIOUserConfig
|
from .models import TETRIOUserConfig
|
||||||
from .typing import Template
|
from .typedefs import Template
|
||||||
|
|
||||||
command.add(
|
command.add(
|
||||||
Subcommand(
|
Subcommand(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from re import compile
|
from re import compile # noqa: A004
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from .api.typing import ValidRank
|
from .api.typedefs import ValidRank
|
||||||
|
|
||||||
GAME_TYPE: Literal['IO'] = 'IO'
|
GAME_TYPE: Literal['IO'] = 'IO'
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from . import command
|
|||||||
from .api.leaderboards import by
|
from .api.leaderboards import by
|
||||||
from .api.schemas.base import P
|
from .api.schemas.base import P
|
||||||
from .api.schemas.leaderboards import Parameter
|
from .api.schemas.leaderboards import Parameter
|
||||||
|
from .api.schemas.leaderboards.by import Entry
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
|
|
||||||
command.add(
|
command.add(
|
||||||
@@ -84,6 +85,7 @@ async def _(
|
|||||||
join_at=None,
|
join_at=None,
|
||||||
)
|
)
|
||||||
for i in league.data.entries
|
for i in league.data.entries
|
||||||
|
if isinstance(i, Entry)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column, relationshi
|
|||||||
|
|
||||||
from ...db.models import PydanticType
|
from ...db.models import PydanticType
|
||||||
from .api.schemas.leaderboards.by import BySuccessModel, Entry
|
from .api.schemas.leaderboards.by import BySuccessModel, Entry
|
||||||
from .api.typing import ValidRank
|
from .api.typedefs import ValidRank
|
||||||
from .typing import Template
|
from .typedefs import Template
|
||||||
|
|
||||||
|
|
||||||
class TETRIOUserConfig(MappedAsDataclass, Model):
|
class TETRIOUserConfig(MappedAsDataclass, Model):
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ from sqlalchemy import select
|
|||||||
from ....db import query_bind_info, trigger
|
from ....db import query_bind_info, trigger
|
||||||
from ....i18n import Lang
|
from ....i18n import Lang
|
||||||
from ....utils.exception import FallbackError
|
from ....utils.exception import FallbackError
|
||||||
from ....utils.typing import Me
|
from ....utils.typedefs import Me
|
||||||
from ... import add_block_handlers, alc
|
from ... import add_block_handlers, alc
|
||||||
from .. import command, get_player
|
from .. import command, get_player
|
||||||
from ..api import Player
|
from ..api import Player
|
||||||
from ..constant import GAME_TYPE
|
from ..constant import GAME_TYPE
|
||||||
from ..models import TETRIOUserConfig
|
from ..models import TETRIOUserConfig
|
||||||
from ..typing import Template
|
from ..typedefs import Template
|
||||||
from .v1 import make_query_image_v1
|
from .v1 import make_query_image_v1
|
||||||
from .v2 import make_query_image_v2
|
from .v2 import make_query_image_v2
|
||||||
|
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ async def make_query_image_v2(player: Player) -> bytes:
|
|||||||
play_time: str | None
|
play_time: str | None
|
||||||
if (game_time := handling_special_value(user_info.data.gametime)) is not None:
|
if (game_time := handling_special_value(user_info.data.gametime)) is not None:
|
||||||
if game_time // 3600 > 0:
|
if game_time // 3600 > 0:
|
||||||
play_time = f'{game_time//3600:.0f}h {game_time % 3600 // 60:.0f}m {game_time % 60:.0f}s'
|
play_time = f'{game_time // 3600:.0f}h {game_time % 3600 // 60:.0f}m {game_time % 60:.0f}s'
|
||||||
elif game_time // 60 > 0:
|
elif game_time // 60 > 0:
|
||||||
play_time = f'{game_time//60:.0f}m {game_time % 60:.0f}s'
|
play_time = f'{game_time // 60:.0f}m {game_time % 60:.0f}s'
|
||||||
else:
|
else:
|
||||||
play_time = f'{game_time:.0f}s'
|
play_time = f'{game_time:.0f}s'
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ from ..models import TETRIOLeagueHistorical, TETRIOLeagueStats, TETRIOLeagueStat
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..api.schemas.leaderboards.by import BySuccessModel
|
from ..api.schemas.leaderboards.by import BySuccessModel
|
||||||
from ..api.typing import Rank
|
from ..api.typedefs import Rank
|
||||||
|
|
||||||
UTC = timezone.utc
|
UTC = timezone.utc
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ async def get_tetra_league_data() -> None:
|
|||||||
|
|
||||||
players: list[Entry] = []
|
players: list[Entry] = []
|
||||||
for result in results:
|
for result in results:
|
||||||
players.extend(result.data.entries)
|
players.extend([i for i in result.data.entries if isinstance(i, Entry)])
|
||||||
players.sort(key=lambda x: x.league.tr, reverse=True)
|
players.sort(key=lambda x: x.league.tr, reverse=True)
|
||||||
|
|
||||||
rank_player_mapping: defaultdict[Rank, list[Entry]] = defaultdict(list)
|
rank_player_mapping: defaultdict[Rank, list[Entry]] = defaultdict(list)
|
||||||
@@ -146,7 +146,7 @@ async def _() -> None:
|
|||||||
await get_tetra_league_data()
|
await get_tetra_league_data()
|
||||||
|
|
||||||
|
|
||||||
from . import all, detail # noqa: E402
|
from . import all, detail # noqa: A004, E402
|
||||||
|
|
||||||
base_command.add(command)
|
base_command.add(command)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from ....utils.screenshot import screenshot
|
|||||||
from .. import alc
|
from .. import alc
|
||||||
from ..constant import GAME_TYPE
|
from ..constant import GAME_TYPE
|
||||||
from ..models import TETRIOLeagueStats
|
from ..models import TETRIOLeagueStats
|
||||||
from ..typing import Template
|
from ..typedefs import Template
|
||||||
from . import command
|
from . import command
|
||||||
|
|
||||||
command.add(
|
command.add(
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from ....utils.render import render
|
|||||||
from ....utils.render.schemas.tetrio.rank.detail import Data, SpecialData
|
from ....utils.render.schemas.tetrio.rank.detail import Data, SpecialData
|
||||||
from ....utils.screenshot import screenshot
|
from ....utils.screenshot import screenshot
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from ..api.typing import ValidRank
|
from ..api.typedefs import ValidRank
|
||||||
from ..constant import GAME_TYPE
|
from ..constant import GAME_TYPE
|
||||||
from ..models import TETRIOLeagueStats
|
from ..models import TETRIOLeagueStats
|
||||||
from . import command
|
from . import command
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from arclet.alconna import Arg, ArgFlag
|
from arclet.alconna import Arg, ArgFlag
|
||||||
from nonebot_plugin_alconna import Args, At, Subcommand
|
from nonebot_plugin_alconna import Args, At, Subcommand
|
||||||
|
|
||||||
from ....utils.typing import Me
|
from ....utils.typedefs import Me
|
||||||
from .. import command as base_command
|
from .. import command as base_command
|
||||||
from .. import get_player
|
from .. import get_player
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from ....utils.render.schemas.base import Avatar
|
|||||||
from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Tspins, User
|
from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Tspins, User
|
||||||
from ....utils.render.schemas.tetrio.record.blitz import Record, Statistic
|
from ....utils.render.schemas.tetrio.record.blitz import Record, Statistic
|
||||||
from ....utils.screenshot import screenshot
|
from ....utils.screenshot import screenshot
|
||||||
from ....utils.typing import Me
|
from ....utils.typedefs import Me
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from ..api.player import Player
|
from ..api.player import Player
|
||||||
from ..constant import GAME_TYPE
|
from ..constant import GAME_TYPE
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from ....utils.render.schemas.base import Avatar
|
|||||||
from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Statistic, Tspins, User
|
from ....utils.render.schemas.tetrio.record.base import Finesse, Max, Mini, Statistic, Tspins, User
|
||||||
from ....utils.render.schemas.tetrio.record.sprint import Record
|
from ....utils.render.schemas.tetrio.record.sprint import Record
|
||||||
from ....utils.screenshot import screenshot
|
from ....utils.screenshot import screenshot
|
||||||
from ....utils.typing import Me
|
from ....utils.typedefs import Me
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from ..api.player import Player
|
from ..api.player import Player
|
||||||
from ..constant import GAME_TYPE
|
from ..constant import GAME_TYPE
|
||||||
|
|||||||
75
nonebot_plugin_tetris_stats/games/tetrio/unbind.py
Normal file
75
nonebot_plugin_tetris_stats/games/tetrio/unbind.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
from nonebot_plugin_alconna import Subcommand
|
||||||
|
from nonebot_plugin_alconna.uniseg import 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 nonebot_plugin_user import User
|
||||||
|
from nonebot_plugin_userinfo import BotUserInfo, UserInfo
|
||||||
|
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
from ...db import query_bind_info, remove_bind, trigger
|
||||||
|
from ...utils.host import HostPage, get_self_netloc
|
||||||
|
from ...utils.image import get_avatar
|
||||||
|
from ...utils.render import Bind, render
|
||||||
|
from ...utils.render.schemas.base import Avatar, People
|
||||||
|
from ...utils.screenshot import screenshot
|
||||||
|
from . import alc, command
|
||||||
|
from .api import Player
|
||||||
|
from .constant import GAME_TYPE
|
||||||
|
|
||||||
|
command.add(Subcommand('unbind', help_text='解除绑定 TETR.IO 账号'))
|
||||||
|
|
||||||
|
alc.shortcut(
|
||||||
|
'(?i:io)(?i:解除绑定|解绑|unbind)',
|
||||||
|
command='tstats TETR.IO unbind',
|
||||||
|
humanized='io解绑',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@alc.assign('TETRIO.unbind')
|
||||||
|
async def _(nb_user: User, event_session: EventSession, bot_info: UserInfo = BotUserInfo()): # noqa: B008
|
||||||
|
async with (
|
||||||
|
trigger(
|
||||||
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
command_type='unbind',
|
||||||
|
command_args=[],
|
||||||
|
),
|
||||||
|
get_session() as session,
|
||||||
|
):
|
||||||
|
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
|
||||||
|
await UniMessage('您还未绑定 TETR.IO 账号').finish()
|
||||||
|
resp = await suggest('您确定要解绑吗?', ['是', '否'])
|
||||||
|
if resp is None or resp.extract_plain_text() == '否':
|
||||||
|
return
|
||||||
|
player = Player(user_id=bind.game_account, trust=True)
|
||||||
|
user = await player.user
|
||||||
|
netloc = get_self_netloc()
|
||||||
|
async with HostPage(
|
||||||
|
await render(
|
||||||
|
'v1/binding',
|
||||||
|
Bind(
|
||||||
|
platform='TETR.IO',
|
||||||
|
status='unlink',
|
||||||
|
user=People(
|
||||||
|
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', hash=md5(user.ID.encode()).hexdigest()), # noqa: S324
|
||||||
|
name=user.name.upper(),
|
||||||
|
),
|
||||||
|
bot=People(
|
||||||
|
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||||
|
name=bot_info.user_name,
|
||||||
|
),
|
||||||
|
command='io绑定{游戏ID}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) as page_hash:
|
||||||
|
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).send()
|
||||||
|
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||||
@@ -2,7 +2,7 @@ from arclet.alconna import Arg, ArgFlag
|
|||||||
from nonebot_plugin_alconna import Args, At, Subcommand
|
from nonebot_plugin_alconna import Args, At, Subcommand
|
||||||
|
|
||||||
from ...utils.exception import MessageFormatError
|
from ...utils.exception import MessageFormatError
|
||||||
from ...utils.typing import Me
|
from ...utils.typedefs import Me
|
||||||
from .. import add_block_handlers, alc, command
|
from .. import add_block_handlers, alc, command
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import USER_NAME
|
from .constant import USER_NAME
|
||||||
@@ -29,6 +29,10 @@ command.add(
|
|||||||
),
|
),
|
||||||
help_text='绑定 TOP 账号',
|
help_text='绑定 TOP 账号',
|
||||||
),
|
),
|
||||||
|
Subcommand(
|
||||||
|
'unbind',
|
||||||
|
help_text='解除绑定 TOP 账号',
|
||||||
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
'query',
|
'query',
|
||||||
Args(
|
Args(
|
||||||
@@ -51,9 +55,22 @@ command.add(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
alc.shortcut('(?i:top)(?i:绑定|绑|bind)', {'command': 'tstats TOP bind', 'humanized': 'top绑定'})
|
alc.shortcut(
|
||||||
alc.shortcut('(?i:top)(?i:查询|查|query|stats)', {'command': 'tstats TOP query', 'humanized': 'top查'})
|
'(?i:top)(?i:绑定|绑|bind)',
|
||||||
|
command='tstats TOP bind',
|
||||||
|
humanized='top绑定',
|
||||||
|
)
|
||||||
|
alc.shortcut(
|
||||||
|
'(?i:top)(?i:解除绑定|解绑|unbind)',
|
||||||
|
command='tstats TOP unbind',
|
||||||
|
humanized='top解绑',
|
||||||
|
)
|
||||||
|
alc.shortcut(
|
||||||
|
'(?i:top)(?i:查询|查|query|stats)',
|
||||||
|
command='tstats TOP query',
|
||||||
|
humanized='top查',
|
||||||
|
)
|
||||||
|
|
||||||
add_block_handlers(alc.assign('TOP.query'))
|
add_block_handlers(alc.assign('TOP.query'))
|
||||||
|
|
||||||
from . import bind, query # noqa: E402, F401
|
from . import bind, query, unbind # noqa: E402, F401
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from re import compile
|
from re import compile # noqa: A004
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from ...utils.render.schemas.base import People
|
|||||||
from ...utils.render.schemas.top_info import Data as InfoData
|
from ...utils.render.schemas.top_info import Data as InfoData
|
||||||
from ...utils.render.schemas.top_info import Info
|
from ...utils.render.schemas.top_info import Info
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.screenshot import screenshot
|
||||||
from ...utils.typing import Me
|
from ...utils.typedefs import Me
|
||||||
from . import alc
|
from . import alc
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .api.schemas.user_profile import Data, UserProfile
|
from .api.schemas.user_profile import Data, UserProfile
|
||||||
|
|||||||
63
nonebot_plugin_tetris_stats/games/top/unbind.py
Normal file
63
nonebot_plugin_tetris_stats/games/top/unbind.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
from nonebot_plugin_alconna.uniseg import 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 nonebot_plugin_user import User
|
||||||
|
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo
|
||||||
|
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
from ...db import query_bind_info, remove_bind, trigger
|
||||||
|
from ...utils.host import HostPage, get_self_netloc
|
||||||
|
from ...utils.image import get_avatar
|
||||||
|
from ...utils.render import Bind, render
|
||||||
|
from ...utils.render.schemas.base import People
|
||||||
|
from ...utils.screenshot import screenshot
|
||||||
|
from . import alc
|
||||||
|
from .api import Player
|
||||||
|
from .constant import GAME_TYPE
|
||||||
|
|
||||||
|
|
||||||
|
@alc.assign('TOP.unbind')
|
||||||
|
async def _(
|
||||||
|
nb_user: User,
|
||||||
|
event_session: EventSession,
|
||||||
|
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
||||||
|
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
||||||
|
):
|
||||||
|
async with (
|
||||||
|
trigger(
|
||||||
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
command_type='unbind',
|
||||||
|
command_args=[],
|
||||||
|
),
|
||||||
|
get_session() as session,
|
||||||
|
):
|
||||||
|
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
|
||||||
|
await UniMessage('您还未绑定 TOP 账号').finish()
|
||||||
|
resp = await suggest('您确定要解绑吗?', ['是', '否'])
|
||||||
|
if resp is None or resp.extract_plain_text() == '否':
|
||||||
|
return
|
||||||
|
player = Player(user_name=bind.game_account, trust=True)
|
||||||
|
user = await player.user
|
||||||
|
netloc = get_self_netloc()
|
||||||
|
async with HostPage(
|
||||||
|
await render(
|
||||||
|
'v1/binding',
|
||||||
|
Bind(
|
||||||
|
platform='TOP',
|
||||||
|
status='unlink',
|
||||||
|
user=People(
|
||||||
|
avatar=await get_avatar(event_user_info, 'Data URI', None),
|
||||||
|
name=user.user_name,
|
||||||
|
),
|
||||||
|
bot=People(
|
||||||
|
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||||
|
name=bot_info.user_name,
|
||||||
|
),
|
||||||
|
command='top绑定{游戏ID}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) as page_hash:
|
||||||
|
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).send()
|
||||||
|
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||||
@@ -2,7 +2,7 @@ from arclet.alconna import Arg, ArgFlag
|
|||||||
from nonebot_plugin_alconna import Args, At, Subcommand
|
from nonebot_plugin_alconna import Args, At, Subcommand
|
||||||
|
|
||||||
from ...utils.exception import MessageFormatError
|
from ...utils.exception import MessageFormatError
|
||||||
from ...utils.typing import Me
|
from ...utils.typedefs import Me
|
||||||
from .. import add_block_handlers, alc, command
|
from .. import add_block_handlers, alc, command
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import USER_NAME
|
from .constant import USER_NAME
|
||||||
@@ -34,6 +34,10 @@ command.add(
|
|||||||
),
|
),
|
||||||
help_text='绑定 茶服 账号',
|
help_text='绑定 茶服 账号',
|
||||||
),
|
),
|
||||||
|
Subcommand(
|
||||||
|
'unbind',
|
||||||
|
help_text='解除绑定 TOS 账号',
|
||||||
|
),
|
||||||
Subcommand(
|
Subcommand(
|
||||||
'query',
|
'query',
|
||||||
Args(
|
Args(
|
||||||
@@ -56,9 +60,22 @@ command.add(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
alc.shortcut('(?i:tos|茶服)(?i:绑定|绑|bind)', {'command': 'tstats TOS bind', 'humanized': '茶服绑定'})
|
alc.shortcut(
|
||||||
alc.shortcut('(?i:tos|茶服)(?i:查询|查|query|stats)', {'command': 'tstats TOS query', 'humanized': '茶服查'})
|
'(?i:tos|茶服)(?i:绑定|绑|bind)',
|
||||||
|
command='tstats TOS bind',
|
||||||
|
humanized='茶服绑定',
|
||||||
|
)
|
||||||
|
alc.shortcut(
|
||||||
|
'(?i:tos|茶服)(?i:解除绑定|解绑|unbind)',
|
||||||
|
command='tstats TOS unbind',
|
||||||
|
humanized='茶服解绑',
|
||||||
|
)
|
||||||
|
alc.shortcut(
|
||||||
|
'(?i:tos|茶服)(?i:查询|查|query|stats)',
|
||||||
|
command='tstats TOS query',
|
||||||
|
humanized='茶服查',
|
||||||
|
)
|
||||||
|
|
||||||
add_block_handlers(alc.assign('TOS.query'))
|
add_block_handlers(alc.assign('TOS.query'))
|
||||||
|
|
||||||
from . import bind, query # noqa: E402, F401
|
from . import bind, query, unbind # noqa: E402, F401
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class Player:
|
|||||||
query = {'teaId': self.teaid}
|
query = {'teaId': self.teaid}
|
||||||
else:
|
else:
|
||||||
path = 'getUsernameInfo'
|
path = 'getUsernameInfo'
|
||||||
query = {'username': cast(str, self.user_name)}
|
query = {'username': cast('str', self.user_name)}
|
||||||
raw_user_info = await request.failover_request(
|
raw_user_info = await request.failover_request(
|
||||||
[i / path % query for i in BASE_URL], failover_code=[502], failover_exc=(TimeoutException,)
|
[i / path % query for i in BASE_URL], failover_code=[502], failover_exc=(TimeoutException,)
|
||||||
)
|
)
|
||||||
@@ -91,7 +91,7 @@ class Player:
|
|||||||
if self._user_profile.get(params) is None:
|
if self._user_profile.get(params) is None:
|
||||||
raw_user_profile = await request.failover_request(
|
raw_user_profile = await request.failover_request(
|
||||||
[
|
[
|
||||||
i / 'getProfile' % {'id': self.teaid or cast(str, self.user_name), **other_parameter}
|
i / 'getProfile' % {'id': self.teaid or cast('str', self.user_name), **other_parameter}
|
||||||
for i in BASE_URL
|
for i in BASE_URL
|
||||||
],
|
],
|
||||||
failover_code=[502],
|
failover_code=[502],
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from re import compile
|
from re import compile # noqa: A004
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from ...utils.render.avatar import get_avatar as get_random_avatar
|
|||||||
from ...utils.render.schemas.base import People, Ranking
|
from ...utils.render.schemas.base import People, Ranking
|
||||||
from ...utils.render.schemas.tos_info import Info, Multiplayer, Radar
|
from ...utils.render.schemas.tos_info import Info, Multiplayer, Radar
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.screenshot import screenshot
|
||||||
from ...utils.typing import Me, Number
|
from ...utils.typedefs import Me, Number
|
||||||
from . import alc
|
from . import alc
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .api.schemas.user_info import UserInfoSuccess
|
from .api.schemas.user_info import UserInfoSuccess
|
||||||
@@ -258,7 +258,7 @@ def make_query_text(user_info: UserInfoSuccess, game_data: GameData | None) -> U
|
|||||||
if user_data.ranked_games == '0':
|
if user_data.ranked_games == '0':
|
||||||
message += '暂无段位统计数据'
|
message += '暂无段位统计数据'
|
||||||
else:
|
else:
|
||||||
message += f', 段位分 {round(float(user_data.rating_now),2)}±{round(float(user_data.rd_now),2)} ({round(float(user_data.vol_now),2)}) '
|
message += f', 段位分 {round(float(user_data.rating_now), 2)}±{round(float(user_data.rd_now), 2)} ({round(float(user_data.vol_now), 2)}) '
|
||||||
if game_data is None:
|
if game_data is None:
|
||||||
message += ', 暂无游戏数据'
|
message += ', 暂无游戏数据'
|
||||||
else:
|
else:
|
||||||
@@ -266,7 +266,7 @@ def make_query_text(user_info: UserInfoSuccess, game_data: GameData | None) -> U
|
|||||||
message += f"\nL'PM: {game_data.metrics.lpm} ( {game_data.metrics.pps} pps )"
|
message += f"\nL'PM: {game_data.metrics.lpm} ( {game_data.metrics.pps} pps )"
|
||||||
message += f'\nAPM: {game_data.metrics.apm} ( x{game_data.metrics.apl} )'
|
message += f'\nAPM: {game_data.metrics.apm} ( x{game_data.metrics.apl} )'
|
||||||
message += f'\nADPM: {game_data.metrics.adpm} ( x{game_data.metrics.adpl} ) ( {game_data.metrics.vs}vs )'
|
message += f'\nADPM: {game_data.metrics.adpm} ( x{game_data.metrics.adpl} ) ( {game_data.metrics.vs}vs )'
|
||||||
message += f'\n40L: {float(user_data.pb_sprint)/1000:.2f}s' if user_data.pb_sprint != '2147483647' else ''
|
message += f'\n40L: {float(user_data.pb_sprint) / 1000:.2f}s' if user_data.pb_sprint != '2147483647' else ''
|
||||||
message += f'\nMarathon: {user_data.pb_marathon}' if user_data.pb_marathon != '0' else ''
|
message += f'\nMarathon: {user_data.pb_marathon}' if user_data.pb_marathon != '0' else ''
|
||||||
message += f'\nChallenge: {user_data.pb_challenge}' if user_data.pb_challenge != '0' else ''
|
message += f'\nChallenge: {user_data.pb_challenge}' if user_data.pb_challenge != '0' else ''
|
||||||
return UniMessage(message)
|
return UniMessage(message)
|
||||||
|
|||||||
66
nonebot_plugin_tetris_stats/games/tos/unbind.py
Normal file
66
nonebot_plugin_tetris_stats/games/tos/unbind.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from nonebot_plugin_alconna.uniseg import 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 nonebot_plugin_user import User
|
||||||
|
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo
|
||||||
|
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
from ...db import query_bind_info, remove_bind, trigger
|
||||||
|
from ...utils.host import HostPage, get_self_netloc
|
||||||
|
from ...utils.image import get_avatar
|
||||||
|
from ...utils.render import Bind, render
|
||||||
|
from ...utils.render.avatar import get_avatar as get_random_avatar
|
||||||
|
from ...utils.render.schemas.base import People
|
||||||
|
from ...utils.screenshot import screenshot
|
||||||
|
from . import alc
|
||||||
|
from .api import Player
|
||||||
|
from .constant import GAME_TYPE
|
||||||
|
|
||||||
|
|
||||||
|
@alc.assign('TOP.unbind')
|
||||||
|
async def _(
|
||||||
|
nb_user: User,
|
||||||
|
event_session: EventSession,
|
||||||
|
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
||||||
|
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
||||||
|
):
|
||||||
|
async with (
|
||||||
|
trigger(
|
||||||
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
command_type='unbind',
|
||||||
|
command_args=[],
|
||||||
|
),
|
||||||
|
get_session() as session,
|
||||||
|
):
|
||||||
|
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
|
||||||
|
await UniMessage('您还未绑定 TOP 账号').finish()
|
||||||
|
resp = await suggest('您确定要解绑吗?', ['是', '否'])
|
||||||
|
if resp is None or resp.extract_plain_text() == '否':
|
||||||
|
return
|
||||||
|
player = Player(user_name=bind.game_account, trust=True)
|
||||||
|
user = await player.user
|
||||||
|
netloc = get_self_netloc()
|
||||||
|
async with HostPage(
|
||||||
|
await render(
|
||||||
|
'v1/binding',
|
||||||
|
Bind(
|
||||||
|
platform='TOS',
|
||||||
|
status='unlink',
|
||||||
|
user=People(
|
||||||
|
avatar=await get_avatar(event_user_info, 'Data URI', None)
|
||||||
|
if event_user_info is not None
|
||||||
|
else get_random_avatar(user.teaid),
|
||||||
|
name=user.name,
|
||||||
|
),
|
||||||
|
bot=People(
|
||||||
|
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||||
|
name=bot_info.user_name,
|
||||||
|
),
|
||||||
|
command='茶服绑定{游戏ID}',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
) as page_hash:
|
||||||
|
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).send()
|
||||||
|
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||||
@@ -2,9 +2,9 @@ from functools import cache
|
|||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from ipaddress import IPv4Address, IPv6Address
|
from ipaddress import IPv4Address, IPv6Address
|
||||||
from pathlib import Path as FilePath
|
from pathlib import Path as FilePath
|
||||||
from typing import TYPE_CHECKING, ClassVar, Literal
|
from typing import TYPE_CHECKING, Annotated, ClassVar, Literal
|
||||||
|
|
||||||
from aiofiles import open
|
from aiofiles import open as aopen
|
||||||
from fastapi import BackgroundTasks, FastAPI, Path, status
|
from fastapi import BackgroundTasks, FastAPI, Path, status
|
||||||
from fastapi.responses import FileResponse, HTMLResponse, Response
|
from fastapi.responses import FileResponse, HTMLResponse, Response
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
@@ -69,9 +69,9 @@ def _(page_hash: str) -> HTMLResponse:
|
|||||||
@app.get('/host/resource/tetrio/{resource_type}/{user_id}', status_code=status.HTTP_200_OK)
|
@app.get('/host/resource/tetrio/{resource_type}/{user_id}', status_code=status.HTTP_200_OK)
|
||||||
async def _(
|
async def _(
|
||||||
resource_type: Literal['avatars', 'banners'],
|
resource_type: Literal['avatars', 'banners'],
|
||||||
|
user_id: Annotated[str, Path(regex=r'^[a-f0-9]{24}$')],
|
||||||
revision: int,
|
revision: int,
|
||||||
background_tasks: BackgroundTasks,
|
background_tasks: BackgroundTasks,
|
||||||
user_id: str = Path(regex=r'^[a-f0-9]{24}$'),
|
|
||||||
) -> Response:
|
) -> Response:
|
||||||
if not (path := CACHE_PATH / 'tetrio' / resource_type / f'{user_id}_{revision}.png').exists():
|
if not (path := CACHE_PATH / 'tetrio' / resource_type / f'{user_id}_{revision}.png').exists():
|
||||||
image = img_to_png(
|
image = img_to_png(
|
||||||
@@ -87,7 +87,7 @@ async def _(
|
|||||||
|
|
||||||
async def write_cache(path: FilePath, data: bytes) -> None:
|
async def write_cache(path: FilePath, data: bytes) -> None:
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
async with open(path, 'wb') as file:
|
async with aopen(path, 'wb') as file:
|
||||||
await file.write(data)
|
await file.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ def limit(limit: timedelta) -> Callable[[Callable[P, Coroutine[Any, Any, T]]], C
|
|||||||
nonlocal last_call
|
nonlocal last_call
|
||||||
async with lock:
|
async with lock:
|
||||||
if (diff := (time() - last_call)) < limit_seconds:
|
if (diff := (time() - last_call)) < limit_seconds:
|
||||||
logger.debug(f'func: {func.__name__} trigger limit, wait {(limit_time:=limit_seconds-diff):.3f}s')
|
logger.debug(
|
||||||
|
f'func: {func.__name__} trigger limit, wait {(limit_time := limit_seconds - diff):.3f}s'
|
||||||
|
)
|
||||||
await sleep(limit_time)
|
await sleep(limit_time)
|
||||||
last_call = time()
|
last_call = time()
|
||||||
return await func(*args, **kwargs)
|
return await func(*args, **kwargs)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import overload
|
from typing import overload
|
||||||
|
|
||||||
from .typing import Number
|
from .typedefs import Number
|
||||||
|
|
||||||
|
|
||||||
class TetrisMetricsBaseWithPPS:
|
class TetrisMetricsBaseWithPPS:
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class SkinManager:
|
|||||||
|
|
||||||
|
|
||||||
class Skin(ABC):
|
class Skin(ABC):
|
||||||
def __new__(cls, *args: Any, **kwargs: Any) -> Self: # noqa: ANN401, ARG003
|
def __new__(cls, *args: Any, **kwargs: Any) -> Self: # noqa: ANN401, ARG004
|
||||||
instance = super().__new__(cls)
|
instance = super().__new__(cls)
|
||||||
SkinManager.register(instance)
|
SkinManager.register(instance)
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import Literal
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ...typing import Number
|
from ...typedefs import Number
|
||||||
|
|
||||||
|
|
||||||
class Avatar(BaseModel):
|
class Avatar(BaseModel):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ......games.tetrio.api.typing import ValidRank
|
from ......games.tetrio.api.typedefs import ValidRank
|
||||||
|
|
||||||
|
|
||||||
class SpecialData(BaseModel):
|
class SpecialData(BaseModel):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ......games.tetrio.api.typing import ValidRank
|
from ......games.tetrio.api.typedefs import ValidRank
|
||||||
|
|
||||||
|
|
||||||
class ItemData(BaseModel):
|
class ItemData(BaseModel):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ......games.tetrio.api.typing import ValidRank
|
from ......games.tetrio.api.typedefs import ValidRank
|
||||||
|
|
||||||
|
|
||||||
class AverageData(BaseModel):
|
class AverageData(BaseModel):
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from .....typing import Number
|
from .....typedefs import Number
|
||||||
|
|
||||||
|
|
||||||
class TetraLeagueHistoryData(BaseModel):
|
class TetraLeagueHistoryData(BaseModel):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ......games.tetrio.api.typing import Rank
|
from ......games.tetrio.api.typedefs import Rank
|
||||||
from .....typing import Number
|
from .....typedefs import Number
|
||||||
from ...base import People, Ranking
|
from ...base import People, Ranking
|
||||||
from .base import TetraLeagueHistoryData
|
from .base import TetraLeagueHistoryData
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ from typing import Literal
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ......games.tetrio.api.typing import Rank
|
from ......games.tetrio.api.typedefs import Rank
|
||||||
from .....typing import Number
|
from .....typedefs import Number
|
||||||
from ...base import Avatar
|
from ...base import Avatar
|
||||||
from .base import TetraLeagueHistoryData
|
from .base import TetraLeagueHistoryData
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from datetime import datetime
|
|||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ......games.tetrio.api.typing import Rank
|
from ......games.tetrio.api.typedefs import Rank
|
||||||
from .....typing import Number
|
from .....typedefs import Number
|
||||||
from ...base import Avatar
|
from ...base import Avatar
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ...typing import Number
|
from ...typedefs import Number
|
||||||
from .base import People
|
from .base import People
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ...typing import Number
|
from ...typedefs import Number
|
||||||
from .base import People, Ranking
|
from .base import People, Ranking
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from collections.abc import Sequence
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from fake_useragent import UserAgent
|
||||||
from httpx import AsyncClient, HTTPError
|
from httpx import AsyncClient, HTTPError
|
||||||
from msgspec import DecodeError, Struct, json
|
from msgspec import DecodeError, Struct, json
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
@@ -113,6 +114,8 @@ class Request:
|
|||||||
def __init__(self, proxy: str | None) -> None:
|
def __init__(self, proxy: str | None) -> None:
|
||||||
self.proxy = proxy
|
self.proxy = proxy
|
||||||
self.anti_cloudflares: dict[str, AntiCloudflare] = {}
|
self.anti_cloudflares: dict[str, AntiCloudflare] = {}
|
||||||
|
self.client = AsyncClient(timeout=config.tetris.request_timeout, proxy=self.proxy)
|
||||||
|
self.ua = UserAgent()
|
||||||
|
|
||||||
async def request(
|
async def request(
|
||||||
self,
|
self,
|
||||||
@@ -129,16 +132,20 @@ class Request:
|
|||||||
else:
|
else:
|
||||||
cookies = None
|
cookies = None
|
||||||
headers = None
|
headers = None
|
||||||
headers = headers if extra_headers is None else extra_headers if headers is None else headers | extra_headers
|
if headers is None:
|
||||||
|
headers = {}
|
||||||
|
if extra_headers:
|
||||||
|
headers.update(extra_headers)
|
||||||
|
headers.setdefault('User-Agent', self.ua.random)
|
||||||
try:
|
try:
|
||||||
async with AsyncClient(cookies=cookies, timeout=config.tetris.request_timeout, proxy=self.proxy) as session:
|
response = await self.client.get(str(url), cookies=cookies, headers=headers)
|
||||||
response = await session.get(str(url), headers=headers)
|
if response.status_code != HTTPStatus.OK:
|
||||||
if response.status_code != HTTPStatus.OK:
|
msg = (
|
||||||
msg = f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}'
|
f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}'
|
||||||
raise RequestError(msg, status_code=response.status_code)
|
)
|
||||||
if is_json:
|
raise RequestError(msg, status_code=response.status_code)
|
||||||
decoder.decode(response.content)
|
if is_json:
|
||||||
return response.content
|
decoder.decode(response.content)
|
||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
msg = f'请求错误 \n{e!r}'
|
msg = f'请求错误 \n{e!r}'
|
||||||
raise RequestError(msg) from e
|
raise RequestError(msg) from e
|
||||||
@@ -146,6 +153,8 @@ class Request:
|
|||||||
if enable_anti_cloudflare and url.host is not None:
|
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)
|
return await self.anti_cloudflares.setdefault(url.host, AntiCloudflare(url.host))(str(url), self.proxy)
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
return response.content
|
||||||
|
|
||||||
async def failover_request(
|
async def failover_request(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from playwright.async_api import BrowserContext, TimeoutError, ViewportSize
|
from playwright.async_api import BrowserContext, TimeoutError, ViewportSize # noqa: A004
|
||||||
|
|
||||||
from ..config.config import config
|
from ..config.config import config
|
||||||
from .browser import BrowserManager
|
from .browser import BrowserManager
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from shutil import rmtree
|
|||||||
from time import time_ns
|
from time import time_ns
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from aiofiles import open
|
from aiofiles import open as aopen
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
@@ -46,7 +46,7 @@ async def download_templates(tag: str) -> Path:
|
|||||||
f'https://github.com/A-Minos/tetris-stats-templates/releases/download/{tag}/dist.zip',
|
f'https://github.com/A-Minos/tetris-stats-templates/releases/download/{tag}/dist.zip',
|
||||||
follow_redirects=True,
|
follow_redirects=True,
|
||||||
) as response,
|
) as response,
|
||||||
open(path, 'wb') as file,
|
aopen(path, 'wb') as file,
|
||||||
):
|
):
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
progress.update(task_id, total=int(response.headers.get('content-length', 0)) or None)
|
progress.update(task_id, total=int(response.headers.get('content-length', 0)) or None)
|
||||||
@@ -76,7 +76,7 @@ async def check_hash(hash_file_path: Path) -> bool:
|
|||||||
if not file_path.is_file():
|
if not file_path.is_file():
|
||||||
logger.error(f'{file_path.name} 不存在或不是文件')
|
logger.error(f'{file_path.name} 不存在或不是文件')
|
||||||
return False
|
return False
|
||||||
async with open(file_path, 'rb') as file:
|
async with aopen(file_path, 'rb') as file:
|
||||||
while True:
|
while True:
|
||||||
chunk = await file.read(65535)
|
chunk = await file.read(65535)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from typing import Literal, TypeAlias
|
|||||||
|
|
||||||
Number: TypeAlias = float | int
|
Number: TypeAlias = float | int
|
||||||
GameType: TypeAlias = Literal['IO', 'TOP', 'TOS']
|
GameType: TypeAlias = Literal['IO', 'TOP', 'TOS']
|
||||||
BaseCommandType: TypeAlias = Literal['bind', 'query']
|
BaseCommandType: TypeAlias = Literal['bind', 'unbind', 'query']
|
||||||
TETRIOCommandType: TypeAlias = BaseCommandType | Literal['rank', 'config', 'list', 'record']
|
TETRIOCommandType: TypeAlias = BaseCommandType | Literal['rank', 'config', 'list', 'record']
|
||||||
AllCommandType: TypeAlias = BaseCommandType | TETRIOCommandType
|
AllCommandType: TypeAlias = BaseCommandType | TETRIOCommandType
|
||||||
Me: TypeAlias = Literal[
|
Me: TypeAlias = Literal[
|
||||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -5,18 +5,20 @@ settings:
|
|||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.3.3
|
version: 3.5.3
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
prettier@3.3.3:
|
|
||||||
resolution:
|
prettier@3.5.3:
|
||||||
{ integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== }
|
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||||
engines: { node: '>=14' }
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
prettier@3.3.3: {}
|
|
||||||
|
prettier@3.5.3: {}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "nonebot-plugin-tetris-stats"
|
name = "nonebot-plugin-tetris-stats"
|
||||||
version = "1.6.3"
|
version = "1.7.2"
|
||||||
description = "一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件"
|
description = "一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "shoucandanghehe", email = "wallfjjd@gmail.com" }]
|
authors = [{ name = "shoucandanghehe", email = "wallfjjd@gmail.com" }]
|
||||||
@@ -10,6 +10,7 @@ dependencies = [
|
|||||||
"aiofiles>=24.1.0",
|
"aiofiles>=24.1.0",
|
||||||
"arclet-alconna<2",
|
"arclet-alconna<2",
|
||||||
"async-lru>=2.0.4",
|
"async-lru>=2.0.4",
|
||||||
|
"fake-useragent>=2.0.3",
|
||||||
"httpx>=0.27.2",
|
"httpx>=0.27.2",
|
||||||
"jinja2>=3.1.4",
|
"jinja2>=3.1.4",
|
||||||
"lxml>=5.3.0",
|
"lxml>=5.3.0",
|
||||||
@@ -22,6 +23,7 @@ dependencies = [
|
|||||||
"nonebot-plugin-session-orm>=0.2.0",
|
"nonebot-plugin-session-orm>=0.2.0",
|
||||||
"nonebot-plugin-user>=0.4.4",
|
"nonebot-plugin-user>=0.4.4",
|
||||||
"nonebot-plugin-userinfo>=0.2.6",
|
"nonebot-plugin-userinfo>=0.2.6",
|
||||||
|
"nonebot-plugin-waiter>=0.8.0",
|
||||||
"nonebot2[fastapi]>=2.3.3",
|
"nonebot2[fastapi]>=2.3.3",
|
||||||
"pandas>=2.2.3",
|
"pandas>=2.2.3",
|
||||||
"pillow>=11.0.0",
|
"pillow>=11.0.0",
|
||||||
@@ -132,8 +134,6 @@ select = [
|
|||||||
]
|
]
|
||||||
ignore = [
|
ignore = [
|
||||||
"E501", # 过长的行由 ruff format 处理, 剩余的都是字符串
|
"E501", # 过长的行由 ruff format 处理, 剩余的都是字符串
|
||||||
"ANN101", # 由 type checker 自动推断
|
|
||||||
"ANN102", # 由 type checker 自动推断
|
|
||||||
"ANN202", # 向 NoneBot 注册的函数
|
"ANN202", # 向 NoneBot 注册的函数
|
||||||
"TRY003",
|
"TRY003",
|
||||||
"COM812", # 强制尾随逗号
|
"COM812", # 强制尾随逗号
|
||||||
@@ -158,7 +158,7 @@ defineConstant = { PYDANTIC_V2 = true }
|
|||||||
typeCheckingMode = "standard"
|
typeCheckingMode = "standard"
|
||||||
|
|
||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "1.6.3"
|
current_version = "1.7.2"
|
||||||
tag = true
|
tag = true
|
||||||
sign_tags = true
|
sign_tags = true
|
||||||
tag_name = "{new_version}"
|
tag_name = "{new_version}"
|
||||||
|
|||||||
19
uv.lock
generated
19
uv.lock
generated
@@ -756,6 +756,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/b7/cd/ff116c050330c4b8c8ebc657f40501460c3e38905fb15993ef21f7d24208/expiringdictx-1.1.0-py3-none-any.whl", hash = "sha256:f5d38ae23b46a8f97da27ce0dd74e669430d43b2efe98232a4a81f10b43cde25", size = 7596 },
|
{ url = "https://files.pythonhosted.org/packages/b7/cd/ff116c050330c4b8c8ebc657f40501460c3e38905fb15993ef21f7d24208/expiringdictx-1.1.0-py3-none-any.whl", hash = "sha256:f5d38ae23b46a8f97da27ce0dd74e669430d43b2efe98232a4a81f10b43cde25", size = 7596 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fake-useragent"
|
||||||
|
version = "2.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d6/a4/f8d204c20e7879c2c1fd1719095673f447a3111282bfe09c0a74a5ed5000/fake_useragent-2.0.3.tar.gz", hash = "sha256:af86a26ef8229efece8fed529b4aeb5b73747d889b60f01cd477b6f301df46e6", size = 194741 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/4f/a639b1dbdc557241e702eefb931ba24ba235c84f8fffdca3e272f096c6af/fake_useragent-2.0.3-py3-none-any.whl", hash = "sha256:8bae50abb72c309a5b3ae2f01a0b82426613fd5c4e2a04dca9332399ec44daa1", size = 201110 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastapi"
|
name = "fastapi"
|
||||||
version = "0.115.3"
|
version = "0.115.3"
|
||||||
@@ -1818,13 +1827,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nonebot-plugin-tetris-stats"
|
name = "nonebot-plugin-tetris-stats"
|
||||||
version = "1.6.2"
|
version = "1.6.3"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiocache" },
|
{ name = "aiocache" },
|
||||||
{ name = "aiofiles" },
|
{ name = "aiofiles" },
|
||||||
{ name = "arclet-alconna" },
|
{ name = "arclet-alconna" },
|
||||||
{ name = "async-lru" },
|
{ name = "async-lru" },
|
||||||
|
{ name = "fake-useragent" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
{ name = "lxml" },
|
{ name = "lxml" },
|
||||||
@@ -1837,6 +1847,7 @@ dependencies = [
|
|||||||
{ name = "nonebot-plugin-session-orm" },
|
{ name = "nonebot-plugin-session-orm" },
|
||||||
{ name = "nonebot-plugin-user" },
|
{ name = "nonebot-plugin-user" },
|
||||||
{ name = "nonebot-plugin-userinfo" },
|
{ name = "nonebot-plugin-userinfo" },
|
||||||
|
{ name = "nonebot-plugin-waiter" },
|
||||||
{ name = "nonebot2", extra = ["fastapi"] },
|
{ name = "nonebot2", extra = ["fastapi"] },
|
||||||
{ name = "pandas" },
|
{ name = "pandas" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
@@ -1845,7 +1856,7 @@ dependencies = [
|
|||||||
{ name = "yarl" },
|
{ name = "yarl" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependency-groups]
|
[package.dev-dependencies]
|
||||||
debug = [
|
debug = [
|
||||||
{ name = "matplotlib" },
|
{ name = "matplotlib" },
|
||||||
{ name = "memory-profiler" },
|
{ name = "memory-profiler" },
|
||||||
@@ -1886,6 +1897,7 @@ requires-dist = [
|
|||||||
{ name = "aiofiles", specifier = ">=24.1.0" },
|
{ name = "aiofiles", specifier = ">=24.1.0" },
|
||||||
{ name = "arclet-alconna", specifier = "<2" },
|
{ name = "arclet-alconna", specifier = "<2" },
|
||||||
{ name = "async-lru", specifier = ">=2.0.4" },
|
{ name = "async-lru", specifier = ">=2.0.4" },
|
||||||
|
{ name = "fake-useragent", specifier = ">=2.0.3" },
|
||||||
{ name = "httpx", specifier = ">=0.27.2" },
|
{ name = "httpx", specifier = ">=0.27.2" },
|
||||||
{ name = "jinja2", specifier = ">=3.1.4" },
|
{ name = "jinja2", specifier = ">=3.1.4" },
|
||||||
{ name = "lxml", specifier = ">=5.3.0" },
|
{ name = "lxml", specifier = ">=5.3.0" },
|
||||||
@@ -1898,6 +1910,7 @@ requires-dist = [
|
|||||||
{ name = "nonebot-plugin-session-orm", specifier = ">=0.2.0" },
|
{ name = "nonebot-plugin-session-orm", specifier = ">=0.2.0" },
|
||||||
{ name = "nonebot-plugin-user", specifier = ">=0.4.4" },
|
{ name = "nonebot-plugin-user", specifier = ">=0.4.4" },
|
||||||
{ name = "nonebot-plugin-userinfo", specifier = ">=0.2.6" },
|
{ name = "nonebot-plugin-userinfo", specifier = ">=0.2.6" },
|
||||||
|
{ name = "nonebot-plugin-waiter", specifier = ">=0.8.0" },
|
||||||
{ name = "nonebot2", extras = ["fastapi"], specifier = ">=2.3.3" },
|
{ name = "nonebot2", extras = ["fastapi"], specifier = ">=2.3.3" },
|
||||||
{ name = "pandas", specifier = ">=2.2.3" },
|
{ name = "pandas", specifier = ">=2.2.3" },
|
||||||
{ name = "pillow", specifier = ">=11.0.0" },
|
{ name = "pillow", specifier = ">=11.0.0" },
|
||||||
@@ -1906,7 +1919,7 @@ requires-dist = [
|
|||||||
{ name = "yarl", specifier = ">=1.16.0" },
|
{ name = "yarl", specifier = ">=1.16.0" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.dependency-groups]
|
[package.metadata.requires-dev]
|
||||||
debug = [
|
debug = [
|
||||||
{ name = "matplotlib", specifier = ">=3.9.2" },
|
{ name = "matplotlib", specifier = ">=3.9.2" },
|
||||||
{ name = "memory-profiler", specifier = ">=0.61.0" },
|
{ name = "memory-profiler", specifier = ">=0.61.0" },
|
||||||
|
|||||||
Reference in New Issue
Block a user