mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
✨ 迁移到新模板 (#536)
Some checks are pending
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Some checks are pending
Code Coverage / Test (macos-latest, 3.10) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.11) (push) Waiting to run
Code Coverage / Test (macos-latest, 3.12) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.10) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.11) (push) Waiting to run
Code Coverage / Test (ubuntu-latest, 3.12) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.10) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.11) (push) Waiting to run
Code Coverage / Test (windows-latest, 3.12) (push) Waiting to run
Code Coverage / check (push) Blocked by required conditions
TypeCheck / TypeCheck (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
* ➕ 添加依赖 strenum * 🐛 优化等待逻辑,修复截图爆炸 * ✨ 使用新模板 * ⚡️ 关闭自动转译 * ✨ 同步新模板 schemas * 🌐 添加模板语言的映射 * ✨ 适配 bind * ✨ 更新模板 * ✨ 全部适配 * 🚨 make mypy happy * Update nonebot_plugin_tetris_stats/games/tos/query.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * ✨ 使用用户设置语言 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
122
nonebot_plugin_tetris_stats/utils/chart.py
Normal file
122
nonebot_plugin_tetris_stats/utils/chart.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from datetime import datetime, timedelta
|
||||
from math import ceil, floor, inf
|
||||
from typing import NamedTuple
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from .exception import WhatTheFuckError
|
||||
from .render.schemas.base import HistoryData
|
||||
from .typedefs import Number
|
||||
|
||||
|
||||
class ValueBound(NamedTuple):
|
||||
value_max: int
|
||||
value_min: int
|
||||
|
||||
|
||||
class Split(NamedTuple):
|
||||
split_value: int
|
||||
offset: int
|
||||
|
||||
|
||||
def get_value_bounds(values: list[int | float]) -> ValueBound:
|
||||
value_max = 10 * ceil(max(values) / 10)
|
||||
value_min = 10 * floor(min(values) / 10)
|
||||
return ValueBound(value_max, value_min)
|
||||
|
||||
|
||||
def get_split(value_bound: ValueBound, max_value: Number = inf, min_value: Number = -inf) -> Split:
|
||||
offset = 0
|
||||
overflow = 0
|
||||
|
||||
while True:
|
||||
if (new_max_value := value_bound.value_max + offset + overflow) > max_value:
|
||||
overflow -= 1
|
||||
continue
|
||||
if (new_min_value := value_bound.value_min - offset + overflow) < min_value:
|
||||
overflow += 1
|
||||
continue
|
||||
if ((new_max_value - new_min_value) / 40).is_integer():
|
||||
split_value = int((value_bound.value_max + offset - (value_bound.value_min - offset)) / 4)
|
||||
break
|
||||
offset += 1
|
||||
return Split(split_value, offset + overflow)
|
||||
|
||||
|
||||
def get_specified_point(
|
||||
previous_point: HistoryData,
|
||||
behind_point: HistoryData,
|
||||
point_time: datetime,
|
||||
) -> HistoryData:
|
||||
"""根据给出的 previous_point 和 behind_point, 推算 point_time 点处的数据
|
||||
|
||||
Args:
|
||||
previous_point (Data): 前面的数据点
|
||||
behind_point (Data): 后面的数据点
|
||||
point_time (datetime): 要推算的点的位置
|
||||
|
||||
Returns:
|
||||
Data: 要推算的点的数据
|
||||
"""
|
||||
# 求两个点的斜率
|
||||
slope = (behind_point.score - previous_point.score) / (
|
||||
datetime.timestamp(behind_point.record_at) - datetime.timestamp(previous_point.record_at)
|
||||
)
|
||||
return HistoryData(
|
||||
record_at=point_time,
|
||||
score=previous_point.score
|
||||
+ slope * (datetime.timestamp(point_time) - datetime.timestamp(previous_point.record_at)),
|
||||
)
|
||||
|
||||
|
||||
def handle_history_data(data: list[HistoryData]) -> list[HistoryData]: # noqa: C901, PLR0912
|
||||
# 按照 记录时间 对数据进行排序
|
||||
data.sort(key=lambda x: x.record_at)
|
||||
|
||||
# 定义时间边界, 右边界为当前时间的当天零点, 左边界为右边界前推9天
|
||||
# 返回值的[0]和[-1]分别应满足left_border和right_border
|
||||
zero = datetime.now(ZoneInfo('Asia/Shanghai')).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
left_border = zero - timedelta(days=9)
|
||||
right_border = zero.replace(microsecond=1000)
|
||||
|
||||
lefts: list[HistoryData] = []
|
||||
in_border: list[HistoryData] = []
|
||||
rights: list[HistoryData] = []
|
||||
|
||||
# 根据 记录时间 将数据分类到对应的列表中
|
||||
for i in data:
|
||||
if i.record_at < left_border:
|
||||
lefts.append(i)
|
||||
elif i.record_at < right_border:
|
||||
in_border.append(i)
|
||||
else:
|
||||
rights.append(i)
|
||||
|
||||
ret: list[HistoryData] = []
|
||||
|
||||
# 处理左边界的点
|
||||
if lefts and in_border: # 如果边界左侧和边界内都有值则推算
|
||||
ret.append(get_specified_point(lefts[-1], in_border[0], left_border))
|
||||
elif lefts and not in_border: # 如果边界左侧有值但是边界内没有值则直接取左侧的最后一个值
|
||||
ret.append(HistoryData(score=lefts[-1].score, record_at=left_border))
|
||||
elif not lefts and in_border: # 如果边界左侧没有值但是边界内有值则直接取边界内的第一个值
|
||||
ret.append(HistoryData(score=in_border[0].score, record_at=left_border))
|
||||
elif not lefts and not in_border and rights: # 如果边界左侧和边界内都没有值但是边界右侧有值则直接取边界右侧的第一个值 # fmt: skip
|
||||
ret.append(HistoryData(score=rights[0].score, record_at=left_border))
|
||||
else: # 暂时没想到其他情况
|
||||
raise WhatTheFuckError
|
||||
|
||||
# 添加边界内数据
|
||||
ret.extend(in_border)
|
||||
|
||||
# 处理右边界的点
|
||||
if in_border and rights: # 如果边界内和边界右侧都有值则推算
|
||||
ret.append(get_specified_point(in_border[-1], rights[0], right_border))
|
||||
elif not in_border and rights: # 如果边界内没有值但是边界右侧有值则直接取右侧的第一个值
|
||||
ret.append(HistoryData(score=rights[0].score, record_at=right_border))
|
||||
elif in_border and not rights: # 如果边界内有值但是边界右侧没有值则直接取边界内的最后一个值
|
||||
ret.append(HistoryData(score=in_border[-1].score, record_at=right_border))
|
||||
elif not in_border and not rights and lefts: # 如果边界内和边界右侧都没有值但是边界左侧有值则直接取边界左侧的最后一个值 # fmt: skip
|
||||
ret.append(HistoryData(score=lefts[-1].score, record_at=right_border))
|
||||
else: # 暂时没想到其他情况
|
||||
raise WhatTheFuckError
|
||||
return ret
|
||||
@@ -52,8 +52,8 @@ class HostPage:
|
||||
@driver.on_startup
|
||||
def _():
|
||||
app.mount(
|
||||
'/host/assets',
|
||||
StaticFiles(directory=TEMPLATES_DIR / 'assets'),
|
||||
'/host/_nuxt',
|
||||
StaticFiles(directory=TEMPLATES_DIR / '_nuxt'),
|
||||
name='assets',
|
||||
)
|
||||
logger.success('assets mounted')
|
||||
|
||||
8
nonebot_plugin_tetris_stats/utils/lang.py
Normal file
8
nonebot_plugin_tetris_stats/utils/lang.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from typing import cast
|
||||
|
||||
from ..i18n.model import Lang
|
||||
from .typedefs import Lang as LangType
|
||||
|
||||
|
||||
def get_lang() -> LangType:
|
||||
return cast('LangType', Lang.template.template_language())
|
||||
@@ -4,20 +4,26 @@ from jinja2 import Environment, FileSystemLoader
|
||||
from nonebot.compat import PYDANTIC_V2
|
||||
|
||||
from ..templates import TEMPLATES_DIR
|
||||
from .schemas.base import Base
|
||||
from .schemas.bind import Bind
|
||||
from .schemas.tetrio.rank.detail import Data as TETRIORankDetailData
|
||||
from .schemas.tetrio.rank.v1 import Data as TETRIORankDataV1
|
||||
from .schemas.tetrio.rank.v2 import Data as TETRIORankDataV2
|
||||
from .schemas.tetrio.record.blitz import Record as TETRIORecordBlitz
|
||||
from .schemas.tetrio.record.sprint import Record as TETRIORecordSprint
|
||||
from .schemas.tetrio.user.info_v1 import Info as TETRIOUserInfoV1
|
||||
from .schemas.tetrio.user.info_v2 import Info as TETRIOUserInfoV2
|
||||
from .schemas.tetrio.user.list_v2 import List as TETRIOUserListV2
|
||||
from .schemas.top_info import Info as TOPInfo
|
||||
from .schemas.tos_info import Info as TOSInfo
|
||||
from .schemas.v1.tetrio.rank import Data as TETRIORankDataV1
|
||||
from .schemas.v1.tetrio.user.info import Info as TETRIOUserInfoV1
|
||||
from .schemas.v1.top.info import Info as TOPInfoV1
|
||||
from .schemas.v1.tos.info import Info as TOSInfoV1
|
||||
from .schemas.v2.tetrio.rank import Data as TETRIORankDataV2
|
||||
from .schemas.v2.tetrio.rank.detail import Data as TETRIORankDetailDataV2
|
||||
from .schemas.v2.tetrio.record.blitz import Record as TETRIORecordBlitzV2
|
||||
from .schemas.v2.tetrio.record.sprint import Record as TETRIORecordSprintV2
|
||||
from .schemas.v2.tetrio.tetra_league import Data as TETRIOTetraLeagueDataV2
|
||||
from .schemas.v2.tetrio.user.info import Info as TETRIOUserInfoV2
|
||||
from .schemas.v2.tetrio.user.list import List as TETRIOUserListV2
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(TEMPLATES_DIR), autoescape=True, trim_blocks=True, lstrip_blocks=True, enable_async=True
|
||||
loader=FileSystemLoader(TEMPLATES_DIR),
|
||||
autoescape=False, # noqa: S701
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
enable_async=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -28,48 +34,26 @@ async def render(render_type: Literal['v1/tetrio/info'], data: TETRIOUserInfoV1)
|
||||
@overload
|
||||
async def render(render_type: Literal['v1/tetrio/rank'], data: TETRIORankDataV1) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v1/top/info'], data: TOPInfo) -> str: ...
|
||||
async def render(render_type: Literal['v1/top/info'], data: TOPInfoV1) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v1/tos/info'], data: TOSInfo) -> str: ...
|
||||
async def render(render_type: Literal['v1/tos/info'], data: TOSInfoV1) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/rank'], data: TETRIORankDataV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/rank/detail'], data: TETRIORankDetailDataV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/record/blitz'], data: TETRIORecordBlitzV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/record/sprint'], data: TETRIORecordSprintV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/tetra-league'], data: TETRIOTetraLeagueDataV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/user/info'], data: TETRIOUserInfoV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/user/list'], data: TETRIOUserListV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/record/40l'], data: TETRIORecordSprint) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/record/blitz'], data: TETRIORecordBlitz) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/rank'], data: TETRIORankDataV2) -> str: ...
|
||||
@overload
|
||||
async def render(render_type: Literal['v2/tetrio/rank/detail'], data: TETRIORankDetailData) -> str: ...
|
||||
|
||||
|
||||
async def render(
|
||||
render_type: Literal[
|
||||
'v1/binding',
|
||||
'v1/tetrio/info',
|
||||
'v1/tetrio/rank',
|
||||
'v1/top/info',
|
||||
'v1/tos/info',
|
||||
'v2/tetrio/user/info',
|
||||
'v2/tetrio/user/list',
|
||||
'v2/tetrio/record/40l',
|
||||
'v2/tetrio/record/blitz',
|
||||
'v2/tetrio/rank',
|
||||
'v2/tetrio/rank/detail',
|
||||
],
|
||||
data: Bind
|
||||
| TETRIOUserInfoV1
|
||||
| TETRIORankDataV1
|
||||
| TOPInfo
|
||||
| TOSInfo
|
||||
| TETRIOUserInfoV2
|
||||
| TETRIOUserListV2
|
||||
| TETRIORecordSprint
|
||||
| TETRIORecordBlitz
|
||||
| TETRIORankDataV2
|
||||
| TETRIORankDetailData,
|
||||
render_type: str,
|
||||
data: Base,
|
||||
) -> str:
|
||||
if PYDANTIC_V2:
|
||||
return await env.get_template('index.html').render_async(
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
from datetime import datetime
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
from strenum import StrEnum
|
||||
|
||||
from ...typedefs import Number
|
||||
from ...typedefs import Lang, Number
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
_lang: Lang
|
||||
|
||||
|
||||
class Avatar(BaseModel):
|
||||
@@ -18,3 +24,14 @@ class People(BaseModel):
|
||||
class Ranking(BaseModel):
|
||||
rating: Number
|
||||
rd: Number
|
||||
|
||||
|
||||
class HistoryData(BaseModel):
|
||||
score: Number
|
||||
record_at: datetime
|
||||
|
||||
|
||||
class Trending(StrEnum):
|
||||
UP = 'up'
|
||||
KEEP = 'keep'
|
||||
DOWN = 'down'
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .base import People
|
||||
from .base import Base, People
|
||||
|
||||
|
||||
class Bind(BaseModel):
|
||||
class Bind(Base):
|
||||
platform: Literal['TETR.IO', 'TOP', 'TOS']
|
||||
status: Literal['error', 'success', 'unknown', 'unlink', 'unverified']
|
||||
type: Literal['success', 'unknown', 'unlink', 'unverified', 'error']
|
||||
user: People
|
||||
bot: People
|
||||
command: str
|
||||
prompt: str
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ......games.tetrio.api.typedefs import ValidRank
|
||||
|
||||
|
||||
class SpecialData(BaseModel):
|
||||
apm: float
|
||||
pps: float
|
||||
lpm: float
|
||||
vs: float
|
||||
adpm: float
|
||||
apl: float | None = None
|
||||
adpl: float | None = None
|
||||
apm_holder: str | None = None
|
||||
pps_holder: str | None = None
|
||||
vs_holder: str | None = None
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
name: ValidRank
|
||||
trending: float
|
||||
require_tr: float
|
||||
players: int
|
||||
|
||||
minimum_data: SpecialData
|
||||
average_data: SpecialData
|
||||
maximum_data: SpecialData
|
||||
|
||||
updated_at: datetime
|
||||
@@ -1,25 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ......games.tetrio.api.typedefs import ValidRank
|
||||
|
||||
|
||||
class AverageData(BaseModel):
|
||||
pps: float
|
||||
apm: float
|
||||
apl: float
|
||||
vs: float
|
||||
adpl: float
|
||||
|
||||
|
||||
class ItemData(BaseModel):
|
||||
require_tr: float
|
||||
trending: float
|
||||
average_data: AverageData
|
||||
players: int
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
items: dict[ValidRank, ItemData]
|
||||
updated_at: datetime
|
||||
@@ -1,10 +0,0 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .....typedefs import Number
|
||||
|
||||
|
||||
class TetraLeagueHistoryData(BaseModel):
|
||||
record_at: datetime
|
||||
tr: Number
|
||||
@@ -1,49 +0,0 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ......games.tetrio.api.typedefs import Rank
|
||||
from .....typedefs import Number
|
||||
from ...base import People, Ranking
|
||||
from .base import TetraLeagueHistoryData
|
||||
|
||||
|
||||
class User(People):
|
||||
bio: str | None
|
||||
|
||||
|
||||
class TetraLeague(BaseModel):
|
||||
rank: Rank
|
||||
tr: Number
|
||||
global_rank: Number
|
||||
pps: Number
|
||||
lpm: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
vs: Number
|
||||
adpm: Number
|
||||
adpl: Number
|
||||
|
||||
|
||||
class TetraLeagueHistory(BaseModel):
|
||||
data: list[TetraLeagueHistoryData]
|
||||
split_interval: Number
|
||||
min_tr: Number
|
||||
max_tr: Number
|
||||
offset: Number
|
||||
|
||||
|
||||
class Radar(BaseModel):
|
||||
app: Number
|
||||
dsps: Number
|
||||
dspp: Number
|
||||
ci: Number
|
||||
ge: Number
|
||||
|
||||
|
||||
class Info(BaseModel):
|
||||
user: User
|
||||
ranking: Ranking
|
||||
tetra_league: TetraLeague
|
||||
tetra_league_history: TetraLeagueHistory
|
||||
radar: Radar
|
||||
sprint: str
|
||||
blitz: str
|
||||
@@ -1,17 +0,0 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ...typedefs import Number
|
||||
from .base import People
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
pps: Number
|
||||
lpm: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
|
||||
|
||||
class Info(BaseModel):
|
||||
user: People
|
||||
today: Data
|
||||
history: Data
|
||||
@@ -1,32 +0,0 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...typedefs import Number
|
||||
from .base import People, Ranking
|
||||
|
||||
|
||||
class Multiplayer(BaseModel):
|
||||
pps: Number
|
||||
lpm: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
vs: Number
|
||||
adpm: Number
|
||||
adpl: Number
|
||||
|
||||
|
||||
class Radar(BaseModel):
|
||||
app: Number
|
||||
OR: Number = Field(serialization_alias='or')
|
||||
dspp: Number
|
||||
ci: Number
|
||||
ge: Number
|
||||
|
||||
|
||||
class Info(BaseModel):
|
||||
user: People
|
||||
ranking: Ranking
|
||||
multiplayer: Multiplayer
|
||||
radar: Radar
|
||||
sprint: str
|
||||
challenge: str
|
||||
marathon: str
|
||||
12
nonebot_plugin_tetris_stats/utils/render/schemas/v1/base.py
Normal file
12
nonebot_plugin_tetris_stats/utils/render/schemas/v1/base.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ....typedefs import Number
|
||||
from ..base import HistoryData
|
||||
|
||||
|
||||
class History(BaseModel):
|
||||
data: list[HistoryData]
|
||||
split_interval: Number
|
||||
min_value: Number
|
||||
max_value: Number
|
||||
offset: Number
|
||||
@@ -3,6 +3,7 @@ from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ......games.tetrio.api.typedefs import ValidRank
|
||||
from ...base import Base
|
||||
|
||||
|
||||
class ItemData(BaseModel):
|
||||
@@ -11,6 +12,6 @@ class ItemData(BaseModel):
|
||||
players: int
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
class Data(Base):
|
||||
items: dict[ValidRank, ItemData]
|
||||
updated_at: datetime
|
||||
@@ -0,0 +1,50 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .......games.tetrio.api.typedefs import Rank
|
||||
from ......typedefs import Number
|
||||
from ....base import Base, People, Trending
|
||||
from ...base import History
|
||||
|
||||
|
||||
class User(People):
|
||||
bio: str | None
|
||||
|
||||
|
||||
class Multiplayer(BaseModel):
|
||||
glicko: str
|
||||
rd: Number
|
||||
rank: Rank
|
||||
tr: str
|
||||
global_rank: Number
|
||||
|
||||
history: History
|
||||
|
||||
lpm: Number
|
||||
pps: Number
|
||||
lpm_trending: Trending
|
||||
|
||||
apm: Number
|
||||
apl: Number
|
||||
apm_trending: Trending
|
||||
|
||||
adpm: Number
|
||||
vs: Number
|
||||
adpl: Number
|
||||
adpm_trending: Trending
|
||||
|
||||
app: Number
|
||||
ci: Number
|
||||
dspp: Number
|
||||
dsps: Number
|
||||
ge: Number
|
||||
|
||||
|
||||
class Singleplayer(BaseModel):
|
||||
sprint: str
|
||||
blitz: str
|
||||
|
||||
|
||||
class Info(Base):
|
||||
user: User
|
||||
multiplayer: Multiplayer
|
||||
singleplayer: Singleplayer
|
||||
@@ -0,0 +1,19 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .....typedefs import Number
|
||||
from ...base import Base, People, Trending
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
pps: Number
|
||||
lpm: Number
|
||||
lpm_trending: Trending
|
||||
apm: Number
|
||||
apl: Number
|
||||
apm_trending: Trending
|
||||
|
||||
|
||||
class Info(Base):
|
||||
user: People
|
||||
today: Data
|
||||
historical: Data
|
||||
@@ -0,0 +1,42 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .....typedefs import Number
|
||||
from ...base import Base, People, Trending
|
||||
from ..base import History
|
||||
|
||||
|
||||
class Multiplayer(BaseModel):
|
||||
history: History
|
||||
rating: Number
|
||||
rd: Number
|
||||
|
||||
lpm: Number
|
||||
pps: Number
|
||||
lpm_trending: Trending
|
||||
|
||||
apm: Number
|
||||
apl: Number
|
||||
apm_trending: Trending
|
||||
|
||||
adpm: Number
|
||||
vs: Number
|
||||
adpl: Number
|
||||
adpm_trending: Trending
|
||||
|
||||
app: Number
|
||||
ci: Number
|
||||
dspp: Number
|
||||
or_: Number = Field(serialization_alias='or')
|
||||
ge: Number
|
||||
|
||||
|
||||
class Singleplayer(BaseModel):
|
||||
sprint: str
|
||||
challenge: str
|
||||
marathon: str
|
||||
|
||||
|
||||
class Info(Base):
|
||||
user: People
|
||||
multiplayer: Multiplayer
|
||||
singleplayer: Singleplayer
|
||||
@@ -0,0 +1,27 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .......games.tetrio.api.typedefs import ValidRank
|
||||
from ......typedefs import Number
|
||||
from ....base import Base
|
||||
|
||||
|
||||
class AverageData(BaseModel):
|
||||
pps: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
vs: Number
|
||||
adpl: Number
|
||||
|
||||
|
||||
class ItemData(BaseModel):
|
||||
require_tr: Number
|
||||
trending: Number
|
||||
average_data: AverageData
|
||||
players: Number
|
||||
|
||||
|
||||
class Data(Base):
|
||||
items: dict[ValidRank, ItemData]
|
||||
updated_at: datetime
|
||||
@@ -0,0 +1,33 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .......games.tetrio.api.typedefs import ValidRank
|
||||
from ......typedefs import Number
|
||||
from ....base import Base
|
||||
|
||||
|
||||
class SpecialData(BaseModel):
|
||||
apm: Number
|
||||
pps: Number
|
||||
lpm: Number
|
||||
vs: Number
|
||||
adpm: Number
|
||||
apl: Number | None = None
|
||||
adpl: Number | None = None
|
||||
apm_holder: str | None = None
|
||||
pps_holder: str | None = None
|
||||
vs_holder: str | None = None
|
||||
|
||||
|
||||
class Data(Base):
|
||||
name: ValidRank
|
||||
trending: Number
|
||||
require_tr: Number
|
||||
players: Number
|
||||
|
||||
minimum_data: SpecialData
|
||||
average_data: SpecialData
|
||||
maximum_data: SpecialData
|
||||
|
||||
updated_at: datetime
|
||||
@@ -3,7 +3,7 @@ from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ...base import People
|
||||
from ....base import Base, People
|
||||
|
||||
|
||||
class User(People):
|
||||
@@ -61,7 +61,7 @@ class Statistic(BaseModel):
|
||||
finesse: Finesse
|
||||
|
||||
|
||||
class Record(BaseModel):
|
||||
class Record(Base):
|
||||
type: Literal['best', 'personal_best', 'recent', 'disputed']
|
||||
|
||||
user: User
|
||||
@@ -0,0 +1,39 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .....typedefs import Number
|
||||
from ...base import Base
|
||||
|
||||
|
||||
class StatisticalData(BaseModel):
|
||||
pps: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
vs: Number
|
||||
adpl: Number
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
|
||||
class Handling(BaseModel):
|
||||
arr: Number
|
||||
das: Number
|
||||
sdf: Number
|
||||
|
||||
|
||||
class Game(BaseModel):
|
||||
user: User
|
||||
points: Number
|
||||
average_data: StatisticalData
|
||||
data: list[StatisticalData]
|
||||
handling: Handling
|
||||
|
||||
|
||||
class Data(Base):
|
||||
replay_id: str
|
||||
games: list[Game]
|
||||
play_at: datetime
|
||||
@@ -3,10 +3,9 @@ from typing import Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ......games.tetrio.api.typedefs import Rank
|
||||
from .....typedefs import Number
|
||||
from ...base import Avatar
|
||||
from .base import TetraLeagueHistoryData
|
||||
from .......games.tetrio.api.typedefs import Rank
|
||||
from ......typedefs import Number
|
||||
from ....base import Avatar, Base, HistoryData
|
||||
|
||||
|
||||
class Badge(BaseModel):
|
||||
@@ -22,6 +21,7 @@ class User(BaseModel):
|
||||
country: str | None
|
||||
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'hidden', 'banned']
|
||||
botmaster: str | None
|
||||
|
||||
avatar: str | Avatar
|
||||
banner: str | None
|
||||
@@ -36,6 +36,9 @@ class User(BaseModel):
|
||||
badges: list[Badge]
|
||||
xp: Number
|
||||
|
||||
ar: Number
|
||||
achievements: list[int]
|
||||
|
||||
playtime: str | None
|
||||
join_at: datetime | None
|
||||
|
||||
@@ -74,18 +77,20 @@ class TetraLeague(BaseModel):
|
||||
|
||||
decaying: bool
|
||||
|
||||
history: list[TetraLeagueHistoryData] | None
|
||||
history: list[HistoryData] | None
|
||||
|
||||
|
||||
class Sprint(BaseModel):
|
||||
time: str
|
||||
global_rank: int | None
|
||||
global_rank: Number | None
|
||||
country_rank: Number | None
|
||||
play_at: datetime
|
||||
|
||||
|
||||
class Blitz(BaseModel):
|
||||
score: int
|
||||
score: Number
|
||||
global_rank: int | None
|
||||
country_rank: int | None
|
||||
play_at: datetime
|
||||
|
||||
|
||||
@@ -94,9 +99,29 @@ class Zen(BaseModel):
|
||||
score: int
|
||||
|
||||
|
||||
class Info(BaseModel):
|
||||
class Week(BaseModel):
|
||||
altitude: Number
|
||||
global_rank: int | None
|
||||
country_rank: int | None
|
||||
play_at: datetime
|
||||
|
||||
|
||||
class Best(BaseModel):
|
||||
altitude: Number
|
||||
global_rank: int | None
|
||||
play_at: datetime
|
||||
|
||||
|
||||
class Zenith(BaseModel):
|
||||
week: Week | None
|
||||
best: Best | None
|
||||
|
||||
|
||||
class Info(Base):
|
||||
user: User
|
||||
tetra_league: TetraLeague | None
|
||||
zenith: Zenith | None
|
||||
zenithex: Zenith | None
|
||||
statistic: Statistic | None
|
||||
sprint: Sprint | None
|
||||
blitz: Blitz | None
|
||||
@@ -1,24 +1,23 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ......games.tetrio.api.typedefs import Rank
|
||||
from .....typedefs import Number
|
||||
from ...base import Avatar
|
||||
from .......games.tetrio.api.typedefs import Rank
|
||||
from ......typedefs import Number
|
||||
from ....base import Avatar, Base
|
||||
|
||||
|
||||
class TetraLeague(BaseModel):
|
||||
pps: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
vs: Number | None
|
||||
adpl: Number | None
|
||||
|
||||
rank: Rank
|
||||
tr: Number
|
||||
|
||||
glicko: Number | None
|
||||
rd: Number | None
|
||||
decaying: bool
|
||||
pps: Number
|
||||
apm: Number
|
||||
apl: Number
|
||||
vs: Number | None
|
||||
adpl: Number | None
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
@@ -26,11 +25,14 @@ class User(BaseModel):
|
||||
name: str
|
||||
avatar: str | Avatar
|
||||
country: str | None
|
||||
tetra_league: TetraLeague
|
||||
xp: Number
|
||||
join_at: datetime | None
|
||||
|
||||
|
||||
class List(BaseModel):
|
||||
class Data(BaseModel):
|
||||
user: User
|
||||
tetra_league: TetraLeague
|
||||
|
||||
|
||||
class List(Base):
|
||||
show_index: bool
|
||||
users: list[User]
|
||||
data: list[Data]
|
||||
@@ -16,6 +16,7 @@ async def screenshot(url: str) -> bytes:
|
||||
context = await BrowserManager.get_context('screenshot', factory=context_factory)
|
||||
async with await context.new_page() as page:
|
||||
await page.goto(url)
|
||||
await page.wait_for_selector('#content')
|
||||
size: ViewportSize = await page.evaluate("""
|
||||
() => {
|
||||
const element = document.querySelector('#content');
|
||||
@@ -26,5 +27,4 @@ async def screenshot(url: str) -> bytes:
|
||||
};
|
||||
""")
|
||||
await page.set_viewport_size(size)
|
||||
await page.wait_for_load_state('networkidle')
|
||||
return await page.locator('id=content').screenshot(animations='disabled', timeout=5000, type='png')
|
||||
|
||||
@@ -30,7 +30,7 @@ async def download_templates(tag: str) -> Path:
|
||||
tag = (
|
||||
(
|
||||
await client.get(
|
||||
'https://github.com/A-Minos/tetris-stats-templates/releases/latest', follow_redirects=True
|
||||
'https://github.com/A-Minos/tetris-stats-templates-new/releases/latest', follow_redirects=True
|
||||
)
|
||||
)
|
||||
.url.path.strip('/')
|
||||
@@ -43,7 +43,7 @@ async def download_templates(tag: str) -> Path:
|
||||
async with (
|
||||
client.stream(
|
||||
'GET',
|
||||
f'https://github.com/A-Minos/tetris-stats-templates/releases/download/{tag}/dist.zip',
|
||||
f'https://github.com/A-Minos/tetris-stats-templates-new/releases/download/{tag}/dist.zip',
|
||||
follow_redirects=True,
|
||||
) as response,
|
||||
aopen(path, 'wb') as file,
|
||||
@@ -107,7 +107,7 @@ async def init_templates(tag: str) -> bool:
|
||||
async def check_tag(tag: str) -> bool:
|
||||
async with AsyncClient(proxy=config.tetris.proxy.github or config.tetris.proxy.main) as client:
|
||||
return (
|
||||
await client.get(f'https://github.com/A-Minos/tetris-stats-templates/releases/tag/{tag}')
|
||||
await client.get(f'https://github.com/A-Minos/tetris-stats-templates-new/releases/tag/{tag}')
|
||||
).status_code != HTTPStatus.NOT_FOUND
|
||||
|
||||
|
||||
|
||||
@@ -59,3 +59,5 @@ Me: TypeAlias = Literal[
|
||||
'self',
|
||||
'oneself',
|
||||
]
|
||||
|
||||
Lang: TypeAlias = Literal['zh-CN', 'en-US']
|
||||
|
||||
Reference in New Issue
Block a user