Compare commits

..

8 Commits
1.6.3 ... 1.7.0

Author SHA1 Message Date
c4bde71546 🔖 1.7.0 2024-12-19 05:26:15 +08:00
呵呵です
f56f993c69 使用随机/特殊 UA (#526)
*  添加依赖 fake-useragent

*  使用随机/特殊 UA
2024-12-19 05:24:48 +08:00
呵呵です
cfcda6f597 添加 unbind 指令 (#525)
*  添加依赖 nonebot-plugin-waiter

*  添加 unbind 指令
2024-12-19 03:59:22 +08:00
renovate[bot]
96f5d4559d ⬆️ Upgrade dependency prettier to v3.4.2 (#523)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-08 02:59:36 +08:00
renovate[bot]
23f412b4f4 ⬆️ Upgrade dependency prettier to v3.4.1 (#522)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 12:27:03 +00:00
renovate[bot]
25b0d2bcdc ⬆️ Upgrade astral-sh/setup-uv action to v4 (#521)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 12:25:13 +00:00
pre-commit-ci[bot]
a116f9901c ⬆️ auto update by pre-commit hooks (#520)
* ⬆️ auto update by pre-commit hooks

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.8.1)

* 🚨 auto fix by pre-commit hooks

* 🚨 fix ruff

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: shoucandanghehe <wallfjjd@gmail.com>
2024-12-03 20:23:19 +08:00
renovate[bot]
82befd631e ⬆️ Upgrade codecov/codecov-action action to v5 (#519)
Some checks failed
Code Coverage / Test (macos-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (macos-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (ubuntu-latest, 3.12) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.10) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.11) (push) Has been cancelled
Code Coverage / Test (windows-latest, 3.12) (push) Has been cancelled
TypeCheck / TypeCheck (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Code Coverage / check (push) Has been cancelled
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 12:50:50 +08:00
27 changed files with 336 additions and 48 deletions

View File

@@ -14,7 +14,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3 - uses: astral-sh/setup-uv@v4
name: Setup UV name: Setup UV
with: with:
enable-cache: true enable-cache: true

View File

@@ -28,7 +28,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup uv - name: Setup uv
uses: astral-sh/setup-uv@v3 uses: astral-sh/setup-uv@v4
with: with:
enable-cache: true enable-cache: true
cache-suffix: ${{ env.PYTHON_VERSION }}_${{ env.OS }} cache-suffix: ${{ env.PYTHON_VERSION }}_${{ env.OS }}
@@ -42,7 +42,7 @@ jobs:
run: uv run pytest --cov=nonebot_plugin_tetris_stats --cov-report xml run: uv run pytest --cov=nonebot_plugin_tetris_stats --cov-report xml
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v5
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
env_vars: OS,PYTHON_VERSION env_vars: OS,PYTHON_VERSION

View File

@@ -9,7 +9,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3 - uses: astral-sh/setup-uv@v4
name: Setup UV name: Setup UV
with: with:
enable-cache: true enable-cache: true

View File

@@ -7,7 +7,7 @@ ci:
autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks' autoupdate_commit_msg: ':arrow_up: auto update by pre-commit hooks'
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.3 rev: v0.8.1
hooks: hooks:
- id: ruff - id: ruff
args: [--fix, --exit-non-zero-on-fix] args: [--fix, --exit-non-zero-on-fix]

View File

@@ -10,6 +10,7 @@ require_plugins = {
'nonebot_plugin_session', 'nonebot_plugin_session',
'nonebot_plugin_user', 'nonebot_plugin_user',
'nonebot_plugin_userinfo', 'nonebot_plugin_userinfo',
'nonebot_plugin_waiter',
} }
for i in require_plugins: for i in require_plugins:

View File

@@ -63,6 +63,23 @@ async def create_or_update_bind(
return status 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') T = TypeVar('T', 'TETRIOHistoricalData', 'TOPHistoricalData', 'TOSHistoricalData')
lock = Lock() lock = Lock()

View File

@@ -44,7 +44,7 @@ async def _(matcher: Matcher, account: MessageFormatError):
@alc.handle() @alc.handle()
async def _(matcher: Matcher, matches: AlcMatches): 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( await matcher.finish(
(f'{matches.error_info!r}\n' if matches.error_info is not None else '') (f'{matches.error_info!r}\n' if matches.error_info is not None else '')
+ f'输入"{matches.header_result} --help"查看帮助' + f'输入"{matches.header_result} --help"查看帮助'

View File

@@ -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) main_command.add(command)
@@ -35,4 +35,5 @@ __all__ = [
'query', 'query',
'rank', 'rank',
'record', 'record',
'unbind',
] ]

View File

@@ -1,10 +1,12 @@
from typing import Literal, overload from typing import Literal, overload
from uuid import UUID from uuid import UUID
from nonebot import __version__ as __nonebot_version__
from nonebot.compat import type_validate_json from nonebot.compat import type_validate_json
from yarl import URL from yarl import URL
from ....utils.exception import RequestError from ....utils.exception import RequestError
from ....version import __version__
from ..constant import BASE_URL from ..constant import BASE_URL
from .cache import Cache from .cache import Cache
from .schemas.base import FailedModel from .schemas.base import FailedModel
@@ -22,7 +24,12 @@ async def by(
await get( await get(
BASE_URL / f'users/by/{by_type}', BASE_URL / f'users/by/{by_type}',
parameter, 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): if isinstance(model, FailedModel):

View File

@@ -14,8 +14,8 @@ __all__ = [
'SoloSuccessModel', 'SoloSuccessModel',
'SummariesModel', 'SummariesModel',
'Zen', 'Zen',
'ZenSuccessModel',
'Zenith', 'Zenith',
'ZenithEx', 'ZenithEx',
'ZenithSuccessModel', 'ZenithSuccessModel',
'ZenSuccessModel',
] ]

View File

@@ -1,4 +1,4 @@
from re import compile from re import compile # noqa: A004
from typing import Literal from typing import Literal
from yarl import URL from yarl import URL

View File

@@ -146,7 +146,7 @@ async def _() -> None:
await get_tetra_league_data() await get_tetra_league_data()
from . import all, detail # noqa: E402 from . import all, detail # noqa: A004, E402
base_command.add(command) base_command.add(command)

View 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)

View File

@@ -29,6 +29,10 @@ command.add(
), ),
help_text='绑定 TOP 账号', help_text='绑定 TOP 账号',
), ),
Subcommand(
'unbind',
help_text='解除绑定 TOP 账号',
),
Subcommand( Subcommand(
'query', 'query',
Args( Args(
@@ -51,9 +55,22 @@ command.add(
) )
) )
alc.shortcut('(?i:top)(?i:绑定|绑|bind)', {'command': 'tstats TOP bind', 'humanized': 'top绑定'}) alc.shortcut(
alc.shortcut('(?i:top)(?i:查询|查|query|stats)', {'command': 'tstats TOP query', 'humanized': 'top查'}) '(?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')) add_block_handlers(alc.assign('TOP.query'))
from . import bind, query # noqa: E402, F401 from . import bind, query, unbind # noqa: E402, F401

View File

@@ -1,4 +1,4 @@
from re import compile from re import compile # noqa: A004
from typing import Literal from typing import Literal
from yarl import URL from yarl import URL

View 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)

View File

@@ -34,6 +34,10 @@ command.add(
), ),
help_text='绑定 茶服 账号', help_text='绑定 茶服 账号',
), ),
Subcommand(
'unbind',
help_text='解除绑定 TOS 账号',
),
Subcommand( Subcommand(
'query', 'query',
Args( Args(
@@ -56,9 +60,22 @@ command.add(
) )
) )
alc.shortcut('(?i:tos|茶服)(?i:绑定|绑|bind)', {'command': 'tstats TOS bind', 'humanized': '茶服绑定'}) alc.shortcut(
alc.shortcut('(?i:tos|茶服)(?i:查询|查|query|stats)', {'command': 'tstats TOS query', 'humanized': '茶服查'}) '(?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')) add_block_handlers(alc.assign('TOS.query'))
from . import bind, query # noqa: E402, F401 from . import bind, query, unbind # noqa: E402, F401

