mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
🎨 重命名一些模块
This commit is contained in:
79
nonebot_plugin_tetris_stats/games/tos/__init__.py
Normal file
79
nonebot_plugin_tetris_stats/games/tos/__init__.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, Option
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
|
||||
from ...utils.exception import MessageFormatError
|
||||
from ...utils.typing import Me
|
||||
from .. import add_default_handlers
|
||||
from ..constant import BIND_COMMAND, QUERY_COMMAND
|
||||
from .api import Player
|
||||
from .constant import USER_NAME
|
||||
|
||||
|
||||
def get_player(teaid_or_name: str) -> Player | MessageFormatError:
|
||||
if (
|
||||
teaid_or_name.startswith(('onebot-', 'qqguild-', 'kook-', 'discord-'))
|
||||
and teaid_or_name.split('-', maxsplit=1)[1].isdigit()
|
||||
):
|
||||
return Player(teaid=teaid_or_name, trust=True)
|
||||
if USER_NAME.match(teaid_or_name) and not teaid_or_name.isdigit() and 2 <= len(teaid_or_name) <= 18: # noqa: PLR2004
|
||||
return Player(user_name=teaid_or_name, trust=True)
|
||||
return MessageFormatError('用户名/ID不合法')
|
||||
|
||||
|
||||
alc = on_alconna(
|
||||
Alconna(
|
||||
'茶服',
|
||||
Option(
|
||||
BIND_COMMAND[0],
|
||||
Args(
|
||||
Arg(
|
||||
'account',
|
||||
get_player,
|
||||
notice='茶服 用户名 / TeaID',
|
||||
flags=[ArgFlag.HIDDEN],
|
||||
)
|
||||
),
|
||||
alias=BIND_COMMAND[1:],
|
||||
compact=True,
|
||||
dest='bind',
|
||||
help_text='绑定 茶服 账号',
|
||||
),
|
||||
Option(
|
||||
QUERY_COMMAND[0],
|
||||
Args(
|
||||
Arg(
|
||||
'target',
|
||||
At | Me,
|
||||
notice='@想要查询的人 | 自己',
|
||||
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
|
||||
),
|
||||
Arg(
|
||||
'account',
|
||||
get_player,
|
||||
notice='茶服 用户名 / TeaID',
|
||||
flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL],
|
||||
),
|
||||
# 如果放在一个 Union Args 里, 验证顺序不能保证, 可能出错
|
||||
),
|
||||
alias=QUERY_COMMAND[1:],
|
||||
compact=True,
|
||||
dest='query',
|
||||
help_text='查询 茶服 游戏信息',
|
||||
),
|
||||
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
|
||||
meta=CommandMeta(
|
||||
description='查询 TetrisOnline茶服 的信息',
|
||||
example='茶服查我',
|
||||
compact=True,
|
||||
fuzzy_match=True,
|
||||
),
|
||||
),
|
||||
skip_for_unmatch=False,
|
||||
auto_send_output=True,
|
||||
aliases={'tos', 'TOS'},
|
||||
)
|
||||
|
||||
|
||||
from . import bind, query # noqa: E402, F401
|
||||
|
||||
add_default_handlers(alc)
|
||||
3
nonebot_plugin_tetris_stats/games/tos/api/__init__.py
Normal file
3
nonebot_plugin_tetris_stats/games/tos/api/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .player import Player
|
||||
|
||||
__all__ = ['Player']
|
||||
20
nonebot_plugin_tetris_stats/games/tos/api/models.py
Normal file
20
nonebot_plugin_tetris_stats/games/tos/api/models.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from nonebot_plugin_orm import Model
|
||||
from sqlalchemy import DateTime, String
|
||||
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
||||
|
||||
from ....db.models import PydanticType
|
||||
from .schemas.user_info import UserInfoSuccess
|
||||
from .schemas.user_profile import UserProfile
|
||||
|
||||
|
||||
class TOSHistoricalData(MappedAsDataclass, Model):
|
||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
|
||||
api_type: Mapped[Literal['User Info', 'User Profile']] = mapped_column(String(16), index=True)
|
||||
data: Mapped[UserInfoSuccess | UserProfile] = mapped_column(
|
||||
PydanticType(get_model=[], models={UserInfoSuccess, UserProfile})
|
||||
)
|
||||
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)
|
||||
128
nonebot_plugin_tetris_stats/games/tos/api/player.py
Normal file
128
nonebot_plugin_tetris_stats/games/tos/api/player.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import overload
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from httpx import TimeoutException
|
||||
from nonebot.compat import type_validate_json
|
||||
|
||||
from ....db import anti_duplicate_add
|
||||
from ....utils.exception import RequestError
|
||||
from ....utils.request import Request, splice_url
|
||||
from ..constant import BASE_URL, USER_NAME
|
||||
from .models import TOSHistoricalData
|
||||
from .schemas.user import User
|
||||
from .schemas.user_info import UserInfo, UserInfoSuccess
|
||||
from .schemas.user_profile import UserProfile
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
|
||||
class Player:
|
||||
@overload
|
||||
def __init__(self, *, teaid: str, trust: bool = False): ...
|
||||
@overload
|
||||
def __init__(self, *, user_name: str, trust: bool = False): ...
|
||||
def __init__(self, *, teaid: str | None = None, user_name: str | None = None, trust: bool = False):
|
||||
self.teaid = teaid
|
||||
self.user_name = user_name
|
||||
if not trust:
|
||||
if self.teaid is not None:
|
||||
if (
|
||||
not self.teaid.startswith(('onebot-', 'qqguild-', 'kook-', 'discord-'))
|
||||
or not self.teaid.split('-', maxsplit=1)[1].isdigit()
|
||||
):
|
||||
msg = 'Invalid teaid'
|
||||
raise ValueError(msg)
|
||||
elif self.user_name is not None:
|
||||
if not USER_NAME.match(self.user_name) or self.user_name.isdigit() or 2 > len(self.user_name) > 18: # noqa: PLR2004
|
||||
msg = 'Invalid user name'
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
msg = 'Invalid user'
|
||||
raise ValueError(msg)
|
||||
self.__user: User | None = None
|
||||
self._user_info: UserInfoSuccess | None = None
|
||||
self._user_profile: dict[str, UserProfile] = {}
|
||||
|
||||
@property
|
||||
async def user(self) -> User:
|
||||
if self.__user is None:
|
||||
user_info = await self.get_info()
|
||||
self.__user = User(teaid=user_info.data.teaid, name=user_info.data.name)
|
||||
self.teaid = user_info.data.teaid
|
||||
self.user_name = user_info.data.name
|
||||
return self.__user
|
||||
|
||||
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,))
|
||||
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}'
|
||||
raise RequestError(msg)
|
||||
self._user_info = user_info
|
||||
await anti_duplicate_add(
|
||||
TOSHistoricalData,
|
||||
TOSHistoricalData(
|
||||
user_unique_identifier=(await self.user).unique_identifier,
|
||||
api_type='User Info',
|
||||
data=user_info,
|
||||
update_time=datetime.now(UTC),
|
||||
),
|
||||
)
|
||||
return self._user_info
|
||||
|
||||
async def get_profile(self, other_parameter: dict[str, str | bytes] | None = None) -> UserProfile:
|
||||
"""获取用户数据"""
|
||||
if other_parameter is None:
|
||||
other_parameter = {}
|
||||
params = urlencode(dict(sorted(other_parameter.items())))
|
||||
if self._user_profile.get(params) is None:
|
||||
raw_user_profile = await Request.failover_request(
|
||||
[
|
||||
splice_url(
|
||||
[
|
||||
i,
|
||||
'getProfile',
|
||||
f'?{urlencode({"id":self.teaid or self.user_name,**other_parameter})}',
|
||||
]
|
||||
)
|
||||
for i in BASE_URL
|
||||
],
|
||||
failover_code=[502],
|
||||
failover_exc=(TimeoutException,),
|
||||
)
|
||||
self._user_profile[params] = type_validate_json(UserProfile, raw_user_profile)
|
||||
await anti_duplicate_add(
|
||||
TOSHistoricalData,
|
||||
TOSHistoricalData(
|
||||
user_unique_identifier=(await self.user).unique_identifier,
|
||||
api_type='User Profile',
|
||||
data=self._user_profile[params],
|
||||
update_time=datetime.now(UTC),
|
||||
),
|
||||
)
|
||||
return self._user_profile[params]
|
||||
18
nonebot_plugin_tetris_stats/games/tos/api/schemas/user.py
Normal file
18
nonebot_plugin_tetris_stats/games/tos/api/schemas/user.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from typing import Literal
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
from ....schemas import BaseUser
|
||||
from ...constant import GAME_TYPE
|
||||
|
||||
|
||||
class User(BaseUser):
|
||||
platform: Literal['TOS'] = GAME_TYPE
|
||||
|
||||
teaid: str
|
||||
name: str
|
||||
|
||||
@property
|
||||
@override
|
||||
def unique_identifier(self) -> str:
|
||||
return self.teaid
|
||||
@@ -0,0 +1,89 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PeriodMatch(BaseModel):
|
||||
name: str
|
||||
teaid: str = Field(..., alias='teaId')
|
||||
rating: str
|
||||
rd: str
|
||||
start_time: datetime = Field(..., alias='startTime')
|
||||
end_time: datetime = Field(..., alias='endTime')
|
||||
win: str
|
||||
lose: str
|
||||
score: str
|
||||
|
||||
|
||||
class UserDataTotalItem(BaseModel):
|
||||
time_map: str = Field(..., alias='timeMap')
|
||||
pieces_map: str = Field(..., alias='piecesMap')
|
||||
clear_lines_map: str = Field(..., alias='clearLinesMap')
|
||||
attacks_map: str = Field(..., alias='attacksMap')
|
||||
dig_map: str = Field(..., alias='digMap')
|
||||
send_map: str = Field(..., alias='sendMap')
|
||||
rise_map: str = Field(..., alias='riseMap')
|
||||
offset_map: str = Field(..., alias='offsetMap')
|
||||
receive_map: str = Field(..., alias='receiveMap')
|
||||
games_map: str = Field(..., alias='gamesMap')
|
||||
tetris_map: str = Field(..., alias='tetrisMap')
|
||||
combo_map: str = Field(..., alias='comboMap')
|
||||
tspin_map: str = Field(..., alias='tspinMap')
|
||||
b2b_map: str = Field(..., alias='b2bMap')
|
||||
perfect_clear_map: str = Field(..., alias='perfectClearMap')
|
||||
time_no_map: str = Field(..., alias='timeNoMap')
|
||||
pieces_no_map: str = Field(..., alias='piecesNoMap')
|
||||
clear_lines_no_map: str = Field(..., alias='clearLinesNoMap')
|
||||
attacks_no_map: str = Field(..., alias='attacksNoMap')
|
||||
dig_no_map: str = Field(..., alias='digNoMap')
|
||||
send_no_map: str = Field(..., alias='sendNoMap')
|
||||
rise_no_map: str = Field(..., alias='riseNoMap')
|
||||
offset_no_map: str = Field(..., alias='offsetNoMap')
|
||||
receive_no_map: str = Field(..., alias='receiveNoMap')
|
||||
games_no_map: str = Field(..., alias='gamesNoMap')
|
||||
tetris_no_map: str = Field(..., alias='tetrisNoMap')
|
||||
combo_no_map: str = Field(..., alias='comboNoMap')
|
||||
tspin_no_map: str = Field(..., alias='tspinNoMap')
|
||||
b2b_no_map: str = Field(..., alias='b2bNoMap')
|
||||
perfect_clear_no_map: str = Field(..., alias='perfectClearNoMap')
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
teaid: str = Field(..., alias='teaId')
|
||||
name: str
|
||||
total_exp: str = Field(..., alias='totalExp')
|
||||
ranking: str
|
||||
ranked_games: str = Field(..., alias='rankedGames')
|
||||
rating_now: str = Field(..., alias='ratingNow')
|
||||
rd_now: str = Field(..., alias='rdNow')
|
||||
vol_now: str = Field(..., alias='volNow')
|
||||
rating_last: str = Field(..., alias='ratingLast')
|
||||
rd_last: str = Field(..., alias='rdLast')
|
||||
vol_last: str = Field(..., alias='volLast')
|
||||
period_matches: list[PeriodMatch] = Field(..., alias='periodMatches')
|
||||
user_data_total: list[UserDataTotalItem] = Field(..., alias='userDataTotal')
|
||||
ranking_items: str = Field(..., alias='rankingItems')
|
||||
ranking_game_items: str = Field(..., alias='rankingGameItems')
|
||||
training_level: str = Field(..., alias='trainingLevel')
|
||||
training_wins: str = Field(..., alias='trainingWins')
|
||||
pb_sprint: str = Field(..., alias='PBSprint')
|
||||
pb_marathon: str = Field(..., alias='PBMarathon')
|
||||
pb_challenge: str = Field(..., alias='PBChallenge')
|
||||
register_date: datetime = Field(..., alias='registerDate')
|
||||
last_login_date: datetime = Field(..., alias='lastLoginDate')
|
||||
|
||||
|
||||
class UserInfoSuccess(BaseModel):
|
||||
code: int
|
||||
success: Literal[True]
|
||||
data: Data
|
||||
|
||||
|
||||
class FailedModel(BaseModel):
|
||||
code: int
|
||||
success: Literal[False]
|
||||
error: str
|
||||
|
||||
|
||||
UserInfo = UserInfoSuccess | FailedModel
|
||||
@@ -0,0 +1,34 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
idmultiplayergameresult: int
|
||||
iduser: str
|
||||
teaid: str
|
||||
time: int
|
||||
clear_lines: int
|
||||
attack: int
|
||||
send: int
|
||||
offset: int
|
||||
receive: int
|
||||
rise: int
|
||||
dig: int
|
||||
pieces: int
|
||||
max_combo: int
|
||||
pc_count: int
|
||||
place: int
|
||||
num_players: int
|
||||
fumen_code: Literal['0', '1'] # wtf
|
||||
rule_set: str
|
||||
garbage: str
|
||||
idmultiplayergame: int
|
||||
datetime: datetime
|
||||
|
||||
|
||||
class UserProfile(BaseModel):
|
||||
code: int
|
||||
success: bool
|
||||
data: list[Data]
|
||||
66
nonebot_plugin_tetris_stats/games/tos/bind.py
Normal file
66
nonebot_plugin_tetris_stats/games/tos/bind.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from urllib.parse import urlunparse
|
||||
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession # type: ignore[import-untyped]
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo # type: ignore[import-untyped]
|
||||
|
||||
from ...db import BindStatus, create_or_update_bind, trigger
|
||||
from ...utils.avatar import get_avatar
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.platform import get_platform
|
||||
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('bind')
|
||||
async def _(
|
||||
bot: Bot,
|
||||
account: Player,
|
||||
event_session: EventSession,
|
||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
||||
):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='bind',
|
||||
command_args=[],
|
||||
):
|
||||
user = await account.user
|
||||
async with get_session() as session:
|
||||
bind_status = await create_or_update_bind(
|
||||
session=session,
|
||||
chat_platform=get_platform(bot),
|
||||
chat_account=event_user_info.user_id,
|
||||
game_platform=GAME_TYPE,
|
||||
game_account=user.unique_identifier,
|
||||
)
|
||||
user_info = await account.get_info()
|
||||
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||
async with HostPage(
|
||||
await render(
|
||||
'binding',
|
||||
Bind(
|
||||
platform=GAME_TYPE,
|
||||
status='unknown',
|
||||
user=People(
|
||||
avatar=await get_avatar(event_user_info, 'Data URI', None), name=user_info.data.name
|
||||
),
|
||||
bot=People(
|
||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||
name=bot_info.user_remark or bot_info.user_displayname or bot_info.user_name,
|
||||
),
|
||||
command='茶服查我',
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
await UniMessage.image(
|
||||
raw=await screenshot(urlunparse(('http', get_self_netloc(), f'/host/{page_hash}.html', '', '', '')))
|
||||
).finish()
|
||||
16
nonebot_plugin_tetris_stats/games/tos/constant.py
Normal file
16
nonebot_plugin_tetris_stats/games/tos/constant.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from re import compile
|
||||
from typing import Literal
|
||||
|
||||
GAME_TYPE: Literal['TOS'] = 'TOS'
|
||||
|
||||
BASE_URL = {
|
||||
'https://teatube.cn:8888/',
|
||||
'http://cafuuchino1.studio26f.org:19970',
|
||||
'http://cafuuchino2.studio26f.org:19970',
|
||||
'http://cafuuchino3.studio26f.org:19970',
|
||||
'http://cafuuchino4.studio26f.org:19970',
|
||||
}
|
||||
|
||||
USER_NAME = compile(
|
||||
r'^(?!\.)(?!com[0-9]$)(?!con$)(?!lpt[0-9]$)(?!nul$)(?!prn$)[^\-][^\+][^\|\*\?\\\s\!:<>/$"]*[^\.\|\*\?\\\s\!:<>/$"]+$'
|
||||
)
|
||||
170
nonebot_plugin_tetris_stats/games/tos/query.py
Normal file
170
nonebot_plugin_tetris_stats/games/tos/query.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from asyncio import gather
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession # type: ignore[import-untyped]
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
|
||||
from ...db import query_bind_info, trigger
|
||||
from ...utils.metrics import TetrisMetricsProWithLPMADPM, get_metrics
|
||||
from ...utils.platform import get_platform
|
||||
from ...utils.typing import Me
|
||||
from ..constant import CANT_VERIFY_MESSAGE
|
||||
from . import alc
|
||||
from .api import Player
|
||||
from .constant import GAME_TYPE
|
||||
|
||||
|
||||
def add_special_handlers(
|
||||
teaid_prefix: Literal['onebot-', 'kook-', 'discord-', 'qqguild-'], match_event: type[Event]
|
||||
) -> None:
|
||||
@alc.assign('query')
|
||||
async def _(event: Event, target: At | Me, event_session: EventSession):
|
||||
if isinstance(event, match_event):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='query',
|
||||
command_args=[],
|
||||
):
|
||||
await (
|
||||
await make_query_text(
|
||||
Player(
|
||||
teaid=f'{teaid_prefix}{target.target}'
|
||||
if isinstance(target, At)
|
||||
else f'{teaid_prefix}{event.get_user_id()}',
|
||||
trust=True,
|
||||
)
|
||||
)
|
||||
).finish()
|
||||
|
||||
|
||||
try:
|
||||
from nonebot.adapters.onebot.v11 import MessageEvent as OB11MessageEvent
|
||||
|
||||
add_special_handlers('onebot-', OB11MessageEvent)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from nonebot.adapters.qq.event import GuildMessageEvent as QQGuildMessageEvent
|
||||
from nonebot.adapters.qq.event import QQMessageEvent
|
||||
|
||||
add_special_handlers('qqguild-', QQGuildMessageEvent)
|
||||
add_special_handlers('onebot-', QQMessageEvent)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from nonebot.adapters.kaiheila.event import MessageEvent as KookMessageEvent
|
||||
|
||||
add_special_handlers('kook-', KookMessageEvent)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from nonebot.adapters.discord import MessageEvent as DiscordMessageEvent
|
||||
|
||||
add_special_handlers('discord-', DiscordMessageEvent)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
@alc.assign('query')
|
||||
async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me, event_session: EventSession):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='query',
|
||||
command_args=[],
|
||||
):
|
||||
async with get_session() as session:
|
||||
bind = await query_bind_info(
|
||||
session=session,
|
||||
chat_platform=get_platform(bot),
|
||||
chat_account=(target.target if isinstance(target, At) else event.get_user_id()),
|
||||
game_platform=GAME_TYPE,
|
||||
)
|
||||
if bind is None:
|
||||
await matcher.finish('未查询到绑定信息')
|
||||
message = CANT_VERIFY_MESSAGE
|
||||
await (message + await make_query_text(Player(teaid=bind.game_account, trust=True))).finish()
|
||||
|
||||
|
||||
@alc.assign('query')
|
||||
async def _(account: Player, event_session: EventSession):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='query',
|
||||
command_args=[],
|
||||
):
|
||||
await (await make_query_text(account)).finish()
|
||||
|
||||
|
||||
@dataclass
|
||||
class GameData:
|
||||
game_num: int
|
||||
metrics: TetrisMetricsProWithLPMADPM
|
||||
|
||||
|
||||
async def get_game_data(player: Player, query_num: int = 50) -> GameData | None:
|
||||
"""获取游戏数据"""
|
||||
user_profile = await player.get_profile()
|
||||
if user_profile.data == []:
|
||||
return None
|
||||
weighted_total_lpm = weighted_total_apm = weighted_total_adpm = total_time = 0.0
|
||||
num = 0
|
||||
for i in user_profile.data:
|
||||
# 排除单人局和时间为0的游戏
|
||||
# 茶: 不计算没挖掘的局, 即使apm和lpm也如此
|
||||
if i.num_players == 1 or i.time == 0 or i.dig is None:
|
||||
continue
|
||||
# 加权计算
|
||||
time = i.time / 1000
|
||||
lpm = 24 * (i.pieces / time)
|
||||
apm = (i.attack / time) * 60
|
||||
adpm = ((i.attack + i.dig) / time) * 60
|
||||
weighted_total_lpm += lpm * time
|
||||
weighted_total_apm += apm * time
|
||||
weighted_total_adpm += adpm * time
|
||||
total_time += time
|
||||
num += 1
|
||||
if num >= query_num:
|
||||
break
|
||||
if num == 0:
|
||||
return None
|
||||
# TODO: 如果有效局数小于 {查询数} , 并且没有无dig信息的局, 且 user_profile.data 内有{请求数}个局, 则继续往前获取信息
|
||||
metrics = get_metrics(
|
||||
lpm=weighted_total_lpm / total_time, apm=weighted_total_apm / total_time, adpm=weighted_total_adpm / total_time
|
||||
)
|
||||
lpm = weighted_total_lpm / total_time
|
||||
apm = weighted_total_apm / total_time
|
||||
adpm = weighted_total_adpm / total_time
|
||||
return GameData(game_num=num, metrics=metrics)
|
||||
|
||||
|
||||
async def make_query_text(player: Player) -> UniMessage:
|
||||
user_info, game_data = await gather(player.get_info(), get_game_data(player))
|
||||
user_data = user_info.data
|
||||
message = f'用户 {user_data.name} ({user_data.teaid}) '
|
||||
if user_data.ranked_games == '0':
|
||||
message += '暂无段位统计数据'
|
||||
else:
|
||||
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:
|
||||
message += ', 暂无游戏数据'
|
||||
else:
|
||||
message += f', 最近 {game_data.game_num} 局数据'
|
||||
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'\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'\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 ''
|
||||
return UniMessage(message)
|
||||
Reference in New Issue
Block a user