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 pydantic import BaseModel, ValidationError
|
||||||
from sqlalchemy import JSON, DateTime, Dialect, PickleType, String, TypeDecorator
|
from sqlalchemy import JSON, DateTime, Dialect, PickleType, String, TypeDecorator
|
||||||
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from ..game_data_processor.schemas import BaseProcessedData, BaseUser
|
from ..game_data_processor.schemas import BaseProcessedData, BaseUser
|
||||||
from ..utils.typing import CommandType, GameType
|
from ..utils.typing import CommandType, GameType
|
||||||
@@ -16,17 +17,20 @@ from ..utils.typing import CommandType, GameType
|
|||||||
class PydanticType(TypeDecorator):
|
class PydanticType(TypeDecorator):
|
||||||
impl = JSON
|
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
|
self.get_model = get_model
|
||||||
super().__init__(*args, **kwargs)
|
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
|
# 将 Pydantic 模型实例转换为 JSON
|
||||||
if isinstance(value, tuple(self.get_model())):
|
if isinstance(value, tuple(self.get_model())):
|
||||||
return value.json() # type: ignore[union-attr]
|
return value.json() # type: ignore[union-attr]
|
||||||
raise TypeError
|
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 模型实例
|
# 将 JSON 转换回 Pydantic 模型实例
|
||||||
if isinstance(value, str | bytes):
|
if isinstance(value, str | bytes):
|
||||||
for i in self.get_model():
|
for i in self.get_model():
|
||||||
|
|||||||
@@ -55,15 +55,10 @@ class Processor(ABC):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def handle_query(self) -> str:
|
async def handle_query(self) -> UniMessage:
|
||||||
"""处理查询消息"""
|
"""处理查询消息"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def generate_message(self) -> str:
|
|
||||||
"""生成消息"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
def __del__(self) -> None:
|
||||||
finish_time = datetime.now(tz=UTC)
|
finish_time = datetime.now(tz=UTC)
|
||||||
if Recorder.is_error_event(self.event_id):
|
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.adapters import Bot, Event
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot_plugin_alconna import At, on_alconna
|
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_orm import get_session
|
||||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
|
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select
|
||||||
@@ -117,10 +118,11 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
|||||||
command_args=[],
|
command_args=[],
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await matcher.finish(message + await proc.handle_query())
|
await (UniMessage(message) + await proc.handle_query()).send()
|
||||||
except NeedCatchError as e:
|
except NeedCatchError as e:
|
||||||
await matcher.send(str(e))
|
await matcher.send(str(e))
|
||||||
raise HandleNotFinishedError from e
|
raise HandleNotFinishedError from e
|
||||||
|
await matcher.finish()
|
||||||
|
|
||||||
|
|
||||||
@alc.assign('query')
|
@alc.assign('query')
|
||||||
@@ -131,10 +133,11 @@ async def _(event: Event, matcher: Matcher, account: User):
|
|||||||
command_args=[],
|
command_args=[],
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await matcher.finish(await proc.handle_query())
|
await (await proc.handle_query()).send()
|
||||||
except NeedCatchError as e:
|
except NeedCatchError as e:
|
||||||
await matcher.send(str(e))
|
await matcher.send(str(e))
|
||||||
raise HandleNotFinishedError from e
|
raise HandleNotFinishedError from e
|
||||||
|
await matcher.finish()
|
||||||
|
|
||||||
|
|
||||||
@alc.assign('rank')
|
@alc.assign('rank')
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from asyncio import gather
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timedelta, timezone
|
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_orm import get_session
|
||||||
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
|
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
from typing_extensions import override
|
||||||
from zstandard import ZstdCompressor
|
from zstandard import ZstdCompressor
|
||||||
|
|
||||||
from ...db import BindStatus, create_or_update_bind
|
from ...db import BindStatus, create_or_update_bind
|
||||||
@@ -32,15 +34,13 @@ from .. import Processor as ProcessorMeta
|
|||||||
from .cache import Cache
|
from .cache import Cache
|
||||||
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
|
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
|
||||||
from .model import IORank
|
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 LeagueAll
|
||||||
from .schemas.league_all import ValidUser as LeagueAllUser
|
from .schemas.league_all import ValidUser as LeagueAllUser
|
||||||
from .schemas.response import ProcessedData, RawResponse
|
from .schemas.response import ProcessedData, RawResponse
|
||||||
from .schemas.user import User
|
from .schemas.user import User
|
||||||
from .schemas.user_info import FailedModel as InfoFailed
|
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, RatedLeague, UserInfo
|
||||||
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, UserInfo
|
|
||||||
from .schemas.user_info import SuccessModel as InfoSuccess
|
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 SoloRecord, UserRecords
|
||||||
from .schemas.user_records import SuccessModel as RecordsSuccess
|
from .schemas.user_records import SuccessModel as RecordsSuccess
|
||||||
from .typing import Rank
|
from .typing import Rank
|
||||||
@@ -63,15 +63,18 @@ class Processor(ProcessorMeta):
|
|||||||
raw_response: RawResponse
|
raw_response: RawResponse
|
||||||
processed_data: ProcessedData
|
processed_data: ProcessedData
|
||||||
|
|
||||||
|
@override
|
||||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||||
super().__init__(event_id, user, command_args)
|
super().__init__(event_id, user, command_args)
|
||||||
self.raw_response = RawResponse()
|
self.raw_response = RawResponse()
|
||||||
self.processed_data = ProcessedData()
|
self.processed_data = ProcessedData()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@override
|
||||||
def game_platform(self) -> Literal['IO']:
|
def game_platform(self) -> Literal['IO']:
|
||||||
return GAME_TYPE
|
return GAME_TYPE
|
||||||
|
|
||||||
|
@override
|
||||||
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
|
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
|
||||||
"""处理绑定消息"""
|
"""处理绑定消息"""
|
||||||
self.command_type = 'bind'
|
self.command_type = 'bind'
|
||||||
@@ -110,11 +113,105 @@ class Processor(ProcessorMeta):
|
|||||||
)
|
)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
async def handle_query(self) -> str:
|
@override
|
||||||
|
async def handle_query(self) -> UniMessage:
|
||||||
"""处理查询消息"""
|
"""处理查询消息"""
|
||||||
self.command_type = 'query'
|
self.command_type = 'query'
|
||||||
await self.get_user()
|
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:
|
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}'])
|
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]
|
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}')
|
raise RequestError(f'用户信息请求错误:\n{user_info.error}')
|
||||||
self.processed_data.user_info = user_info
|
self.processed_data.user_info = user_info
|
||||||
return self.processed_data.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'])
|
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]
|
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}')
|
raise RequestError(f'用户Solo数据请求错误:\n{user_records.error}')
|
||||||
self.processed_data.user_records = user_records
|
self.processed_data.user_records = user_records
|
||||||
return self.processed_data.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)
|
@scheduler.scheduled_job('cron', hour='0,6,12,18', minute=0)
|
||||||
@retry(exception_type=RequestError, delay=timedelta(minutes=15))
|
@retry(exception_type=RequestError, delay=timedelta(minutes=15))
|
||||||
@@ -197,7 +254,7 @@ async def get_io_rank_data() -> None:
|
|||||||
LeagueAll, # type: ignore[arg-type]
|
LeagueAll, # type: ignore[arg-type]
|
||||||
(data := await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))),
|
(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}')
|
raise RequestError(f'排行榜数据请求错误:\n{league_all.error}')
|
||||||
|
|
||||||
def pps(user: LeagueAllUser) -> float:
|
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.adapters import Bot, Event
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot_plugin_alconna import At, on_alconna
|
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_orm import get_session
|
||||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo # type: ignore[import-untyped]
|
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=[],
|
command_args=[],
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await matcher.finish(message + await proc.handle_query())
|
await (UniMessage(message) + await proc.handle_query()).send()
|
||||||
except NeedCatchError as e:
|
except NeedCatchError as e:
|
||||||
await matcher.send(str(e))
|
await matcher.send(str(e))
|
||||||
raise HandleNotFinishedError from e
|
raise HandleNotFinishedError from e
|
||||||
|
await matcher.finish()
|
||||||
|
|
||||||
|
|
||||||
@alc.assign('query')
|
@alc.assign('query')
|
||||||
@@ -125,10 +127,11 @@ async def _(event: Event, matcher: Matcher, account: User):
|
|||||||
command_args=[],
|
command_args=[],
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await matcher.finish(await proc.handle_query())
|
await (await proc.handle_query()).send()
|
||||||
except NeedCatchError as e:
|
except NeedCatchError as e:
|
||||||
await matcher.send(str(e))
|
await matcher.send(str(e))
|
||||||
raise HandleNotFinishedError from e
|
raise HandleNotFinishedError from e
|
||||||
|
await matcher.finish()
|
||||||
|
|
||||||
|
|
||||||
add_default_handlers(alc)
|
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_orm import get_session
|
||||||
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
|
from nonebot_plugin_userinfo import UserInfo # type: ignore[import-untyped]
|
||||||
from pandas import read_html
|
from pandas import read_html
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from ...db import BindStatus, create_or_update_bind
|
from ...db import BindStatus, create_or_update_bind
|
||||||
from ...utils.avatar import get_avatar
|
from ...utils.avatar import get_avatar
|
||||||
@@ -30,6 +31,7 @@ class User(BaseUser):
|
|||||||
name: str
|
name: str
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@override
|
||||||
def unique_identifier(self) -> str:
|
def unique_identifier(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@@ -57,15 +59,18 @@ class Processor(ProcessorMeta):
|
|||||||
raw_response: RawResponse
|
raw_response: RawResponse
|
||||||
processed_data: ProcessedData
|
processed_data: ProcessedData
|
||||||
|
|
||||||
|
@override
|
||||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||||
super().__init__(event_id, user, command_args)
|
super().__init__(event_id, user, command_args)
|
||||||
self.raw_response = RawResponse()
|
self.raw_response = RawResponse()
|
||||||
self.processed_data = ProcessedData()
|
self.processed_data = ProcessedData()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@override
|
||||||
def game_platform(self) -> Literal['TOP']:
|
def game_platform(self) -> Literal['TOP']:
|
||||||
return GAME_TYPE
|
return GAME_TYPE
|
||||||
|
|
||||||
|
@override
|
||||||
async def handle_bind(self, platform: str, account: str, bot_info: UserInfo, user_info: UserInfo) -> UniMessage:
|
async def handle_bind(self, platform: str, account: str, bot_info: UserInfo, user_info: UserInfo) -> UniMessage:
|
||||||
"""处理绑定消息"""
|
"""处理绑定消息"""
|
||||||
self.command_type = 'bind'
|
self.command_type = 'bind'
|
||||||
@@ -99,11 +104,26 @@ class Processor(ProcessorMeta):
|
|||||||
)
|
)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
async def handle_query(self) -> str:
|
@override
|
||||||
|
async def handle_query(self) -> UniMessage:
|
||||||
"""处理查询消息"""
|
"""处理查询消息"""
|
||||||
self.command_type = 'query'
|
self.command_type = 'query'
|
||||||
await self.check_user()
|
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:
|
async def get_user_profile(self) -> str:
|
||||||
"""获取用户信息"""
|
"""获取用户信息"""
|
||||||
@@ -140,21 +160,3 @@ class Processor(ProcessorMeta):
|
|||||||
dataframe = read_html(table, encoding='utf-8', header=0)[0]
|
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
|
total = Data(lpm=dataframe['lpm'].mean(), apm=dataframe['apm'].mean()) if len(dataframe) != 0 else None
|
||||||
return GameData(day=day, total=total)
|
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.adapters import Bot, Event
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot_plugin_alconna import At, on_alconna
|
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_orm import get_session
|
||||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo # type: ignore[import-untyped]
|
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:
|
async def finish_special_query(matcher: Matcher, proc: Processor) -> NoReturn:
|
||||||
try:
|
try:
|
||||||
await matcher.finish(await proc.handle_query())
|
await (await proc.handle_query()).send()
|
||||||
except NeedCatchError as e:
|
except NeedCatchError as e:
|
||||||
if isinstance(e, RequestError) and '未找到此用户' in e.message:
|
if isinstance(e, RequestError) and '未找到此用户' in e.message:
|
||||||
matcher.skip()
|
matcher.skip()
|
||||||
await matcher.send(str(e))
|
await matcher.send(str(e))
|
||||||
raise HandleNotFinishedError from e
|
raise HandleNotFinishedError from e
|
||||||
|
await matcher.finish()
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -165,10 +167,11 @@ async def _(bot: Bot, event: Event, matcher: Matcher, target: At | Me):
|
|||||||
command_args=[],
|
command_args=[],
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await matcher.finish(message + await proc.handle_query())
|
await (UniMessage(message) + await proc.handle_query()).send()
|
||||||
except NeedCatchError as e:
|
except NeedCatchError as e:
|
||||||
await matcher.send(str(e))
|
await matcher.send(str(e))
|
||||||
raise HandleNotFinishedError from e
|
raise HandleNotFinishedError from e
|
||||||
|
await matcher.finish()
|
||||||
|
|
||||||
|
|
||||||
@alc.assign('query')
|
@alc.assign('query')
|
||||||
@@ -179,10 +182,11 @@ async def _(event: Event, matcher: Matcher, account: User):
|
|||||||
command_args=[],
|
command_args=[],
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await matcher.finish(await proc.handle_query())
|
await (await proc.handle_query()).send()
|
||||||
except NeedCatchError as e:
|
except NeedCatchError as e:
|
||||||
await matcher.send(str(e))
|
await matcher.send(str(e))
|
||||||
raise HandleNotFinishedError from e
|
raise HandleNotFinishedError from e
|
||||||
|
await matcher.finish()
|
||||||
|
|
||||||
|
|
||||||
add_default_handlers(alc)
|
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_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_userinfo import UserInfo as NBUserInfo # type: ignore[import-untyped]
|
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 ...db import BindStatus, create_or_update_bind
|
||||||
from ...utils.avatar import get_avatar
|
from ...utils.avatar import get_avatar
|
||||||
@@ -32,6 +33,7 @@ class User(BaseUser):
|
|||||||
name: str | None = None
|
name: str | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@override
|
||||||
def unique_identifier(self) -> str:
|
def unique_identifier(self) -> str:
|
||||||
if self.teaid is None:
|
if self.teaid is None:
|
||||||
raise ValueError('不完整的User!')
|
raise ValueError('不完整的User!')
|
||||||
@@ -70,15 +72,18 @@ class Processor(ProcessorMeta):
|
|||||||
raw_response: RawResponse
|
raw_response: RawResponse
|
||||||
processed_data: ProcessedData
|
processed_data: ProcessedData
|
||||||
|
|
||||||
|
@override
|
||||||
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
def __init__(self, event_id: int, user: User, command_args: list[str]) -> None:
|
||||||
super().__init__(event_id, user, command_args)
|
super().__init__(event_id, user, command_args)
|
||||||
self.raw_response = RawResponse(user_profile={})
|
self.raw_response = RawResponse(user_profile={})
|
||||||
self.processed_data = ProcessedData(user_profile={})
|
self.processed_data = ProcessedData(user_profile={})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@override
|
||||||
def game_platform(self) -> Literal['TOS']:
|
def game_platform(self) -> Literal['TOS']:
|
||||||
return GAME_TYPE
|
return GAME_TYPE
|
||||||
|
|
||||||
|
@override
|
||||||
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
|
async def handle_bind(self, platform: str, account: str, bot_info: NBUserInfo) -> UniMessage:
|
||||||
"""处理绑定消息"""
|
"""处理绑定消息"""
|
||||||
self.command_type = 'bind'
|
self.command_type = 'bind'
|
||||||
@@ -113,11 +118,29 @@ class Processor(ProcessorMeta):
|
|||||||
)
|
)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
async def handle_query(self) -> str:
|
@override
|
||||||
|
async def handle_query(self) -> UniMessage:
|
||||||
"""处理查询消息"""
|
"""处理查询消息"""
|
||||||
self.command_type = 'query'
|
self.command_type = 'query'
|
||||||
await self.get_user()
|
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:
|
async def get_user(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -228,24 +251,3 @@ class Processor(ProcessorMeta):
|
|||||||
adpl=round((adpm / lpm), 2),
|
adpl=round((adpm / lpm), 2),
|
||||||
vs=round((adpm / 60 * 100), 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;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-content {
|
.main-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
@@ -77,21 +77,22 @@
|
|||||||
font-family: 'CabinetGrotesk-Variable';
|
font-family: 'CabinetGrotesk-Variable';
|
||||||
}
|
}
|
||||||
|
|
||||||
#account-box {
|
.account-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#info-box {
|
.info-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-info-box {
|
.user-info-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
@@ -103,14 +104,14 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-avatar {
|
.user-avatar {
|
||||||
width: 125px;
|
width: 125px;
|
||||||
height: 125px;
|
height: 125px;
|
||||||
filter: drop-shadow(0px 11px 23px rgba(0, 0, 0, 0.22));
|
filter: drop-shadow(0px 11px 23px rgba(0, 0, 0, 0.22));
|
||||||
border-radius: 65px;
|
border-radius: 65px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-name {
|
.user-name {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
line-height: 31px;
|
line-height: 31px;
|
||||||
@@ -118,7 +119,7 @@
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-sign {
|
.user-sign {
|
||||||
width: 225px;
|
width: 225px;
|
||||||
height: 66px;
|
height: 66px;
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-info-box {
|
.game-info-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@@ -147,19 +148,19 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-type-box {
|
.game-type-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-logo {
|
.game-logo {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-name {
|
.game-name {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
line-height: 37px;
|
line-height: 37px;
|
||||||
@@ -167,18 +168,18 @@
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#game-info-dividing-line {
|
.game-info-dividing-line {
|
||||||
width: 225px;
|
width: 225px;
|
||||||
border: 1px solid #bababa;
|
border: 1px solid #bababa;
|
||||||
transform: rotate(0.25deg);
|
transform: rotate(0.25deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ranking-info-box {
|
.ranking-info-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ranking-title {
|
.ranking-title {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
line-height: 31px;
|
line-height: 31px;
|
||||||
@@ -186,7 +187,7 @@
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ranking {
|
.ranking {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 50px;
|
font-size: 50px;
|
||||||
line-height: 120%;
|
line-height: 120%;
|
||||||
@@ -194,7 +195,7 @@
|
|||||||
color: #000000;
|
color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rd {
|
.rd {
|
||||||
margin-top: -16px;
|
margin-top: -16px;
|
||||||
|
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@@ -214,7 +215,7 @@
|
|||||||
background: linear-gradient(222.34deg, #525252 11.97%, #1d1916 89.73%);
|
background: linear-gradient(222.34deg, #525252 11.97%, #1d1916 89.73%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#TR-title {
|
.TR-title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
margin-top: 19px;
|
margin-top: 19px;
|
||||||
@@ -227,7 +228,7 @@
|
|||||||
color: #fafafa;
|
color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rank-icon {
|
.rank-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 27px;
|
margin-left: 27px;
|
||||||
margin-top: 90px;
|
margin-top: 90px;
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#TR {
|
.TR {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 24px;
|
margin-left: 24px;
|
||||||
margin-top: 143px;
|
margin-top: 143px;
|
||||||
@@ -248,14 +249,14 @@
|
|||||||
color: #fafafa;
|
color: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
#multiplayer-box {
|
.multiplayer-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#multiplayer-data-box {
|
.multiplayer-data-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -268,84 +269,102 @@
|
|||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#lpm-box {
|
.lpm-box {
|
||||||
background-image: url('../static/data/LPM.svg');
|
background-image: url('../static/data/LPM.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
#lpm-value {
|
.lpm-value {
|
||||||
color: #4d7d0f;
|
color: #4d7d0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pps-value {
|
.pps-value {
|
||||||
color: #4d7d0f;
|
color: #4d7d0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
#apm-box {
|
.apm-box {
|
||||||
background-image: url('../static/data/APM.svg');
|
background-image: url('../static/data/APM.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
#apm-value {
|
.apm-value {
|
||||||
color: #b5530a;
|
color: #b5530a;
|
||||||
}
|
}
|
||||||
|
|
||||||
#apl-value {
|
.apl-value {
|
||||||
color: #b5530a;
|
color: #b5530a;
|
||||||
}
|
}
|
||||||
|
|
||||||
#adpm-box {
|
.adpm-box {
|
||||||
background-image: url('../static/data/ADPM.svg');
|
background-image: url('../static/data/ADPM.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
#adpm-value {
|
.adpm-value {
|
||||||
color: #235db4;
|
color: #235db4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#vs-value {
|
.vs-value {
|
||||||
top: 62px;
|
top: 62px;
|
||||||
color: #4779c6;
|
color: #4779c6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#adpl-value {
|
.adpl-value {
|
||||||
color: #4779c6;
|
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 {
|
#radar-chart {
|
||||||
width: 275px;
|
width: 275px;
|
||||||
height: 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;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-content: space-between;
|
align-content: space-between;
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sprint-box {
|
.sprint-box {
|
||||||
background-image: url('../static/data/40L.svg');
|
background-image: url('../static/data/40L.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
#blitz-box {
|
.blitz-box {
|
||||||
background-image: url('../static/data/Blitz.svg');
|
background-image: url('../static/data/Blitz.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
#sprint-value {
|
.sprint-value {
|
||||||
color: #b42323;
|
color: #b42323;
|
||||||
}
|
}
|
||||||
|
|
||||||
#blitz-value {
|
.blitz-value {
|
||||||
color: #8e23b4;
|
color: #8e23b4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer {
|
.footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
font-family: '26FGalaxySans-ObliqueVF';
|
font-family: '26FGalaxySans-ObliqueVF';
|
||||||
font-size: 32px;
|
font-size: 30px;
|
||||||
font-weight: 257;
|
font-weight: 257;
|
||||||
|
line-height: 120%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,75 +8,84 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="main-content">
|
<div class="main-content">
|
||||||
<span class="big-title">Account&Rankings</span>
|
<span class="big-title">Account&Rankings</span>
|
||||||
<div id="account-box">
|
<div class="account-box">
|
||||||
<div id="info-box">
|
<div class="info-box">
|
||||||
<div class="flex-gap"></div>
|
<div class="flex-gap"></div>
|
||||||
<div class="box-shadow box-rounded-corners" id="user-info-box">
|
<div class="box-shadow box-rounded-corners user-info-box">
|
||||||
<img id="user-avatar" src="{{user_avatar}}" />
|
<div class="flex-gap"></div>
|
||||||
<div id="user-name">{{user_name}}</div>
|
<img class="user-avatar" src="{{user_avatar}}" />
|
||||||
<div id="user-sign">“{{user_sign}}”</div>
|
<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>
|
||||||
<div class="flex-gap"></div>
|
<div class="flex-gap"></div>
|
||||||
<div class="box-shadow box-rounded-corners" id="game-info-box">
|
<div class="box-shadow box-rounded-corners game-info-box">
|
||||||
<div id="game-type-box">
|
<div class="game-type-box">
|
||||||
<img id="game-logo" src="../../static/static/logo/{{game_type}}.svg" />
|
<img class="game-logo" src="../../static/static/logo/{{game_type}}.svg" />
|
||||||
<span id="game-name">{{game_type}}</span>
|
<span class="game-name">{{game_type}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="game-info-dividing-line"></div>
|
<div class="game-info-dividing-line"></div>
|
||||||
<div id="ranking-info-box">
|
<div class="ranking-info-box">
|
||||||
<span id="ranking-title">Ranking</span>
|
<span class="ranking-title">Ranking</span>
|
||||||
<span id="ranking">{{ranking}}</span>
|
<span class="ranking">{{ranking}}</span>
|
||||||
<span id="rd">±{{rd}}</span>
|
<span class="rd">±{{rd}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-gap"></div>
|
<div class="flex-gap"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-shadow box-rounded-corners" id="TR-curve-chart">
|
{# <div class="chart-shadow box-rounded-corners" id="TR-curve-chart">
|
||||||
<span id="TR-title">Tetra Rating (TR)</span>
|
<span class="TR-title">Tetra Rating (TR)</span>
|
||||||
<img id="rank-icon" src="../../static/static/rank/{{rank}}.svg" />
|
<img class="rank-icon" src="../../static/static/rank/{{rank}}.svg" />
|
||||||
<span id="TR" style="display: flex; align-items: flex-end"
|
<span class="TR" style="display: flex; align-items: flex-end"
|
||||||
>{{TR}}
|
>{{TR}}
|
||||||
<p style="font-size: 30px; font-weight: 400; line-height: 47px">(#{{global_rank}})</p>
|
<p style="font-size: 30px; font-weight: 400; line-height: 47px">(#{{global_rank}})</p>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div> #}
|
||||||
</div>
|
</div>
|
||||||
<span class="big-title">Multiplayer Stats</span>
|
<span class="big-title">Multiplayer Stats</span>
|
||||||
<div id="multiplayer-box">
|
<div class="multiplayer-box">
|
||||||
<div class="flex-gap"></div>
|
<div class="flex-gap"></div>
|
||||||
<div id="multiplayer-data-box">
|
<div class="multiplayer-data-box">
|
||||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="lpm-box">
|
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners lpm-box">
|
||||||
<span class="big-data-value" id="lpm-value">{{lpm}}</span>
|
<span class="big-data-value lpm-value">{{lpm}}</span>
|
||||||
<span class="small-data-value" id="pps-value">{{pps}} pps</span>
|
<span class="small-data-value pps-value">{{pps}} pps</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="apm-box">
|
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners apm-box">
|
||||||
<span class="big-data-value" id="apm-value">{{apm}}</span>
|
<span class="big-data-value apm-value">{{apm}}</span>
|
||||||
<span class="small-data-value" id="apl-value">x{{apl}}</span>
|
<span class="small-data-value apl-value">x{{apl}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners" id="adpm-box">
|
<div class="multiplayer-data small-data-box box-shadow box-rounded-corners adpm-box">
|
||||||
<span class="big-data-value" id="adpm-value">{{adpm}}</span>
|
<span class="big-data-value adpm-value">{{adpm}}</span>
|
||||||
<span class="small-data-value" id="adpl-value">x{{adpl}}</span>
|
<span class="small-data-value adpl-value">x{{adpl}}</span>
|
||||||
<span class="small-data-value" id="vs-value">{{vs}} vs</span>
|
<span class="small-data-value vs-value">{{vs}} vs</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-gap"></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 class="flex-gap"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="big-title">Singleplayer Stats</span>
|
<span class="big-title">Singleplayer Stats</span>
|
||||||
<div id="singleplayer-box">
|
<div class="singleplayer-box">
|
||||||
<div class="flex-gap"></div>
|
<div class="flex-gap"></div>
|
||||||
<div class="small-data-box box-shadow box-rounded-corners" id="sprint-box">
|
<div class="small-data-box box-shadow box-rounded-corners sprint-box">
|
||||||
<span class="big-data-value" id="sprint-value">{{sprint}}</span>
|
<span class="big-data-value sprint-value">{{sprint}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-gap"></div>
|
<div class="flex-gap"></div>
|
||||||
<div class="small-data-box box-shadow box-rounded-corners" id="blitz-box">
|
<div class="small-data-box box-shadow box-rounded-corners blitz-box">
|
||||||
<span class="big-data-value" id="blitz-value">{{blitz}}</span>
|
<span class="big-data-value blitz-value">{{blitz}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-gap"></div>
|
<div class="flex-gap"></div>
|
||||||
</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>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
@@ -279,8 +288,8 @@
|
|||||||
indicator: [
|
indicator: [
|
||||||
{ name: 'PPS' },
|
{ name: 'PPS' },
|
||||||
{ name: 'APP', nameRotate: 60 },
|
{ name: 'APP', nameRotate: 60 },
|
||||||
{ name: 'DSPP', nameRotate: -60 },
|
{ name: 'DSPS', nameRotate: -60 },
|
||||||
{ name: 'OR' },
|
{ name: 'DSPP' },
|
||||||
{ name: 'CI', nameRotate: 60 },
|
{ name: 'CI', nameRotate: 60 },
|
||||||
{ name: 'GE', nameRotate: -60 },
|
{ name: 'GE', nameRotate: -60 },
|
||||||
],
|
],
|
||||||
@@ -341,7 +350,7 @@
|
|||||||
},
|
},
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
value: [{{pps}}, {{app}}, {{dspp}}, {{OR}}, {{ci}}, {{ge}}],
|
value: [{{pps}}, {{app}}, {{dsps}}, {{dspp}}, {{ci}}, {{ge}}],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class BrowserManager:
|
|||||||
logger.error('安装/更新 playwright 浏览器失败')
|
logger.error('安装/更新 playwright 浏览器失败')
|
||||||
try:
|
try:
|
||||||
await cls._start_browser()
|
await cls._start_browser()
|
||||||
except BaseException as e: # noqa: BLE001 不知道会有什么异常, 交给用户解决
|
except BaseException as e: # 不知道会有什么异常, 交给用户解决
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
'playwright 启动失败, 请尝试在命令行运行 playwright install-deps firefox, 如果仍然启动失败, 请参考上面的报错👆'
|
'playwright 启动失败, 请尝试在命令行运行 playwright install-deps firefox, 如果仍然启动失败, 请参考上面的报错👆'
|
||||||
) from e
|
) from e
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async def render(
|
|||||||
*,
|
*,
|
||||||
user_avatar: str,
|
user_avatar: str,
|
||||||
user_name: str,
|
user_name: str,
|
||||||
user_sign: str,
|
user_sign: str | None,
|
||||||
game_type: Literal['TETR.IO'],
|
game_type: Literal['TETR.IO'],
|
||||||
ranking: str | float,
|
ranking: str | float,
|
||||||
rd: str | float,
|
rd: str | float,
|
||||||
@@ -56,8 +56,8 @@ async def render(
|
|||||||
value_min: int,
|
value_min: int,
|
||||||
offset: int,
|
offset: int,
|
||||||
app: str | float,
|
app: str | float,
|
||||||
|
dsps: str | float,
|
||||||
dspp: str | float,
|
dspp: str | float,
|
||||||
OR: str | float, # noqa: N803
|
|
||||||
ci: str | float,
|
ci: str | float,
|
||||||
ge: str | float,
|
ge: str | float,
|
||||||
) -> str: ...
|
) -> str: ...
|
||||||
|
|||||||
19
poetry.lock
generated
19
poetry.lock
generated
@@ -1141,13 +1141,13 @@ nonebot2 = ">=2.2.0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nonebot-plugin-alconna"
|
name = "nonebot-plugin-alconna"
|
||||||
version = "0.45.2"
|
version = "0.45.3"
|
||||||
description = "Alconna Adapter for Nonebot"
|
description = "Alconna Adapter for Nonebot"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "nonebot_plugin_alconna-0.45.2-py3-none-any.whl", hash = "sha256:38461346f52479c3133d9a789cca1d280a05834790dbadeecfc88639a1e326a2"},
|
{file = "nonebot_plugin_alconna-0.45.3-py3-none-any.whl", hash = "sha256:1e6ff5e99464ea2acad03df8b9fc48949b7d40a7c8d1976c53a934eafb8d3bf0"},
|
||||||
{file = "nonebot_plugin_alconna-0.45.2.tar.gz", hash = "sha256:a54bd294c0a829fcd344a3d3f5b9b040c1dcdbe739245557037cca019187d031"},
|
{file = "nonebot_plugin_alconna-0.45.3.tar.gz", hash = "sha256:7667df82fdae02842b0fa28b39d61daf501f1af41d6fecf288fb8bb38a35ff9d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@@ -2061,6 +2061,17 @@ typing-extensions = ">=4.5,<5.0"
|
|||||||
[package.extras]
|
[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)"]
|
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]]
|
[[package]]
|
||||||
name = "types-pytz"
|
name = "types-pytz"
|
||||||
version = "2024.1.0.20240417"
|
version = "2024.1.0.20240417"
|
||||||
@@ -2679,4 +2690,4 @@ cffi = ["cffi (>=1.11)"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "c568aeaa3fa7dc9049bb95fb3f682754aff0be497fa7e097c51d1becb90a84a5"
|
content-hash = "b850c0a22d448d72056fadd2f5c0a5b3c72c44a081e6a4b6a4c70c90177b83e3"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = 'nonebot-plugin-tetris-stats'
|
name = 'nonebot-plugin-tetris-stats'
|
||||||
version = '1.0.0'
|
version = '1.0.1'
|
||||||
description = '一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件'
|
description = '一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件'
|
||||||
authors = ['scdhh <wallfjjd@gmail.com>']
|
authors = ['scdhh <wallfjjd@gmail.com>']
|
||||||
readme = 'README.md'
|
readme = 'README.md'
|
||||||
@@ -39,6 +39,7 @@ nonebot-adapter-onebot = "^2.4.1"
|
|||||||
nonebot-adapter-satori = "^0.11.4"
|
nonebot-adapter-satori = "^0.11.4"
|
||||||
nonebot-adapter-kaiheila = "^0.3.4"
|
nonebot-adapter-kaiheila = "^0.3.4"
|
||||||
nonebot-adapter-discord = "^0.1.3"
|
nonebot-adapter-discord = "^0.1.3"
|
||||||
|
types-pillow = "^10.2.0.20240423"
|
||||||
|
|
||||||
[tool.poetry.group.debug.dependencies]
|
[tool.poetry.group.debug.dependencies]
|
||||||
objprint = '^0.2.2'
|
objprint = '^0.2.2'
|
||||||
@@ -49,6 +50,10 @@ requires = ['poetry-core>=1.0.0']
|
|||||||
build-backend = 'poetry.core.masonry.api'
|
build-backend = 'poetry.core.masonry.api'
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
select = [
|
select = [
|
||||||
'F', # pyflakes
|
'F', # pyflakes
|
||||||
'E', # pycodestyle errors
|
'E', # pycodestyle errors
|
||||||
@@ -89,14 +94,12 @@ ignore = [
|
|||||||
'ANN202', # 向 NoneBot 注册的函数
|
'ANN202', # 向 NoneBot 注册的函数
|
||||||
'TRY003',
|
'TRY003',
|
||||||
]
|
]
|
||||||
line-length = 120
|
|
||||||
target-version = "py310"
|
|
||||||
flake8-quotes = { inline-quotes = 'single', multiline-quotes = 'double' }
|
flake8-quotes = { inline-quotes = 'single', multiline-quotes = 'double' }
|
||||||
|
|
||||||
[tool.ruff.flake8-annotations]
|
[tool.ruff.lint.flake8-annotations]
|
||||||
mypy-init-return = true
|
mypy-init-return = true
|
||||||
|
|
||||||
[tool.ruff.flake8-builtins]
|
[tool.ruff.lint.flake8-builtins]
|
||||||
builtins-ignorelist = ["id"]
|
builtins-ignorelist = ["id"]
|
||||||
|
|
||||||
[tool.ruff.format]
|
[tool.ruff.format]
|
||||||
|
|||||||
Reference in New Issue
Block a user