View File

@@ -1,4 +1,4 @@
from re import compile from re import compile # noqa: A004
from typing import Literal from typing import Literal
from yarl import URL from yarl import URL

View 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)

View File

@@ -4,7 +4,7 @@ from ipaddress import IPv4Address, IPv6Address
from pathlib import Path as FilePath from pathlib import Path as FilePath
from typing import TYPE_CHECKING, ClassVar, Literal 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 import BackgroundTasks, FastAPI, Path, status
from fastapi.responses import FileResponse, HTMLResponse, Response from fastapi.responses import FileResponse, HTMLResponse, Response
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@@ -87,7 +87,7 @@ async def _(
async def write_cache(path: FilePath, data: bytes) -> None: async def write_cache(path: FilePath, data: bytes) -> None:
path.parent.mkdir(parents=True, exist_ok=True) 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) await file.write(data)

View File

@@ -2,6 +2,7 @@ from collections.abc import Sequence
from http import HTTPStatus from http import HTTPStatus
from typing import Any from typing import Any
from fake_useragent import UserAgent
from httpx import AsyncClient, HTTPError from httpx import AsyncClient, HTTPError
from msgspec import DecodeError, Struct, json from msgspec import DecodeError, Struct, json
from nonebot import get_driver from nonebot import get_driver
@@ -113,6 +114,8 @@ class Request:
def __init__(self, proxy: str | None) -> None: def __init__(self, proxy: str | None) -> None:
self.proxy = proxy self.proxy = proxy
self.anti_cloudflares: dict[str, AntiCloudflare] = {} self.anti_cloudflares: dict[str, AntiCloudflare] = {}
self.client = AsyncClient(timeout=config.tetris.request_timeout, proxy=self.proxy)
self.ua = UserAgent()
async def request( async def request(
self, self,
@@ -129,16 +132,20 @@ class Request:
else: else:
cookies = None cookies = None
headers = 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: try:
async with AsyncClient(cookies=cookies, timeout=config.tetris.request_timeout, proxy=self.proxy) as session: response = await self.client.get(str(url), cookies=cookies, headers=headers)
response = await session.get(str(url), headers=headers) if response.status_code != HTTPStatus.OK:
if response.status_code != HTTPStatus.OK: msg = (
msg = f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}' f'请求错误 code: {response.status_code} {HTTPStatus(response.status_code).phrase}\n{response.text}'
raise RequestError(msg, status_code=response.status_code) )
if is_json: raise RequestError(msg, status_code=response.status_code)
decoder.decode(response.content) if is_json:
return response.content decoder.decode(response.content)
except HTTPError as e: except HTTPError as e:
msg = f'请求错误 \n{e!r}' msg = f'请求错误 \n{e!r}'
raise RequestError(msg) from e raise RequestError(msg) from e
@@ -146,6 +153,8 @@ class Request:
if enable_anti_cloudflare and url.host is not None: 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) return await self.anti_cloudflares.setdefault(url.host, AntiCloudflare(url.host))(str(url), self.proxy)
raise raise
else:
return response.content
async def failover_request( async def failover_request(
self, self,

View File

@@ -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 ..config.config import config
from .browser import BrowserManager from .browser import BrowserManager

View File

@@ -5,7 +5,7 @@ from shutil import rmtree
from time import time_ns from time import time_ns
from zipfile import ZipFile from zipfile import ZipFile
from aiofiles import open from aiofiles import open as aopen
from httpx import AsyncClient from httpx import AsyncClient
from nonebot import get_driver from nonebot import get_driver
from nonebot.log import logger 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', f'https://github.com/A-Minos/tetris-stats-templates/releases/download/{tag}/dist.zip',
follow_redirects=True, follow_redirects=True,
) as response, ) as response,
open(path, 'wb') as file, aopen(path, 'wb') as file,
): ):
response.raise_for_status() response.raise_for_status()
progress.update(task_id, total=int(response.headers.get('content-length', 0)) or None) 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(): if not file_path.is_file():
logger.error(f'{file_path.name} 不存在或不是文件') logger.error(f'{file_path.name} 不存在或不是文件')
return False return False
async with open(file_path, 'rb') as file: async with aopen(file_path, 'rb') as file:
while True: while True:
chunk = await file.read(65535) chunk = await file.read(65535)
if not chunk: if not chunk:

