mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e45db8cf5 | |||
|
|
2020deadac | ||
|
|
ce7bce6e20 | ||
|
|
d4b690f682 | ||
| c4bde71546 | |||
|
|
f56f993c69 | ||
|
|
cfcda6f597 | ||
|
|
96f5d4559d | ||
|
|
23f412b4f4 | ||
|
|
25b0d2bcdc | ||
|
|
a116f9901c | ||
|
|
82befd631e |
2
.github/workflows/Release.yml
vendored
2
.github/workflows/Release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: astral-sh/setup-uv@v3
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
name: Setup UV
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
4
.github/workflows/Test.yml
vendored
4
.github/workflows/Test.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v3
|
||||
uses: astral-sh/setup-uv@v5
|
||||
with:
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ env.PYTHON_VERSION }}_${{ env.OS }}
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: uv run pytest --cov=nonebot_plugin_tetris_stats --cov-report xml
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
env_vars: OS,PYTHON_VERSION
|
||||
|
||||
2
.github/workflows/TypeCheck.yml
vendored
2
.github/workflows/TypeCheck.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: astral-sh/setup-uv@v3
|
||||
- uses: astral-sh/setup-uv@v5
|
||||
name: Setup UV
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
@@ -7,7 +7,7 @@ ci:
|
||||
autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks'
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.7.3
|
||||
rev: v0.8.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
@@ -10,6 +10,7 @@ require_plugins = {
|
||||
'nonebot_plugin_session',
|
||||
'nonebot_plugin_user',
|
||||
'nonebot_plugin_userinfo',
|
||||
'nonebot_plugin_waiter',
|
||||
}
|
||||
|
||||
for i in require_plugins:
|
||||
|
||||
@@ -63,6 +63,23 @@ async def create_or_update_bind(
|
||||
return status
|
||||
|
||||
|
||||
async def remove_bind(
|
||||
session: AsyncSession,
|
||||
user: User,
|
||||
game_platform: GameType,
|
||||
) -> bool:
|
||||
bind = await query_bind_info(
|
||||
session=session,
|
||||
user=user,
|
||||
game_platform=game_platform,
|
||||
)
|
||||
if bind is not None:
|
||||
await session.delete(bind)
|
||||
await session.commit()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
T = TypeVar('T', 'TETRIOHistoricalData', 'TOPHistoricalData', 'TOSHistoricalData')
|
||||
|
||||
lock = Lock()
|
||||
|
||||
@@ -44,7 +44,7 @@ async def _(matcher: Matcher, account: MessageFormatError):
|
||||
|
||||
@alc.handle()
|
||||
async def _(matcher: Matcher, matches: AlcMatches):
|
||||
if matches.head_matched and matches.options != {} or matches.main_args == {}:
|
||||
if (matches.head_matched and matches.options != {}) or matches.main_args == {}:
|
||||
await matcher.finish(
|
||||
(f'{matches.error_info!r}\n' if matches.error_info is not None else '')
|
||||
+ f'输入"{matches.header_result} --help"查看帮助'
|
||||
|
||||
@@ -23,7 +23,7 @@ command = Subcommand(
|
||||
)
|
||||
|
||||
|
||||
from . import bind, config, list, query, rank, record # noqa: E402
|
||||
from . import bind, config, list, query, rank, record, unbind # noqa: A004, E402
|
||||
|
||||
main_command.add(command)
|
||||
|
||||
@@ -35,4 +35,5 @@ __all__ = [
|
||||
'query',
|
||||
'rank',
|
||||
'record',
|
||||
'unbind',
|
||||
]
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import Literal, overload
|
||||
from uuid import UUID
|
||||
|
||||
from nonebot import __version__ as __nonebot_version__
|
||||
from nonebot.compat import type_validate_json
|
||||
from yarl import URL
|
||||
|
||||
from ....utils.exception import RequestError
|
||||
from ....version import __version__
|
||||
from ..constant import BASE_URL
|
||||
from .cache import Cache
|
||||
from .schemas.base import FailedModel
|
||||
@@ -22,7 +24,12 @@ async def by(
|
||||
await get(
|
||||
BASE_URL / f'users/by/{by_type}',
|
||||
parameter,
|
||||
{'X-Session-ID': str(x_session_id)} if x_session_id is not None else None,
|
||||
{
|
||||
'X-Session-ID': str(x_session_id),
|
||||
'User-Agent': f'nonebot-plugin-tetris-stats/{__version__} (Windows NT 10.0; Win64; x64) NoneBot2/{__nonebot_version__}',
|
||||
}
|
||||
if x_session_id is not None
|
||||
else None,
|
||||
),
|
||||
)
|
||||
if isinstance(model, FailedModel):
|
||||
|
||||
@@ -7,7 +7,7 @@ from ...typing import Rank, ValidRank
|
||||
from ..base import ArCounts, FailedModel, P, SuccessModel
|
||||
|
||||
|
||||
class League(BaseModel):
|
||||
class BaseLeague(BaseModel):
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
tr: float
|
||||
@@ -16,13 +16,21 @@ class League(BaseModel):
|
||||
bestrank: ValidRank
|
||||
glicko: float
|
||||
rd: float
|
||||
apm: float
|
||||
pps: float
|
||||
vs: float
|
||||
decaying: bool
|
||||
|
||||
|
||||
class Entry(BaseModel):
|
||||
class InvalidLeague(BaseLeague):
|
||||
apm: None
|
||||
vs: None
|
||||
|
||||
|
||||
class League(BaseLeague):
|
||||
apm: float
|
||||
vs: float
|
||||
|
||||
|
||||
class BaseEntry(BaseModel):
|
||||
id: str = Field(..., alias='_id')
|
||||
username: str
|
||||
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop']
|
||||
@@ -30,7 +38,6 @@ class Entry(BaseModel):
|
||||
xp: float
|
||||
country: str | None = None
|
||||
supporter: bool | None = None
|
||||
league: League
|
||||
gamesplayed: int
|
||||
gameswon: int
|
||||
gametime: float
|
||||
@@ -39,8 +46,16 @@ class Entry(BaseModel):
|
||||
p: P
|
||||
|
||||
|
||||
class InvalidEntry(BaseEntry):
|
||||
league: InvalidLeague
|
||||
|
||||
|
||||
class Entry(BaseEntry):
|
||||
league: League
|
||||
|
||||
|
||||
class Data(BaseModel):
|
||||
entries: list[Entry]
|
||||
entries: list[Entry | InvalidEntry]
|
||||
|
||||
|
||||
class BySuccessModel(SuccessModel):
|
||||
|
||||
@@ -14,8 +14,8 @@ __all__ = [
|
||||
'SoloSuccessModel',
|
||||
'SummariesModel',
|
||||
'Zen',
|
||||
'ZenSuccessModel',
|
||||
'Zenith',
|
||||
'ZenithEx',
|
||||
'ZenithSuccessModel',
|
||||
'ZenSuccessModel',
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from re import compile
|
||||
from re import compile # noqa: A004
|
||||
from typing import Literal
|
||||
|
||||
from yarl import URL
|
||||
|
||||
@@ -14,6 +14,7 @@ from . import command
|
||||
from .api.leaderboards import by
|
||||
from .api.schemas.base import P
|
||||
from .api.schemas.leaderboards import Parameter
|
||||
from .api.schemas.leaderboards.by import Entry
|
||||
from .constant import GAME_TYPE
|
||||
|
||||
command.add(
|
||||
@@ -84,6 +85,7 @@ async def _(
|
||||
join_at=None,
|
||||
)
|
||||
for i in league.data.entries
|
||||
if isinstance(i, Entry)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -95,7 +95,7 @@ async def get_tetra_league_data() -> None:
|
||||
|
||||
players: list[Entry] = []
|
||||
for result in results:
|
||||
players.extend(result.data.entries)
|
||||
players.extend([i for i in result.data.entries if isinstance(i, Entry)])
|
||||
players.sort(key=lambda x: x.league.tr, reverse=True)
|
||||
|
||||
rank_player_mapping: defaultdict[Rank, list[Entry]] = defaultdict(list)
|
||||
@@ -146,7 +146,7 @@ async def _() -> None:
|
||||
await get_tetra_league_data()
|
||||
|
||||
|
||||
from . import all, detail # noqa: E402
|
||||
from . import all, detail # noqa: A004, E402
|
||||
|
||||
base_command.add(command)
|
||||
|
||||
|
||||
75
nonebot_plugin_tetris_stats/games/tetrio/unbind.py
Normal file
75
nonebot_plugin_tetris_stats/games/tetrio/unbind.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from hashlib import md5
|
||||
|
||||
from nonebot_plugin_alconna import Subcommand
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from nonebot_plugin_user import User
|
||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo
|
||||
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||
from yarl import URL
|
||||
|
||||
from ...db import query_bind_info, remove_bind, trigger
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.image import get_avatar
|
||||
from ...utils.render import Bind, render
|
||||
from ...utils.render.schemas.base import Avatar, People
|
||||
from ...utils.screenshot import screenshot
|
||||
from . import alc, command
|
||||
from .api import Player
|
||||
from .constant import GAME_TYPE
|
||||
|
||||
command.add(Subcommand('unbind', help_text='解除绑定 TETR.IO 账号'))
|
||||
|
||||
alc.shortcut(
|
||||
'(?i:io)(?i:解除绑定|解绑|unbind)',
|
||||
command='tstats TETR.IO unbind',
|
||||
humanized='io解绑',
|
||||
)
|
||||
|
||||
|
||||
@alc.assign('TETRIO.unbind')
|
||||
async def _(nb_user: User, event_session: EventSession, bot_info: UserInfo = BotUserInfo()): # noqa: B008
|
||||
async with (
|
||||
trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='unbind',
|
||||
command_args=[],
|
||||
),
|
||||
get_session() as session,
|
||||
):
|
||||
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
|
||||
await UniMessage('您还未绑定 TETR.IO 账号').finish()
|
||||
resp = await suggest('您确定要解绑吗?', ['是', '否'])
|
||||
if resp is None or resp.extract_plain_text() == '否':
|
||||
return
|
||||
player = Player(user_id=bind.game_account, trust=True)
|
||||
user = await player.user
|
||||
netloc = get_self_netloc()
|
||||
async with HostPage(
|
||||
await render(
|
||||
'v1/binding',
|
||||
Bind(
|
||||
platform='TETR.IO',
|
||||
status='unlink',
|
||||
user=People(
|
||||
avatar=str(
|
||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}')
|
||||
% {'revision': avatar_revision}
|
||||
)
|
||||
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
|
||||
else Avatar(type='identicon', hash=md5(user.ID.encode()).hexdigest()), # noqa: S324
|
||||
name=user.name.upper(),
|
||||
),
|
||||
bot=People(
|
||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||
name=bot_info.user_name,
|
||||
),
|
||||
command='io绑定{游戏ID}',
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).send()
|
||||
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||
@@ -29,6 +29,10 @@ command.add(
|
||||
),
|
||||
help_text='绑定 TOP 账号',
|
||||
),
|
||||
Subcommand(
|
||||
'unbind',
|
||||
help_text='解除绑定 TOP 账号',
|
||||
),
|
||||
Subcommand(
|
||||
'query',
|
||||
Args(
|
||||
@@ -51,9 +55,22 @@ command.add(
|
||||
)
|
||||
)
|
||||
|
||||
alc.shortcut('(?i:top)(?i:绑定|绑|bind)', {'command': 'tstats TOP bind', 'humanized': 'top绑定'})
|
||||
alc.shortcut('(?i:top)(?i:查询|查|query|stats)', {'command': 'tstats TOP query', 'humanized': 'top查'})
|
||||
alc.shortcut(
|
||||
'(?i:top)(?i:绑定|绑|bind)',
|
||||
command='tstats TOP bind',
|
||||
humanized='top绑定',
|
||||
)
|
||||
alc.shortcut(
|
||||
'(?i:top)(?i:解除绑定|解绑|unbind)',
|
||||
command='tstats TOP unbind',
|
||||
humanized='top解绑',
|
||||
)
|
||||
alc.shortcut(
|
||||
'(?i:top)(?i:查询|查|query|stats)',
|
||||
command='tstats TOP query',
|
||||
humanized='top查',
|
||||
)
|
||||
|
||||
add_block_handlers(alc.assign('TOP.query'))
|
||||
|
||||
from . import bind, query # noqa: E402, F401
|
||||
from . import bind, query, unbind # noqa: E402, F401
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from re import compile
|
||||
from re import compile # noqa: A004
|
||||
from typing import Literal
|
||||
|
||||
from yarl import URL
|
||||
|
||||
63
nonebot_plugin_tetris_stats/games/top/unbind.py
Normal file
63
nonebot_plugin_tetris_stats/games/top/unbind.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from nonebot_plugin_user import User
|
||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo
|
||||
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||
|
||||
from ...db import query_bind_info, remove_bind, trigger
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.image import get_avatar
|
||||
from ...utils.render import Bind, render
|
||||
from ...utils.render.schemas.base import People
|
||||
from ...utils.screenshot import screenshot
|
||||
from . import alc
|
||||
from .api import Player
|
||||
from .constant import GAME_TYPE
|
||||
|
||||
|
||||
@alc.assign('TOP.unbind')
|
||||
async def _(
|
||||
nb_user: User,
|
||||
event_session: EventSession,
|
||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
||||
):
|
||||
async with (
|
||||
trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='unbind',
|
||||
command_args=[],
|
||||
),
|
||||
get_session() as session,
|
||||
):
|
||||
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
|
||||
await UniMessage('您还未绑定 TOP 账号').finish()
|
||||
resp = await suggest('您确定要解绑吗?', ['是', '否'])
|
||||
if resp is None or resp.extract_plain_text() == '否':
|
||||
return
|
||||
player = Player(user_name=bind.game_account, trust=True)
|
||||
user = await player.user
|
||||
netloc = get_self_netloc()
|
||||
async with HostPage(
|
||||
await render(
|
||||
'v1/binding',
|
||||
Bind(
|
||||
platform='TOP',
|
||||
status='unlink',
|
||||
user=People(
|
||||
avatar=await get_avatar(event_user_info, 'Data URI', None),
|
||||
name=user.user_name,
|
||||
),
|
||||
bot=People(
|
||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||
name=bot_info.user_name,
|
||||
),
|
||||
command='top绑定{游戏ID}',
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).send()
|
||||
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||
@@ -34,6 +34,10 @@ command.add(
|
||||
),
|
||||
help_text='绑定 茶服 账号',
|
||||
),
|
||||
Subcommand(
|
||||
'unbind',
|
||||
help_text='解除绑定 TOS 账号',
|
||||
),
|
||||
Subcommand(
|
||||
'query',
|
||||
Args(
|
||||
@@ -56,9 +60,22 @@ command.add(
|
||||
)
|
||||
)
|
||||
|
||||
alc.shortcut('(?i:tos|茶服)(?i:绑定|绑|bind)', {'command': 'tstats TOS bind', 'humanized': '茶服绑定'})
|
||||
alc.shortcut('(?i:tos|茶服)(?i:查询|查|query|stats)', {'command': 'tstats TOS query', 'humanized': '茶服查'})
|
||||
alc.shortcut(
|
||||
'(?i:tos|茶服)(?i:绑定|绑|bind)',
|
||||
command='tstats TOS bind',
|
||||
humanized='茶服绑定',
|
||||
)
|
||||
alc.shortcut(
|
||||
'(?i:tos|茶服)(?i:解除绑定|解绑|unbind)',
|
||||
command='tstats TOS unbind',
|
||||
humanized='茶服解绑',
|
||||
)
|
||||
alc.shortcut(
|
||||
'(?i:tos|茶服)(?i:查询|查|query|stats)',
|
||||
command='tstats TOS query',
|
||||
humanized='茶服查',
|
||||
)
|
||||
|
||||
add_block_handlers(alc.assign('TOS.query'))
|
||||
|
||||
from . import bind, query # noqa: E402, F401
|
||||
from . import bind, query, unbind # noqa: E402, F401
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from re import compile
|
||||
from re import compile # noqa: A004
|
||||
from typing import Literal
|
||||
|
||||
from yarl import URL
|
||||
|
||||
66
nonebot_plugin_tetris_stats/games/tos/unbind.py
Normal file
66
nonebot_plugin_tetris_stats/games/tos/unbind.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_session import EventSession
|
||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
||||
from nonebot_plugin_user import User
|
||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo
|
||||
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||
|
||||
from ...db import query_bind_info, remove_bind, trigger
|
||||
from ...utils.host import HostPage, get_self_netloc
|
||||
from ...utils.image import get_avatar
|
||||
from ...utils.render import Bind, render
|
||||
from ...utils.render.avatar import get_avatar as get_random_avatar
|
||||
from ...utils.render.schemas.base import People
|
||||
from ...utils.screenshot import screenshot
|
||||
from . import alc
|
||||
from .api import Player
|
||||
from .constant import GAME_TYPE
|
||||
|
||||
|
||||
@alc.assign('TOP.unbind')
|
||||
async def _(
|
||||
nb_user: User,
|
||||
event_session: EventSession,
|
||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
||||
):
|
||||
async with (
|
||||
trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='unbind',
|
||||
command_args=[],
|
||||
),
|
||||
get_session() as session,
|
||||
):
|
||||
if (bind := await query_bind_info(session=session, user=nb_user, game_platform=GAME_TYPE)) is None:
|
||||
await UniMessage('您还未绑定 TOP 账号').finish()
|
||||
resp = await suggest('您确定要解绑吗?', ['是', '否'])
|
||||
if resp is None or resp.extract_plain_text() == '否':
|
||||
return
|
||||
player = Player(user_name=bind.game_account, trust=True)
|
||||
user = await player.user
|
||||
netloc = get_self_netloc()
|
||||
async with HostPage(
|
||||
await render(
|
||||
'v1/binding',
|
||||
Bind(
|
||||
platform='TOS',
|
||||
status='unlink',
|
||||
user=People(
|
||||
avatar=await get_avatar(event_user_info, 'Data URI', None)
|
||||
if event_user_info is not None
|
||||
else get_random_avatar(user.teaid),
|
||||
name=user.name,
|
||||
),
|
||||
bot=People(
|
||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
||||
name=bot_info.user_name,
|
||||
),
|
||||
command='茶服绑定{游戏ID}',
|
||||
),
|
||||
)
|
||||
) as page_hash:
|
||||
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).send()
|
||||
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||
@@ -4,7 +4,7 @@ from ipaddress import IPv4Address, IPv6Address
|
||||
from pathlib import Path as FilePath
|
||||
from typing import TYPE_CHECKING, ClassVar, Literal
|
||||
|
||||
from aiofiles import open
|
||||
from aiofiles import open as aopen
|
||||
from fastapi import BackgroundTasks, FastAPI, Path, status
|
||||
from fastapi.responses import FileResponse, HTMLResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@@ -87,7 +87,7 @@ async def _(
|
||||
|
||||
async def write_cache(path: FilePath, data: bytes) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with open(path, 'wb') as file:
|
||||
async with aopen(path, 'wb') as file:
|
||||
await file.write(data)
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ from collections.abc import Sequence
|
||||
from http import HTTPStatus
|
||||
from typing import Any
|
||||
|
||||
from fake_useragent import UserAgent
|
||||
from httpx import AsyncClient, HTTPError
|
||||
from msgspec import DecodeError, Struct, json
|
||||
from nonebot import get_driver
|
||||
@@ -113,6 +114,8 @@ class Request:
|
||||
def __init__(self, proxy: str | None) -> None:
|
||||
self.proxy = proxy
|
||||
self.anti_cloudflares: dict[str, AntiCloudflare] = {}
|
||||
self.client = AsyncClient(timeout=config.tetris.request_timeout, proxy=self.proxy)
|
||||
self.ua = UserAgent()
|
||||
|
||||
async def request(
|
||||
self,
|
||||
@@ -129,16 +132,20 @@ class Request:
|
||||
else:
|
||||
cookies = None
|
||||
headers = None
|
||||
headers = headers if extra_headers is None else extra_headers if headers is None else headers | extra_headers
|
||||
if headers is None:
|
||||
headers = {}
|
||||
if extra_headers:
|
||||
headers.update(extra_headers)
|
||||
headers.setdefault('User-Agent', self.ua.random)
|
||||
try:
|
||||
async with AsyncClient(cookies=cookies, timeout=config.tetris.request_timeout, proxy=self.proxy) as session:
|
||||
response = await session.get(str(url), headers=headers)
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
msg = f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}'
|
||||
raise RequestError(msg, status_code=response.status_code)
|
||||
if is_json:
|
||||
decoder.decode(response.content)
|
||||
return response.content
|
||||
response = await self.client.get(str(url), cookies=cookies, headers=headers)
|
||||
if response.status_code != HTTPStatus.OK:
|
||||
msg = (
|
||||
f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}'
|
||||
)
|
||||
raise RequestError(msg, status_code=response.status_code)
|
||||
if is_json:
|
||||
decoder.decode(response.content)
|
||||
except HTTPError as e:
|
||||
msg = f'请求错误 \n{e!r}'
|
||||
raise RequestError(msg) from e
|
||||
@@ -146,6 +153,8 @@ class Request:
|
||||
if enable_anti_cloudflare and url.host is not None:
|
||||
return await self.anti_cloudflares.setdefault(url.host, AntiCloudflare(url.host))(str(url), self.proxy)
|
||||
raise
|
||||
else:
|
||||
return response.content
|
||||
|
||||
async def failover_request(
|
||||
self,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from playwright.async_api import BrowserContext, TimeoutError, ViewportSize
|
||||
from playwright.async_api import BrowserContext, TimeoutError, ViewportSize # noqa: A004
|
||||
|
||||
from ..config.config import config
|
||||
from .browser import BrowserManager
|
||||
|
||||
@@ -5,7 +5,7 @@ from shutil import rmtree
|
||||
from time import time_ns
|
||||
from zipfile import ZipFile
|
||||
|
||||
from aiofiles import open
|
||||
from aiofiles import open as aopen
|
||||
from httpx import AsyncClient
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
@@ -46,7 +46,7 @@ async def download_templates(tag: str) -> Path:
|
||||
f'https://github.com/A-Minos/tetris-stats-templates/releases/download/{tag}/dist.zip',
|
||||
follow_redirects=True,
|
||||
) as response,
|
||||
open(path, 'wb') as file,
|
||||
aopen(path, 'wb') as file,
|
||||
):
|
||||
response.raise_for_status()
|
||||
progress.update(task_id, total=int(response.headers.get('content-length', 0)) or None)
|
||||
@@ -76,7 +76,7 @@ async def check_hash(hash_file_path: Path) -> bool:
|
||||
if not file_path.is_file():
|
||||
logger.error(f'{file_path.name} 不存在或不是文件')
|
||||
return False
|
||||
async with open(file_path, 'rb') as file:
|
||||
async with aopen(file_path, 'rb') as file:
|
||||
while True:
|
||||
chunk = await file.read(65535)
|
||||
if not chunk:
|
||||
|
||||
@@ -2,7 +2,7 @@ from typing import Literal, TypeAlias
|
||||
|
||||
Number: TypeAlias = float | int
|
||||
GameType: TypeAlias = Literal['IO', 'TOP', 'TOS']
|
||||
BaseCommandType: TypeAlias = Literal['bind', 'query']
|
||||
BaseCommandType: TypeAlias = Literal['bind', 'unbind', 'query']
|
||||
TETRIOCommandType: TypeAlias = BaseCommandType | Literal['rank', 'config', 'list', 'record']
|
||||
AllCommandType: TypeAlias = BaseCommandType | TETRIOCommandType
|
||||
Me: TypeAlias = Literal[
|
||||
|
||||
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -5,18 +5,20 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
prettier:
|
||||
specifier: ^3.3.3
|
||||
version: 3.3.3
|
||||
version: 3.4.2
|
||||
|
||||
packages:
|
||||
prettier@3.3.3:
|
||||
resolution:
|
||||
{ integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== }
|
||||
engines: { node: '>=14' }
|
||||
|
||||
prettier@3.4.2:
|
||||
resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
prettier@3.3.3: {}
|
||||
|
||||
prettier@3.4.2: {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "nonebot-plugin-tetris-stats"
|
||||
version = "1.6.3"
|
||||
version = "1.7.1"
|
||||
description = "一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "shoucandanghehe", email = "wallfjjd@gmail.com" }]
|
||||
@@ -10,6 +10,7 @@ dependencies = [
|
||||
"aiofiles>=24.1.0",
|
||||
"arclet-alconna<2",
|
||||
"async-lru>=2.0.4",
|
||||
"fake-useragent>=2.0.3",
|
||||
"httpx>=0.27.2",
|
||||
"jinja2>=3.1.4",
|
||||
"lxml>=5.3.0",
|
||||
@@ -22,6 +23,7 @@ dependencies = [
|
||||
"nonebot-plugin-session-orm>=0.2.0",
|
||||
"nonebot-plugin-user>=0.4.4",
|
||||
"nonebot-plugin-userinfo>=0.2.6",
|
||||
"nonebot-plugin-waiter>=0.8.0",
|
||||
"nonebot2[fastapi]>=2.3.3",
|
||||
"pandas>=2.2.3",
|
||||
"pillow>=11.0.0",
|
||||
@@ -132,8 +134,6 @@ select = [
|
||||
]
|
||||
ignore = [
|
||||
"E501", # 过长的行由 ruff format 处理, 剩余的都是字符串
|
||||
"ANN101", # 由 type checker 自动推断
|
||||
"ANN102", # 由 type checker 自动推断
|
||||
"ANN202", # 向 NoneBot 注册的函数
|
||||
"TRY003",
|
||||
"COM812", # 强制尾随逗号
|
||||
@@ -158,7 +158,7 @@ defineConstant = { PYDANTIC_V2 = true }
|
||||
typeCheckingMode = "standard"
|
||||
|
||||
[tool.bumpversion]
|
||||
current_version = "1.6.3"
|
||||
current_version = "1.7.1"
|
||||
tag = true
|
||||
sign_tags = true
|
||||
tag_name = "{new_version}"
|
||||
|
||||
19
uv.lock
generated
19
uv.lock
generated
@@ -756,6 +756,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/cd/ff116c050330c4b8c8ebc657f40501460c3e38905fb15993ef21f7d24208/expiringdictx-1.1.0-py3-none-any.whl", hash = "sha256:f5d38ae23b46a8f97da27ce0dd74e669430d43b2efe98232a4a81f10b43cde25", size = 7596 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-useragent"
|
||||
version = "2.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/a4/f8d204c20e7879c2c1fd1719095673f447a3111282bfe09c0a74a5ed5000/fake_useragent-2.0.3.tar.gz", hash = "sha256:af86a26ef8229efece8fed529b4aeb5b73747d889b60f01cd477b6f301df46e6", size = 194741 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/4f/a639b1dbdc557241e702eefb931ba24ba235c84f8fffdca3e272f096c6af/fake_useragent-2.0.3-py3-none-any.whl", hash = "sha256:8bae50abb72c309a5b3ae2f01a0b82426613fd5c4e2a04dca9332399ec44daa1", size = 201110 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.3"
|
||||
@@ -1818,13 +1827,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-plugin-tetris-stats"
|
||||
version = "1.6.2"
|
||||
version = "1.6.3"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiocache" },
|
||||
{ name = "aiofiles" },
|
||||
{ name = "arclet-alconna" },
|
||||
{ name = "async-lru" },
|
||||
{ name = "fake-useragent" },
|
||||
{ name = "httpx" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "lxml" },
|
||||
@@ -1837,6 +1847,7 @@ dependencies = [
|
||||
{ name = "nonebot-plugin-session-orm" },
|
||||
{ name = "nonebot-plugin-user" },
|
||||
{ name = "nonebot-plugin-userinfo" },
|
||||
{ name = "nonebot-plugin-waiter" },
|
||||
{ name = "nonebot2", extra = ["fastapi"] },
|
||||
{ name = "pandas" },
|
||||
{ name = "pillow" },
|
||||
@@ -1845,7 +1856,7 @@ dependencies = [
|
||||
{ name = "yarl" },
|
||||
]
|
||||
|
||||
[package.dependency-groups]
|
||||
[package.dev-dependencies]
|
||||
debug = [
|
||||
{ name = "matplotlib" },
|
||||
{ name = "memory-profiler" },
|
||||
@@ -1886,6 +1897,7 @@ requires-dist = [
|
||||
{ name = "aiofiles", specifier = ">=24.1.0" },
|
||||
{ name = "arclet-alconna", specifier = "<2" },
|
||||
{ name = "async-lru", specifier = ">=2.0.4" },
|
||||
{ name = "fake-useragent", specifier = ">=2.0.3" },
|
||||
{ name = "httpx", specifier = ">=0.27.2" },
|
||||
{ name = "jinja2", specifier = ">=3.1.4" },
|
||||
{ name = "lxml", specifier = ">=5.3.0" },
|
||||
@@ -1898,6 +1910,7 @@ requires-dist = [
|
||||
{ name = "nonebot-plugin-session-orm", specifier = ">=0.2.0" },
|
||||
{ name = "nonebot-plugin-user", specifier = ">=0.4.4" },
|
||||
{ name = "nonebot-plugin-userinfo", specifier = ">=0.2.6" },
|
||||
{ name = "nonebot-plugin-waiter", specifier = ">=0.8.0" },
|
||||
{ name = "nonebot2", extras = ["fastapi"], specifier = ">=2.3.3" },
|
||||
{ name = "pandas", specifier = ">=2.2.3" },
|
||||
{ name = "pillow", specifier = ">=11.0.0" },
|
||||
@@ -1906,7 +1919,7 @@ requires-dist = [
|
||||
{ name = "yarl", specifier = ">=1.16.0" },
|
||||
]
|
||||
|
||||
[package.metadata.dependency-groups]
|
||||
[package.metadata.requires-dev]
|
||||
debug = [
|
||||
{ name = "matplotlib", specifier = ">=3.9.2" },
|
||||
{ name = "memory-profiler", specifier = ">=0.61.0" },
|
||||
|
||||
Reference in New Issue
Block a user