mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f39faced7e | |||
| fffa07dc03 | |||
| 0467b3e5df | |||
| f6cc0229ba | |||
| e2708b661d | |||
| 65d019a6d3 | |||
| be1b07d5dc | |||
| c92bc3aaad | |||
| d4b887ef83 | |||
|
|
695ff13aa2 | ||
| ec1001b3bb | |||
| b545b12255 | |||
| b2505e0979 | |||
| 38defe37cd | |||
| 7a3d7c908c |
@@ -8,6 +8,7 @@ from nonebot_plugin_orm import Model
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from sqlalchemy import JSON, DateTime, Dialect, PickleType, String, TypeDecorator
|
||||
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
||||
from typing_extensions import override
|
||||
|
||||
from ..game_data_processor.schemas import BaseProcessedData, BaseUser
|
||||
from ..utils.typing import CommandType, GameType
|
||||
@@ -16,17 +17,20 @@ from ..utils.typing import CommandType, GameType
|
||||
class PydanticType(TypeDecorator):
|
||||
impl = JSON
|
||||
|
||||
def __init__(self, get_model: Callable[[], Sequence[type[BaseModel]]], *args: Any, **kwargs: Any): # noqa: ANN401
|
||||
@override
|
||||
def __init__(self, get_model: Callable[[], Sequence[type[BaseModel]]], *args: Any, **kwargs: Any):
|
||||
self.get_model = get_model
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str: # noqa: ANN401
|
||||
@override
|
||||
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str:
|
||||
# 将 Pydantic 模型实例转换为 JSON
|
||||
if isinstance(value, tuple(self.get_model())):
|
||||
return value.json() # type: ignore[union-attr]
|
||||
raise TypeError
|
||||
|
||||
def process_result_value(self, value: Any | None, dialect: Dialect) -> BaseModel: # noqa: ANN401
|
||||
@override
|
||||
def process_result_value(self, value: Any | None, dialect: Dialect) -> BaseModel:
|
||||
# 将 JSON 转换回 Pydantic 模型实例
|
||||
if isinstance(value, str | bytes):
|
||||
for i in self.get_model():
|
||||
|
||||
@@ -55,15 +55,10 @@ class Processor(ABC):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def handle_query(self) -> str:
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __del__(self) -> None:
|
||||
finish_time = datetime.now(tz=UTC)
|
||||
if Recorder.is_error_event(self.event_id):
|
||||
|
||||
@@ -5,6 +5,7 @@ from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, O
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
|
||||
from sqlalchemy import func, select
|
||||
@@ -117,10 +118,11 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(message + await proc.handle_query())
|
||||
await (UniMessage(message) + await proc.handle_query()).send()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
@alc.assign('query')
|
||||
@@ -131,10 +133,11 @@ async def _(event: Event, matcher: Matcher, account: User):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).send()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
@alc.assign('rank')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from asyncio import gather
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -18,6 +19,7 @@ from nonebot_plugin_localstore import get_data_file # type: ignore[import-untyp
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
|
||||
from sqlalchemy import select
|
||||
from typing_extensions import override
|
||||
from zstandard import ZstdCompressor
|
||||
|
||||
from ...db import BindStatus, create_or_update_bind
|
||||
@@ -32,15 +34,13 @@ from .. import Processor as ProcessorMeta
|
||||
from .cache import Cache
|
||||
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
|
||||
from .model import IORank
|
||||
from .schemas.league_all import FailedModel as LeagueAllFailed
|
||||
from .schemas.base import FailedModel
|
||||
from .schemas.league_all import LeagueAll
|
||||
from .schemas.league_all import ValidUser as LeagueAllUser
|
||||
from .schemas.response import ProcessedData, RawResponse
|
||||
from .schemas.user import User
|
||||
from .schemas.user_info import FailedModel as InfoFailed
|
||||
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, UserInfo
|
||||
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, RatedLeague, UserInfo
|
||||
from .schemas.user_info import SuccessModel as InfoSuccess
|
||||
from .schemas.user_records import FailedModel as RecordsFailed
|
||||
from .schemas.user_records import SoloRecord, UserRecords
|
||||
from .schemas.user_records import SuccessModel as RecordsSuccess
|
||||
from .typing import Rank
|
||||
@@ -63,15 +63,18 @@ class Processor(ProcessorMeta):
|
||||
raw_response: RawResponse
|
||||
processed_data: ProcessedData
|
||||
|
||||
@override
|
||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||
super().__init__(event_id, user, command_args)
|
||||
self.raw_response = RawResponse()
|
||||
self.processed_data = ProcessedData()
|
||||
|
||||
@property
|
||||
@override
|
||||
def game_platform(self) -> Literal['IO']:
|
||||
return GAME_TYPE
|
||||
|
||||
@override
|
||||
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
|
||||
"""处理绑定消息"""
|
||||
self.command_type = 'bind'
|
||||
@@ -110,11 +113,105 @@ class Processor(ProcessorMeta):
|
||||
)
|
||||
return message
|
||||
|
||||
async def handle_query(self) -> str:
|
||||
@override
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
self.command_type = 'query'
|
||||
await self.get_user()
|
||||
return await self.generate_message()
|
||||
user_info, user_records = await gather(self.get_user_info(), self.get_user_records())
|
||||
user_name = user_info.data.user.username.upper()
|
||||
league = user_info.data.user.league
|
||||
sprint = user_records.data.records.sprint
|
||||
blitz = user_records.data.records.blitz
|
||||
if isinstance(league, RatedLeague) and league.vs is not None:
|
||||
if sprint.record is None:
|
||||
sprint_value = 'N/A'
|
||||
else:
|
||||
if not isinstance(sprint.record, SoloRecord):
|
||||
raise WhatTheFuckError('40L记录不是单人记录')
|
||||
duration = timedelta(milliseconds=sprint.record.endcontext.final_time).total_seconds()
|
||||
sprint_value = f'{duration:.1f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.1f}s' # noqa: PLR2004
|
||||
if blitz.record is None:
|
||||
blitz_value = 'N/A'
|
||||
else:
|
||||
if not isinstance(blitz.record, SoloRecord):
|
||||
raise WhatTheFuckError('Blitz记录不是单人记录')
|
||||
blitz_value = f'{blitz.record.endcontext.score:,}'
|
||||
async with HostPage(
|
||||
await render(
|
||||
'data.j2.html',
|
||||
user_avatar=f'https://tetr.io/user-content/avatars/{user_info.data.user.id}.jpg?rv={user_info.data.user.avatar_revision}'
|
||||
if user_info.data.user.avatar_revision is not None
|
||||
else f'../../identicon?md5={md5(user_info.data.user.id.encode()).hexdigest()}', # noqa: S324
|
||||
user_name=user_name,
|
||||
user_sign=user_info.data.user.bio,
|
||||
game_type='TETR.IO',
|
||||
ranking=round(league.glicko, 2),
|
||||
rd=round(league.rd, 2),
|
||||
rank=league.rank,
|
||||
TR=round(league.rating, 2),
|
||||
global_rank=league.standing,
|
||||
lpm=round(lpm := (league.pps * 24), 2),
|
||||
pps=league.pps,
|
||||
apm=league.apm,
|
||||
apl=round(league.apm / lpm, 2),
|
||||
adpm=round(adpm := (league.vs * 0.6), 2),
|
||||
adpl=round(adpm / lpm, 2),
|
||||
vs=league.vs,
|
||||
sprint=sprint_value,
|
||||
blitz=blitz_value,
|
||||
data=[[0, 0]],
|
||||
split_value=0,
|
||||
value_max=0,
|
||||
value_min=0,
|
||||
offset=0,
|
||||
app=(app := (league.apm / (60 * league.pps))),
|
||||
dsps=(dsps := ((league.vs / 100) - (league.apm / 60))),
|
||||
dspp=(dspp := (dsps / league.pps)),
|
||||
ci=150 * dspp - 125 * app + 50 * (league.vs / league.apm) - 25,
|
||||
ge=2 * ((app * dsps) / league.pps),
|
||||
)
|
||||
) as page_hash:
|
||||
return UniMessage.image(
|
||||
raw=await screenshot(
|
||||
urlunparse(('http', get_self_netloc(), f'/host/page/{page_hash}.html', '', '', ''))
|
||||
)
|
||||
)
|
||||
# call back
|
||||
ret_message = ''
|
||||
if isinstance(league, NeverPlayedLeague):
|
||||
ret_message += f'用户 {user_name} 没有排位统计数据'
|
||||
else:
|
||||
if isinstance(league, NeverRatedLeague):
|
||||
ret_message += f'用户 {user_name} 暂未完成定级赛, 最近十场的数据:'
|
||||
else:
|
||||
if league.rank == 'z':
|
||||
ret_message += f'用户 {user_name} 暂无段位, {round(league.rating,2)} TR'
|
||||
else:
|
||||
ret_message += (
|
||||
f'{league.rank.upper()} 段用户 {user_name} {round(league.rating,2)} TR (#{league.standing})'
|
||||
)
|
||||
ret_message += f', 段位分 {round(league.glicko,2)}±{round(league.rd,2)}, 最近十场的数据:'
|
||||
lpm = league.pps * 24
|
||||
ret_message += f"\nL'PM: {round(lpm, 2)} ( {league.pps} pps )"
|
||||
ret_message += f'\nAPM: {league.apm} ( x{round(league.apm/lpm,2)} )'
|
||||
if league.vs is not None:
|
||||
adpm = league.vs * 0.6
|
||||
ret_message += f'\nADPM: {round(adpm,2)} ( x{round(adpm/lpm,2)} ) ( {league.vs}vs )'
|
||||
user_records = await self.get_user_records()
|
||||
sprint = user_records.data.records.sprint
|
||||
if sprint.record is not None:
|
||||
if not isinstance(sprint.record, SoloRecord):
|
||||
raise WhatTheFuckError('40L记录不是单人记录')
|
||||
ret_message += f'\n40L: {round(sprint.record.endcontext.final_time/1000,2)}s'
|
||||
ret_message += f' ( #{sprint.rank} )' if sprint.rank is not None else ''
|
||||
blitz = user_records.data.records.blitz
|
||||
if blitz.record is not None:
|
||||
if not isinstance(blitz.record, SoloRecord):
|
||||
raise WhatTheFuckError('Blitz记录不是单人记录')
|
||||
ret_message += f'\nBlitz: {blitz.record.endcontext.score}'
|
||||
ret_message += f' ( #{blitz.rank} )' if blitz.rank is not None else ''
|
||||
return UniMessage(ret_message)
|
||||
|
||||
async def get_user(self) -> None:
|
||||
"""
|
||||
@@ -132,7 +229,7 @@ class Processor(ProcessorMeta):
|
||||
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}'])
|
||||
)
|
||||
user_info: UserInfo = type_validate_json(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
|
||||
if isinstance(user_info, InfoFailed):
|
||||
if isinstance(user_info, FailedModel):
|
||||
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
|
||||
self.processed_data.user_info = user_info
|
||||
return self.processed_data.user_info
|
||||
@@ -144,51 +241,11 @@ class Processor(ProcessorMeta):
|
||||
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}/', 'records'])
|
||||
)
|
||||
user_records: UserRecords = type_validate_json(UserRecords, self.raw_response.user_records) # type: ignore[arg-type]
|
||||
if isinstance(user_records, RecordsFailed):
|
||||
if isinstance(user_records, FailedModel):
|
||||
raise RequestError(f'用户Solo数据请求错误:\n{user_records.error}')
|
||||
self.processed_data.user_records = user_records
|
||||
return self.processed_data.user_records
|
||||
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
user_info = await self.get_user_info()
|
||||
user_name = user_info.data.user.username.upper()
|
||||
league = user_info.data.user.league
|
||||
ret_message = ''
|
||||
if isinstance(league, NeverPlayedLeague):
|
||||
ret_message += f'用户 {user_name} 没有排位统计数据'
|
||||
else:
|
||||
if isinstance(league, NeverRatedLeague):
|
||||
ret_message += f'用户 {user_name} 暂未完成定级赛, 最近十场的数据:'
|
||||
else:
|
||||
if league.rank == 'z':
|
||||
ret_message += f'用户 {user_name} 暂无段位, {round(league.rating,2)} TR'
|
||||
else:
|
||||
ret_message += (
|
||||
f'{league.rank.upper()} 段用户 {user_name} {round(league.rating,2)} TR (#{league.standing})'
|
||||
)
|
||||
ret_message += f', 段位分 {round(league.glicko,2)}±{round(league.rd,2)}, 最近十场的数据:'
|
||||
lpm = league.pps * 24
|
||||
ret_message += f"\nL'PM: {round(lpm, 2)} ( {league.pps} pps )"
|
||||
ret_message += f'\nAPM: {league.apm} ( x{round(league.apm/(league.pps*24),2)} )'
|
||||
if league.vs is not None:
|
||||
adpm = league.vs * 0.6
|
||||
ret_message += f'\nADPM: {round(adpm,2)} ( x{round(adpm/lpm,2)} ) ( {league.vs}vs )'
|
||||
user_records = await self.get_user_records()
|
||||
sprint = user_records.data.records.sprint
|
||||
if sprint.record is not None:
|
||||
if not isinstance(sprint.record, SoloRecord):
|
||||
raise WhatTheFuckError('40L记录不是单人记录')
|
||||
ret_message += f'\n40L: {round(sprint.record.endcontext.final_time/1000,2)}s'
|
||||
ret_message += f' ( #{sprint.rank} )' if sprint.rank is not None else ''
|
||||
blitz = user_records.data.records.blitz
|
||||
if blitz.record is not None:
|
||||
if not isinstance(blitz.record, SoloRecord):
|
||||
raise WhatTheFuckError('Blitz记录不是单人记录')
|
||||
ret_message += f'\nBlitz: {blitz.record.endcontext.score}'
|
||||
ret_message += f' ( #{blitz.rank} )' if blitz.rank is not None else ''
|
||||
return ret_message
|
||||
|
||||
|
||||
@scheduler.scheduled_job('cron', hour='0,6,12,18', minute=0)
|
||||
@retry(exception_type=RequestError, delay=timedelta(minutes=15))
|
||||
@@ -197,7 +254,7 @@ async def get_io_rank_data() -> None:
|
||||
LeagueAll, # type: ignore[arg-type]
|
||||
(data := await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))),
|
||||
)
|
||||
if isinstance(league_all, LeagueAllFailed):
|
||||
if isinstance(league_all, FailedModel):
|
||||
raise RequestError(f'排行榜数据请求错误:\n{league_all.error}')
|
||||
|
||||
def pps(user: LeagueAllUser) -> float:
|
||||
|
||||
@@ -2,6 +2,7 @@ from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, O
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo # type: ignore[import-untyped]
|
||||
|
||||
@@ -111,10 +112,11 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(message + await proc.handle_query())
|
||||
await (UniMessage(message) + await proc.handle_query()).send()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
@alc.assign('query')
|
||||
@@ -125,10 +127,11 @@ async def _(event: Event, matcher: Matcher, account: User):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).send()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
add_default_handlers(alc)
|
||||
|
||||
@@ -10,6 +10,7 @@ from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
|
||||
from pandas import read_html
|
||||
from typing_extensions import override
|
||||
|
||||
from ...db import BindStatus, create_or_update_bind
|
||||
from ...utils.avatar import get_avatar
|
||||
@@ -30,6 +31,7 @@ class User(BaseUser):
|
||||
name: str
|
||||
|
||||
@property
|
||||
@override
|
||||
def unique_identifier(self) -> str:
|
||||
return self.name
|
||||
|
||||
@@ -57,15 +59,18 @@ class Processor(ProcessorMeta):
|
||||
raw_response: RawResponse
|
||||
processed_data: ProcessedData
|
||||
|
||||
@override
|
||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||
super().__init__(event_id, user, command_args)
|
||||
self.raw_response = RawResponse()
|
||||
self.processed_data = ProcessedData()
|
||||
|
||||
@property
|
||||
@override
|
||||
def game_platform(self) -> Literal['TOP']:
|
||||
return GAME_TYPE
|
||||
|
||||
@override
|
||||
async def handle_bind(self, platform: str, account: str, bot_info: UserInfo, user_info: UserInfo) -> UniMessage:
|
||||
"""处理绑定消息"""
|
||||
self.command_type = 'bind'
|
||||
@@ -99,11 +104,26 @@ class Processor(ProcessorMeta):
|
||||
)
|
||||
return message
|
||||
|
||||
async def handle_query(self) -> str:
|
||||
@override
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
self.command_type = 'query'
|
||||
await self.check_user()
|
||||
return await self.generate_message()
|
||||
game_data = await self.get_game_data()
|
||||
message = ''
|
||||
if game_data.day is not None:
|
||||
message += f'用户 {self.user.name} 24小时内统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.day.lpm,2)} ( {round(game_data.day.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.day.apm,2)} ( x{round(game_data.day.apm/game_data.day.lpm,2)} )'
|
||||
else:
|
||||
message += f'用户 {self.user.name} 暂无24小时内统计数据'
|
||||
if game_data.total is not None:
|
||||
message += '\n历史统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.total.lpm,2)} ( {round(game_data.total.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.total.apm,2)} ( x{round(game_data.total.apm/game_data.total.lpm,2)} )'
|
||||
else:
|
||||
message += '\n暂无历史统计数据'
|
||||
return UniMessage(message)
|
||||
|
||||
async def get_user_profile(self) -> str:
|
||||
"""获取用户信息"""
|
||||
@@ -140,21 +160,3 @@ class Processor(ProcessorMeta):
|
||||
dataframe = read_html(table, encoding='utf-8', header=0)[0]
|
||||
total = Data(lpm=dataframe['lpm'].mean(), apm=dataframe['apm'].mean()) if len(dataframe) != 0 else None
|
||||
return GameData(day=day, total=total)
|
||||
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
game_data = await self.get_game_data()
|
||||
message = ''
|
||||
if game_data.day is not None:
|
||||
message += f'用户 {self.user.name} 24小时内统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.day.lpm,2)} ( {round(game_data.day.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.day.apm,2)} ( x{round(game_data.day.apm/game_data.day.lpm,2)} )'
|
||||
else:
|
||||
message += f'用户 {self.user.name} 暂无24小时内统计数据'
|
||||
if game_data.total is not None:
|
||||
message += '\n历史统计数据为: '
|
||||
message += f"\nL'PM: {round(game_data.total.lpm,2)} ( {round(game_data.total.lpm/24,2)} pps )"
|
||||
message += f'\nAPM: {round(game_data.total.apm,2)} ( x{round(game_data.total.apm/game_data.total.lpm,2)} )'
|
||||
else:
|
||||
message += '\n暂无历史统计数据'
|
||||
return message
|
||||
|
||||
@@ -4,6 +4,7 @@ from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, O
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At, on_alconna
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
|
||||
|
||||
@@ -72,12 +73,13 @@ alc = on_alconna(
|
||||
|
||||
async def finish_special_query(matcher: Matcher, proc: Processor) -> NoReturn:
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).send()
|
||||
except NeedCatchError as e:
|
||||
if isinstance(e, RequestError) and '未找到此用户' in e.message:
|
||||
matcher.skip()
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
try:
|
||||
@@ -165,10 +167,11 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(message + await proc.handle_query())
|
||||
await (UniMessage(message) + await proc.handle_query()).send()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
@alc.assign('query')
|
||||
@@ -179,10 +182,11 @@ async def _(event: Event, matcher: Matcher, account: User):
|
||||
command_args=[],
|
||||
)
|
||||
try:
|
||||
await matcher.finish(await proc.handle_query())
|
||||
await (await proc.handle_query()).send()
|
||||
except NeedCatchError as e:
|
||||
await matcher.send(str(e))
|
||||
raise HandleNotFinishedError from e
|
||||
await matcher.finish()
|
||||
|
||||
|
||||
add_default_handlers(alc)
|
||||
|
||||
@@ -8,6 +8,7 @@ from nonebot.compat import type_validate_json
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
|
||||
from typing_extensions import override
|
||||
|
||||
from ...db import BindStatus, create_or_update_bind
|
||||
from ...utils.avatar import get_avatar
|
||||
@@ -32,6 +33,7 @@ class User(BaseUser):
|
||||
name: str | None = None
|
||||
|
||||
@property
|
||||
@override
|
||||
def unique_identifier(self) -> str:
|
||||
if self.teaid is None:
|
||||
raise ValueError('不完整的User!')
|
||||
@@ -70,15 +72,18 @@ class Processor(ProcessorMeta):
|
||||
raw_response: RawResponse
|
||||
processed_data: ProcessedData
|
||||
|
||||
@override
|
||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||
super().__init__(event_id, user, command_args)
|
||||
self.raw_response = RawResponse(user_profile={})
|
||||
self.processed_data = ProcessedData(user_profile={})
|
||||
|
||||
@property
|
||||
@override
|
||||
def game_platform(self) -> Literal['TOS']:
|
||||
return GAME_TYPE
|
||||
|
||||
@override
|
||||
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
|
||||
"""处理绑定消息"""
|
||||
self.command_type = 'bind'
|
||||
@@ -113,11 +118,29 @@ class Processor(ProcessorMeta):
|
||||
)
|
||||
return message
|
||||
|
||||
async def handle_query(self) -> str:
|
||||
@override
|
||||
async def handle_query(self) -> UniMessage:
|
||||
"""处理查询消息"""
|
||||
self.command_type = 'query'
|
||||
await self.get_user()
|
||||
return await self.generate_message()
|
||||
user_info = (await self.get_user_info()).data
|
||||
message = f'用户 {user_info.name} ({user_info.teaid}) '
|
||||
if user_info.ranked_games == '0':
|
||||
message += '暂无段位统计数据'
|
||||
else:
|
||||
message += f', 段位分 {round(float(user_info.rating_now),2)}±{round(float(user_info.rd_now),2)} ({round(float(user_info.vol_now),2)}) '
|
||||
game_data = await self.get_game_data()
|
||||
if game_data is None:
|
||||
message += ', 暂无游戏数据'
|
||||
else:
|
||||
message += f', 最近 {game_data.num} 局数据'
|
||||
message += f"\nL'PM: {game_data.lpm} ( {game_data.pps} pps )"
|
||||
message += f'\nAPM: {game_data.apm} ( x{game_data.apl} )'
|
||||
message += f'\nADPM: {game_data.adpm} ( x{game_data.adpl} ) ( {game_data.vs}vs )'
|
||||
message += f'\n40L: {float(user_info.pb_sprint)/1000:.2f}s' if user_info.pb_sprint != '2147483647' else ''
|
||||
message += f'\nMarathon: {user_info.pb_marathon}' if user_info.pb_marathon != '0' else ''
|
||||
message += f'\nChallenge: {user_info.pb_challenge}' if user_info.pb_challenge != '0' else ''
|
||||
return UniMessage(message)
|
||||
|
||||
async def get_user(self) -> None:
|
||||
"""
|
||||
@@ -228,24 +251,3 @@ class Processor(ProcessorMeta):
|
||||
adpl=round((adpm / lpm), 2),
|
||||
vs=round((adpm / 60 * 100), 2),
|
||||
)
|
||||
|
||||
async def generate_message(self) -> str:
|
||||
"""生成消息"""
|
||||
user_info = (await self.get_user_info()).data
|
||||
message = f'用户 {user_info.name} ({user_info.teaid}) '
|
||||
if user_info.ranked_games == '0':
|
||||
message += '暂无段位统计数据'
|
||||
else:
|
||||
message += f', 段位分 {round(float(user_info.rating_now),2)}±{round(float(user_info.rd_now),2)} ({round(float(user_info.vol_now),2)}) '
|
||||
game_data = await self.get_game_data()
|
||||
if game_data is None:
|
||||
message += ', 暂无游戏数据'
|
||||
else:
|
||||
message += f', 最近 {game_data.num} 局数据'
|
||||
message += f"\nL'PM: {game_data.lpm} ( {game_data.pps} pps )"
|
||||
message += f'\nAPM: {game_data.apm} ( x{game_data.apl} )'
|
||||
message += f'\nADPM: {game_data.adpm} ( x{game_data.adpl} ) ( {game_data.vs}vs )'
|
||||
message += f'\n40L: {float(user_info.pb_sprint)/1000:.2f}s' if user_info.pb_sprint != '2147483647' else ''
|
||||
message += f'\nMarathon: {user_info.pb_marathon}' if user_info.pb_marathon != '0' else ''
|
||||
message += f'\nChallenge: {user_info.pb_challenge}' if user_info.pb_challenge != '0' else ''
|
||||
return message
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -77,21 +77,22 @@
|
||||
font-family: 'CabinetGrotesk-Variable';
|
||||
}
|
||||
|
||||
#account-box {
|
||||
.account-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#info-box {
|
||||
.info-box {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#user-info-box {
|
||||
.user-info-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 25px;
|
||||
gap: 10px;
|
||||
|
||||
@@ -103,14 +104,14 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#user-avatar {
|
||||
.user-avatar {
|
||||
width: 125px;
|
||||
height: 125px;
|
||||
filter: drop-shadow(0px 11px 23px rgba(0, 0, 0, 0.22));
|
||||
border-radius: 65px;
|
||||
}
|
||||
|
||||
#user-name {
|
||||
.user-name {
|
||||
font-weight: 800;
|
||||
font-size: 25px;
|
||||
line-height: 31px;
|
||||
@@ -118,7 +119,7 @@
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#user-sign {
|
||||
.user-sign {
|
||||
width: 225px;
|
||||
height: 66px;
|
||||
|
||||
@@ -132,7 +133,7 @@
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#game-info-box {
|
||||
.game-info-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -147,19 +148,19 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#game-type-box {
|
||||
.game-type-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#game-logo {
|
||||
.game-logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
#game-name {
|
||||
.game-name {
|
||||
font-weight: 800;
|
||||
font-size: 30px;
|
||||
line-height: 37px;
|
||||
@@ -167,18 +168,18 @@
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#game-info-dividing-line {
|
||||
.game-info-dividing-line {
|
||||
width: 225px;
|
||||
border: 1px solid #bababa;
|
||||
transform: rotate(0.25deg);
|
||||
}
|
||||
|
||||
#ranking-info-box {
|
||||
.ranking-info-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#ranking-title {
|
||||
.ranking-title {
|
||||
font-weight: 800;
|
||||
font-size: 25px;
|
||||
line-height: 31px;
|
||||
@@ -186,7 +187,7 @@
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#ranking {
|
||||
.ranking {
|
||||
font-weight: 400;
|
||||
font-size: 50px;
|
||||
line-height: 120%;
|
||||
@@ -194,7 +195,7 @@
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#rd {
|
||||
.rd {
|
||||
margin-top: -16px;
|
||||
|
||||
font-weight: 300;
|
||||
@@ -214,7 +215,7 @@
|
||||
background: linear-gradient(222.34deg, #525252 11.97%, #1d1916 89.73%);
|
||||
}
|
||||
|
||||
#TR-title {
|
||||
.TR-title {
|
||||
position: absolute;
|
||||
margin-left: 24px;
|
||||
margin-top: 19px;
|
||||
@@ -227,7 +228,7 @@
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
#rank-icon {
|
||||
.rank-icon {
|
||||
position: absolute;
|
||||
margin-left: 27px;
|
||||
margin-top: 90px;
|
||||
@@ -236,7 +237,7 @@
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#TR {
|
||||
.TR {
|
||||
position: absolute;
|
||||
margin-left: 24px;
|
||||
margin-top: 143px;
|
||||
@@ -248,14 +249,14 @@
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
#multiplayer-box {
|
||||
.multiplayer-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
#multiplayer-data-box {
|
||||
.multiplayer-data-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -268,84 +269,102 @@
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
#lpm-box {
|
||||
.lpm-box {
|
||||
background-image: url('../static/data/LPM.svg');
|
||||
}
|
||||
|
||||
#lpm-value {
|
||||
.lpm-value {
|
||||
color: #4d7d0f;
|
||||
}
|
||||
|
||||
#pps-value {
|
||||
.pps-value {
|
||||
color: #4d7d0f;
|
||||
}
|
||||
|
||||
#apm-box {
|
||||
.apm-box {
|
||||
background-image: url('../static/data/APM.svg');
|
||||
}
|
||||
|
||||
#apm-value {
|
||||
.apm-value {
|
||||
color: #b5530a;
|
||||
}
|
||||
|
||||
#apl-value {
|
||||
.apl-value {
|
||||
color: #b5530a;
|
||||
}
|
||||
|
||||
#adpm-box {
|
||||
.adpm-box {
|
||||
background-image: url('../static/data/ADPM.svg');
|
||||
}
|
||||
|
||||
#adpm-value {
|
||||
.adpm-value {
|
||||
color: #235db4;
|
||||
}
|
||||
|
||||
#vs-value {
|
||||
.vs-value {
|
||||
top: 62px;
|
||||
color: #4779c6;
|
||||
}
|
||||
|
||||
#adpl-value {
|
||||
.adpl-value {
|
||||
color: #4779c6;
|
||||
}
|
||||
|
||||
.radar-chart-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.radar-background {
|
||||
background: linear-gradient(222.34deg, #525252 11.97%, #1d1916 89.73%),
|
||||
linear-gradient(222.34deg, #4f9dff 11.97%, #2563ea 89.73%);
|
||||
}
|
||||
|
||||
#radar-chart {
|
||||
width: 275px;
|
||||
height: 275px;
|
||||
background: linear-gradient(222.34deg, #525252 11.97%, #1d1916 89.73%),
|
||||
linear-gradient(222.34deg, #4f9dff 11.97%, #2563ea 89.73%);
|
||||
}
|
||||
|
||||
#singleplayer-box {
|
||||
.radar-description {
|
||||
text-align: left;
|
||||
padding-left: 20px;
|
||||
padding-top: 15px;
|
||||
font-size: 12px;
|
||||
color: #fafafa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.singleplayer-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: space-between;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
#sprint-box {
|
||||
.sprint-box {
|
||||
background-image: url('../static/data/40L.svg');
|
||||
}
|
||||
|
||||
#blitz-box {
|
||||
.blitz-box {
|
||||
background-image: url('../static/data/Blitz.svg');
|
||||
}
|
||||
|
||||
#sprint-value {
|
||||
.sprint-value {
|
||||
color: #b42323;
|
||||
}
|
||||
|
||||
#blitz-value {
|
||||
.blitz-value {
|
||||
color: #8e23b4;
|
||||
}
|
||||
|
||||
#footer {
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
font-family: '26FGalaxySans-ObliqueVF';
|
||||
font-size: 32px;
|
||||
font-size: 30px;
|
||||
font-weight: 257;
|
||||
line-height: 120%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -8,75 +8,84 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main-content">
|
||||
<div class="main-content">
|
||||
<span class="big-title">Account&Rankings</span>
|
||||
<div id="account-box">
|
||||
<div id="info-box">
|
||||
<div class="account-box">
|
||||
<div class="info-box">
|
||||
<div class="flex-gap"></div>
|
||||
<div class="box-shadow box-rounded-corners" id="user-info-box">
|
||||
<img id="user-avatar" src="{{user_avatar}}" />
|
||||
<div id="user-name">{{user_name}}</div>
|
||||
<div id="user-sign">“{{user_sign}}”</div>
|
||||
<div class="box-shadow box-rounded-corners user-info-box">
|
||||
<div class="flex-gap"></div>
|
||||
<img class="user-avatar" src="{{user_avatar}}" />
|
||||
<div class="flex-gap"></div>
|
||||
<div class="user-name">{{user_name}}</div>
|
||||
<div class="flex-gap"></div>
|
||||
{% if user_sign is not none %}
|
||||
<div class="user-sign">“{{user_sign}}”</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex-gap"></div>
|
||||
<div class="box-shadow box-rounded-corners" id="game-info-box">
|
||||
<div id="game-type-box">
|
||||
<img id="game-logo" src="../../static/static/logo/{{game_type}}.svg" />
|
||||
<span id="game-name">{{game_type}}</span>
|
||||
<div class="box-shadow box-rounded-corners game-info-box">
|
||||
<div class="game-type-box">
|
||||
<img class="game-logo" src="../../static/static/logo/{{game_type}}.svg" />
|
||||
<span class="game-name">{{game_type}}</span>
|
||||
</div>
|
||||
<div id="game-info-dividing-line"></div>
|
||||
<div id="ranking-info-box">
|
||||
<span id="ranking-title">Ranking</span>
|
||||
<span id="ranking">{{ranking}}</span>
|
||||
<span id="rd">±{{rd}}</span>
|
||||
<div class="game-info-dividing-line"></div>
|
||||
<div class="ranking-info-box">
|
||||
<span class="ranking-title">Ranking</span>
|
||||
<span class="ranking">{{ranking}}</span>
|
||||
<span class="rd">±{{rd}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-gap"></div>
|
||||
</div>
|
||||
<div class="chart-shadow box-rounded-corners" id="TR-curve-chart">
|
||||
<span id="TR-title">Tetra Rating (TR)</span>
|
||||
<img id="rank-icon" src="../../static/static/rank/{{rank}}.svg" />
|
||||
<span id="TR" style="display: flex; align-items: flex-end"
|
||||
{# <div class="chart-shadow box-rounded-corners" id="TR-curve-chart">
|
||||
<span class="TR-title">Tetra Rating (TR)</span>
|
||||
<img class="rank-icon" src="../../static/static/rank/{{rank}}.svg" />
|
||||
<span class="TR" style="display: flex; align-items: flex-end"
|
||||
>{{TR}}
|
||||
<p style="font-size: 30px; font-weight: 400; line-height: 47px">(#{{global_rank}})</p>
|
||||
</span>
|
||||
</div>
|
||||
</div> #}
|
||||
</div>
|
||||
<span class="big-title">Multiplayer Stats</span>
|
||||
<div id="multiplayer-box">
|
||||
<div class="multiplayer-box">
|
||||
<div class="flex-gap"></div>
|
||||
<div id="multiplayer-data-box">
|
||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="lpm-box">
|
||||
<span class="big-data-value" id="lpm-value">{{lpm}}</span>
|
||||
<span class="small-data-value" id="pps-value">{{pps}} pps</span>
|
||||
<div class="multiplayer-data-box">
|
||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners lpm-box">
|
||||
<span class="big-data-value lpm-value">{{lpm}}</span>
|
||||
<span class="small-data-value pps-value">{{pps}} pps</span>
|
||||
</div>
|
||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="apm-box">
|
||||
<span class="big-data-value" id="apm-value">{{apm}}</span>
|
||||
<span class="small-data-value" id="apl-value">x{{apl}}</span>
|
||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners apm-box">
|
||||
<span class="big-data-value apm-value">{{apm}}</span>
|
||||
<span class="small-data-value apl-value">x{{apl}}</span>
|
||||
</div>
|
||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="adpm-box">
|
||||
<span class="big-data-value" id="adpm-value">{{adpm}}</span>
|
||||
<span class="small-data-value" id="adpl-value">x{{adpl}}</span>
|
||||
<span class="small-data-value" id="vs-value">{{vs}} vs</span>
|
||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners adpm-box">
|
||||
<span class="big-data-value adpm-value">{{adpm}}</span>
|
||||
<span class="small-data-value adpl-value">x{{adpl}}</span>
|
||||
<span class="small-data-value vs-value">{{vs}} vs</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-gap"></div>
|
||||
<div class="chart-shadow box-rounded-corners" id="radar-chart"></div>
|
||||
<div class="radar-chart-box">
|
||||
<div class="chart-shadow box-rounded-corners radar-background" id="radar-chart"></div>
|
||||
<div class="flex-gap"></div>
|
||||
<div class="chart-shadow box-rounded-corners small-data-box radar-background radar-description"><p style="font-size: 18px;display: inline;">tips: </p><br />DSPS 每秒挖掘<br />DSPP 每块挖掘<br />CI 奶酪指数<br />GE 垃圾利用率</div>
|
||||
</div>
|
||||
<div class="flex-gap"></div>
|
||||
</div>
|
||||
<span class="big-title">Singleplayer Stats</span>
|
||||
<div id="singleplayer-box">
|
||||
<div class="singleplayer-box">
|
||||
<div class="flex-gap"></div>
|
||||
<div class="small-data-box box-shadow box-rounded-corners" id="sprint-box">
|
||||
<span class="big-data-value" id="sprint-value">{{sprint}}</span>
|
||||
<div class="small-data-box box-shadow box-rounded-corners sprint-box">
|
||||
<span class="big-data-value sprint-value">{{sprint}}</span>
|
||||
</div>
|
||||
<div class="flex-gap"></div>
|
||||
<div class="small-data-box box-shadow box-rounded-corners" id="blitz-box">
|
||||
<span class="big-data-value" id="blitz-value">{{blitz}}</span>
|
||||
<div class="small-data-box box-shadow box-rounded-corners blitz-box">
|
||||
<span class="big-data-value blitz-value">{{blitz}}</span>
|
||||
</div>
|
||||
<div class="flex-gap"></div>
|
||||
</div>
|
||||
<div id="footer">Powered by<br />Nonebot2 x nonebot-plugin-tetris-stats</div>
|
||||
<div class="footer">Powered by<br />Nonebot2 x nonebot-plugin-tetris-stats</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -279,8 +288,8 @@
|
||||
indicator: [
|
||||
{ name: 'PPS' },
|
||||
{ name: 'APP', nameRotate: 60 },
|
||||
{ name: 'DSPP', nameRotate: -60 },
|
||||
{ name: 'OR' },
|
||||
{ name: 'DSPS', nameRotate: -60 },
|
||||
{ name: 'DSPP' },
|
||||
{ name: 'CI', nameRotate: 60 },
|
||||
{ name: 'GE', nameRotate: -60 },
|
||||
],
|
||||
@@ -341,7 +350,7 @@
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: [{{pps}}, {{app}}, {{dspp}}, {{OR}}, {{ci}}, {{ge}}],
|
||||
value: [{{pps}}, {{app}}, {{dsps}}, {{dspp}}, {{ci}}, {{ge}}],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ class BrowserManager:
|
||||
logger.error('安装/更新 playwright 浏览器失败')
|
||||
try:
|
||||
await cls._start_browser()
|
||||
except BaseException as e: # noqa: BLE001 不知道会有什么异常, 交给用户解决
|
||||
except BaseException as e: # 不知道会有什么异常, 交给用户解决
|
||||
raise ImportError(
|
||||
'playwright 启动失败, 请尝试在命令行运行 playwright install-deps firefox, 如果仍然启动失败, 请参考上面的报错👆'
|
||||
) from e
|
||||
|
||||
@@ -34,7 +34,7 @@ async def render(
|
||||
*,
|
||||
user_avatar: str,
|
||||
user_name: str,
|
||||
user_sign: str,
|
||||
user_sign: str | None,
|
||||
game_type: Literal['TETR.IO'],
|
||||
ranking: str | float,
|
||||
rd: str | float,
|
||||
@@ -56,8 +56,8 @@ async def render(
|
||||
value_min: int,
|
||||
offset: int,
|
||||
app: str | float,
|
||||
dsps: str | float,
|
||||
dspp: str | float,
|
||||
OR: str | float, # noqa: N803
|
||||
ci: str | float,
|
||||
ge: str | float,
|
||||
) -> str: ...
|
||||
|
||||
19
poetry.lock
generated
19
poetry.lock
generated
@@ -1141,13 +1141,13 @@ nonebot2 = ">=2.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-alconna"
|
||||
version = "0.45.2"
|
||||
version = "0.45.3"
|
||||
description = "Alconna Adapter for Nonebot"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "nonebot_plugin_alconna-0.45.2-py3-none-any.whl", hash = "sha256:38461346f52479c3133d9a789cca1d280a05834790dbadeecfc88639a1e326a2"},
|
||||
{file = "nonebot_plugin_alconna-0.45.2.tar.gz", hash = "sha256:a54bd294c0a829fcd344a3d3f5b9b040c1dcdbe739245557037cca019187d031"},
|
||||
{file = "nonebot_plugin_alconna-0.45.3-py3-none-any.whl", hash = "sha256:1e6ff5e99464ea2acad03df8b9fc48949b7d40a7c8d1976c53a934eafb8d3bf0"},
|
||||
{file = "nonebot_plugin_alconna-0.45.3.tar.gz", hash = "sha256:7667df82fdae02842b0fa28b39d61daf501f1af41d6fecf288fb8bb38a35ff9d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2061,6 +2061,17 @@ typing-extensions = ">=4.5,<5.0"
|
||||
[package.extras]
|
||||
test = ["beautifulsoup4 (>=4.8,<5.0)", "html5lib (==1.1)", "lxml (>=4.9)", "mypy (==1.9.*)", "pyright (>=1.1.289)", "pytest (>=7.0,<9)", "pytest-mypy-plugins (==1.11.1)", "tox (>=4.0,<5.0)", "typeguard (>=3.0,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "types-pillow"
|
||||
version = "10.2.0.20240423"
|
||||
description = "Typing stubs for Pillow"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "types-Pillow-10.2.0.20240423.tar.gz", hash = "sha256:696e68b9b6a58548fc307a8669830469237c5b11809ddf978ac77fafa79251cd"},
|
||||
{file = "types_Pillow-10.2.0.20240423-py3-none-any.whl", hash = "sha256:bd12923093b96c91d523efcdb66967a307f1a843bcfaf2d5a529146c10a9ced3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-pytz"
|
||||
version = "2024.1.0.20240417"
|
||||
@@ -2679,4 +2690,4 @@ cffi = ["cffi (>=1.11)"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "c568aeaa3fa7dc9049bb95fb3f682754aff0be497fa7e097c51d1becb90a84a5"
|
||||
content-hash = "b850c0a22d448d72056fadd2f5c0a5b3c72c44a081e6a4b6a4c70c90177b83e3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = 'nonebot-plugin-tetris-stats'
|
||||
version = '1.0.0'
|
||||
version = '1.0.1'
|
||||
description = '一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件'
|
||||
authors = ['scdhh <wallfjjd@gmail.com>']
|
||||
readme = 'README.md'
|
||||
@@ -39,6 +39,7 @@ nonebot-adapter-onebot = "^2.4.1"
|
||||
nonebot-adapter-satori = "^0.11.4"
|
||||
nonebot-adapter-kaiheila = "^0.3.4"
|
||||
nonebot-adapter-discord = "^0.1.3"
|
||||
types-pillow = "^10.2.0.20240423"
|
||||
|
||||
[tool.poetry.group.debug.dependencies]
|
||||
objprint = '^0.2.2'
|
||||
@@ -49,6 +50,10 @@ requires = ['poetry-core>=1.0.0']
|
||||
build-backend = 'poetry.core.masonry.api'
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
'F', # pyflakes
|
||||
'E', # pycodestyle errors
|
||||
@@ -89,14 +94,12 @@ ignore = [
|
||||
'ANN202', # 向 NoneBot 注册的函数
|
||||
'TRY003',
|
||||
]
|
||||
line-length = 120
|
||||
target-version = "py310"
|
||||
flake8-quotes = { inline-quotes = 'single', multiline-quotes = 'double' }
|
||||
|
||||
[tool.ruff.flake8-annotations]
|
||||
[tool.ruff.lint.flake8-annotations]
|
||||
mypy-init-return = true
|
||||
|
||||
[tool.ruff.flake8-builtins]
|
||||
[tool.ruff.lint.flake8-builtins]
|
||||
builtins-ignorelist = ["id"]
|
||||
|
||||
[tool.ruff.format]
|
||||
|
||||
Reference in New Issue
Block a user