View File

@@ -2,7 +2,7 @@ from typing import Literal, TypeAlias
Number: TypeAlias = float | int Number: TypeAlias = float | int
GameType: TypeAlias = Literal['IO', 'TOP', 'TOS'] 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'] TETRIOCommandType: TypeAlias = BaseCommandType | Literal['rank', 'config', 'list', 'record']
AllCommandType: TypeAlias = BaseCommandType | TETRIOCommandType AllCommandType: TypeAlias = BaseCommandType | TETRIOCommandType
Me: TypeAlias = Literal[ Me: TypeAlias = Literal[

14
pnpm-lock.yaml generated
View File

@@ -5,18 +5,20 @@ settings:
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
importers: importers:
.: .:
devDependencies: devDependencies:
prettier: prettier:
specifier: ^3.3.3 specifier: ^3.3.3
version: 3.3.3 version: 3.4.2
packages: packages:
prettier@3.3.3:
resolution: prettier@3.4.2:
{ integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== } resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
engines: { node: '>=14' } engines: {node: '>=14'}
hasBin: true hasBin: true
snapshots: snapshots:
prettier@3.3.3: {}
prettier@3.4.2: {}

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "nonebot-plugin-tetris-stats" name = "nonebot-plugin-tetris-stats"
version = "1.6.3" version = "1.7.0"
description = "一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件" description = "一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件"
readme = "README.md" readme = "README.md"
authors = [{ name = "shoucandanghehe", email = "wallfjjd@gmail.com" }] authors = [{ name = "shoucandanghehe", email = "wallfjjd@gmail.com" }]
@@ -10,6 +10,7 @@ dependencies = [
"aiofiles>=24.1.0", "aiofiles>=24.1.0",
"arclet-alconna<2", "arclet-alconna<2",
"async-lru>=2.0.4", "async-lru>=2.0.4",
"fake-useragent>=2.0.3",
"httpx>=0.27.2", "httpx>=0.27.2",
"jinja2>=3.1.4", "jinja2>=3.1.4",
"lxml>=5.3.0", "lxml>=5.3.0",
@@ -22,6 +23,7 @@ dependencies = [
"nonebot-plugin-session-orm>=0.2.0", "nonebot-plugin-session-orm>=0.2.0",
"nonebot-plugin-user>=0.4.4", "nonebot-plugin-user>=0.4.4",
"nonebot-plugin-userinfo>=0.2.6", "nonebot-plugin-userinfo>=0.2.6",
"nonebot-plugin-waiter>=0.8.0",
"nonebot2[fastapi]>=2.3.3", "nonebot2[fastapi]>=2.3.3",
"pandas>=2.2.3", "pandas>=2.2.3",
"pillow>=11.0.0", "pillow>=11.0.0",
@@ -132,8 +134,6 @@ select = [
] ]
ignore = [ ignore = [
"E501", # 过长的行由 ruff format 处理, 剩余的都是字符串 "E501", # 过长的行由 ruff format 处理, 剩余的都是字符串
"ANN101", # 由 type checker 自动推断
"ANN102", # 由 type checker 自动推断
"ANN202", # 向 NoneBot 注册的函数 "ANN202", # 向 NoneBot 注册的函数
"TRY003", "TRY003",
"COM812", # 强制尾随逗号 "COM812", # 强制尾随逗号
@@ -158,7 +158,7 @@ defineConstant = { PYDANTIC_V2 = true }
typeCheckingMode = "standard" typeCheckingMode = "standard"
[tool.bumpversion] [tool.bumpversion]
current_version = "1.6.3" current_version = "1.7.0"
tag = true tag = true
sign_tags = true sign_tags = true
tag_name = "{new_version}" tag_name = "{new_version}"

19
uv.lock generated
View File

@@ -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 }, { 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]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.115.3" version = "0.115.3"
@@ -1818,13 +1827,14 @@ wheels = [
[[package]] [[package]]
name = "nonebot-plugin-tetris-stats" name = "nonebot-plugin-tetris-stats"
version = "1.6.2" version = "1.6.3"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "aiocache" }, { name = "aiocache" },
{ name = "aiofiles" }, { name = "aiofiles" },
{ name = "arclet-alconna" }, { name = "arclet-alconna" },
{ name = "async-lru" }, { name = "async-lru" },
{ name = "fake-useragent" },
{ name = "httpx" }, { name = "httpx" },
{ name = "jinja2" }, { name = "jinja2" },
{ name = "lxml" }, { name = "lxml" },
@@ -1837,6 +1847,7 @@ dependencies = [
{ name = "nonebot-plugin-session-orm" }, { name = "nonebot-plugin-session-orm" },
{ name = "nonebot-plugin-user" }, { name = "nonebot-plugin-user" },
{ name = "nonebot-plugin-userinfo" }, { name = "nonebot-plugin-userinfo" },
{ name = "nonebot-plugin-waiter" },
{ name = "nonebot2", extra = ["fastapi"] }, { name = "nonebot2", extra = ["fastapi"] },
{ name = "pandas" }, { name = "pandas" },
{ name = "pillow" }, { name = "pillow" },
@@ -1845,7 +1856,7 @@ dependencies = [
{ name = "yarl" }, { name = "yarl" },
] ]
[package.dependency-groups] [package.dev-dependencies]
debug = [ debug = [
{ name = "matplotlib" }, { name = "matplotlib" },
{ name = "memory-profiler" }, { name = "memory-profiler" },
@@ -1886,6 +1897,7 @@ requires-dist = [
{ name = "aiofiles", specifier = ">=24.1.0" }, { name = "aiofiles", specifier = ">=24.1.0" },
{ name = "arclet-alconna", specifier = "<2" }, { name = "arclet-alconna", specifier = "<2" },
{ name = "async-lru", specifier = ">=2.0.4" }, { name = "async-lru", specifier = ">=2.0.4" },
{ name = "fake-useragent", specifier = ">=2.0.3" },
{ name = "httpx", specifier = ">=0.27.2" }, { name = "httpx", specifier = ">=0.27.2" },
{ name = "jinja2", specifier = ">=3.1.4" }, { name = "jinja2", specifier = ">=3.1.4" },
{ name = "lxml", specifier = ">=5.3.0" }, { name = "lxml", specifier = ">=5.3.0" },
@@ -1898,6 +1910,7 @@ requires-dist = [
{ name = "nonebot-plugin-session-orm", specifier = ">=0.2.0" }, { name = "nonebot-plugin-session-orm", specifier = ">=0.2.0" },
{ name = "nonebot-plugin-user", specifier = ">=0.4.4" }, { name = "nonebot-plugin-user", specifier = ">=0.4.4" },
{ name = "nonebot-plugin-userinfo", specifier = ">=0.2.6" }, { 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 = "nonebot2", extras = ["fastapi"], specifier = ">=2.3.3" },
{ name = "pandas", specifier = ">=2.2.3" }, { name = "pandas", specifier = ">=2.2.3" },
{ name = "pillow", specifier = ">=11.0.0" }, { name = "pillow", specifier = ">=11.0.0" },
@@ -1906,7 +1919,7 @@ requires-dist = [
{ name = "yarl", specifier = ">=1.16.0" }, { name = "yarl", specifier = ">=1.16.0" },
] ]
[package.metadata.dependency-groups] [package.metadata.requires-dev]
debug = [ debug = [
{ name = "matplotlib", specifier = ">=3.9.2" }, { name = "matplotlib", specifier = ">=3.9.2" },
{ name = "memory-profiler", specifier = ">=0.61.0" }, { name = "memory-profiler", specifier = ">=0.61.0" },